diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
commit | 19fcec84d8d7d21e796c7624e521b60d28ee21ed (patch) | |
tree | 42d26aa27d1e3f7c0b8bd3fd14e7d7082f5008dc /src/jaegertracing/thrift/compiler | |
parent | Initial commit. (diff) | |
download | ceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.tar.xz ceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.zip |
Adding upstream version 16.2.11+ds.upstream/16.2.11+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
111 files changed, 94159 insertions, 0 deletions
diff --git a/src/jaegertracing/thrift/compiler/cpp/CMakeLists.txt b/src/jaegertracing/thrift/compiler/cpp/CMakeLists.txt new file mode 100644 index 000000000..17dae4787 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/CMakeLists.txt @@ -0,0 +1,123 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +cmake_minimum_required(VERSION 3.3) +project("thrift-compiler" VERSION ${PACKAGE_VERSION}) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/thrift/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/thrift/version.h) + +find_package(FLEX REQUIRED) +find_package(BISON REQUIRED) + +# create directory for thrifty and thriftl +file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/thrift/) + +# Create flex and bison files and build the lib parse static library +BISON_TARGET(thrifty ${CMAKE_CURRENT_SOURCE_DIR}/src/thrift/thrifty.yy ${CMAKE_CURRENT_BINARY_DIR}/thrift/thrifty.cc) +FLEX_TARGET(thriftl ${CMAKE_CURRENT_SOURCE_DIR}/src/thrift/thriftl.ll ${CMAKE_CURRENT_BINARY_DIR}/thrift/thriftl.cc) +ADD_FLEX_BISON_DEPENDENCY(thriftl thrifty) + +set(parse_SOURCES + ${CMAKE_CURRENT_BINARY_DIR}/thrift/thrifty.cc + ${CMAKE_CURRENT_BINARY_DIR}/thrift/thriftl.cc + ${CMAKE_CURRENT_BINARY_DIR}/thrift/thrifty.hh +) + +add_library(parse STATIC ${parse_SOURCES}) + +# Create the thrift compiler +set(compiler_core + src/thrift/common.cc + src/thrift/generate/t_generator.cc + src/thrift/parse/t_typedef.cc + src/thrift/parse/parse.cc + ${CMAKE_CURRENT_BINARY_DIR}/thrift/version.h +) + +set(thrift-compiler_SOURCES + src/thrift/main.cc + src/thrift/audit/t_audit.cpp +) + +set(thrift_compiler_LANGS +) + +# This macro adds an option THRIFT_COMPILER_${NAME} +# that allows enabling or disabling certain languages +macro(THRIFT_ADD_COMPILER name description initial) + string(TOUPPER "THRIFT_COMPILER_${name}" enabler) + set(src "src/thrift/generate/t_${name}_generator.cc") + option(${enabler} ${description} ${initial}) + if(${enabler}) + list(APPEND thrift-compiler_SOURCES ${src}) + list(APPEND thrift_compiler_LANGS ${name}) + endif() +endmacro() + +# The following compiler can be enabled or disabled +THRIFT_ADD_COMPILER(as3 "Enable compiler for ActionScript 3" ON) +THRIFT_ADD_COMPILER(c_glib "Enable compiler for C with Glib" ON) +THRIFT_ADD_COMPILER(cl "Enable compiler for Common LISP" ON) +THRIFT_ADD_COMPILER(cpp "Enable compiler for C++" ON) +THRIFT_ADD_COMPILER(csharp "Enable compiler for C#" ON) +THRIFT_ADD_COMPILER(d "Enable compiler for D" ON) +THRIFT_ADD_COMPILER(dart "Enable compiler for Dart" ON) +THRIFT_ADD_COMPILER(delphi "Enable compiler for Delphi" ON) +THRIFT_ADD_COMPILER(erl "Enable compiler for Erlang" ON) +THRIFT_ADD_COMPILER(go "Enable compiler for Go" ON) +THRIFT_ADD_COMPILER(gv "Enable compiler for GraphViz" ON) +THRIFT_ADD_COMPILER(haxe "Enable compiler for Haxe" ON) +THRIFT_ADD_COMPILER(hs "Enable compiler for Haskell" ON) +THRIFT_ADD_COMPILER(html "Enable compiler for HTML Documentation" ON) +THRIFT_ADD_COMPILER(java "Enable compiler for Java" ON) +THRIFT_ADD_COMPILER(javame "Enable compiler for Java ME" ON) +THRIFT_ADD_COMPILER(js "Enable compiler for JavaScript" ON) +THRIFT_ADD_COMPILER(json "Enable compiler for JSON" ON) +THRIFT_ADD_COMPILER(lua "Enable compiler for Lua" ON) +THRIFT_ADD_COMPILER(netcore "Enable compiler for .NET Core" ON) +THRIFT_ADD_COMPILER(netstd "Enable compiler for .NET Standard" ON) +THRIFT_ADD_COMPILER(ocaml "Enable compiler for OCaml" ON) +THRIFT_ADD_COMPILER(perl "Enable compiler for Perl" ON) +THRIFT_ADD_COMPILER(php "Enable compiler for PHP" ON) +THRIFT_ADD_COMPILER(py "Enable compiler for Python 2.0" ON) +THRIFT_ADD_COMPILER(rb "Enable compiler for Ruby" ON) +THRIFT_ADD_COMPILER(rs "Enable compiler for Rust" ON) +THRIFT_ADD_COMPILER(st "Enable compiler for Smalltalk" ON) +THRIFT_ADD_COMPILER(swift "Enable compiler for Cocoa Swift" ON) +THRIFT_ADD_COMPILER(xml "Enable compiler for XML" ON) +THRIFT_ADD_COMPILER(xsd "Enable compiler for XSD" ON) + +# Thrift is looking for include files in the src directory +# we also add the current binary directory for generated files +include_directories(${CMAKE_CURRENT_BINARY_DIR} src) + +list(APPEND thrift-compiler_SOURCES ${compiler_core}) + +add_executable(thrift-compiler ${thrift-compiler_SOURCES}) + +set_target_properties(thrift-compiler PROPERTIES RUNTIME_OUTPUT_DIRECTORY bin/) +set_target_properties(thrift-compiler PROPERTIES OUTPUT_NAME thrift) + +target_link_libraries(thrift-compiler parse) + +install(TARGETS thrift-compiler DESTINATION bin) + +if(BUILD_TESTING) + add_subdirectory(test) +endif() diff --git a/src/jaegertracing/thrift/compiler/cpp/Makefile.am b/src/jaegertracing/thrift/compiler/cpp/Makefile.am new file mode 100644 index 000000000..91801c6ab --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/Makefile.am @@ -0,0 +1,127 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# +# Contains some contributions under the Thrift Software License. +# Please see doc/old-thrift-license.txt in the Thrift distribution for +# details. + +AUTOMAKE_OPTIONS = subdir-objects nostdinc + +SUBDIRS = src . +if WITH_TESTS +SUBDIRS += test +endif + +bin_PROGRAMS = thrift + +thrift_OBJDIR = obj + +thrift_SOURCES = src/thrift/audit/t_audit.cpp \ + src/thrift/audit/t_audit.h \ + src/thrift/common.cc \ + src/thrift/common.h \ + src/thrift/generate/t_generator.cc \ + src/thrift/generate/t_generator.h \ + src/thrift/generate/t_generator_registry.h \ + src/thrift/generate/t_html_generator.h \ + src/thrift/generate/t_oop_generator.h \ + src/thrift/globals.h \ + src/thrift/logging.h \ + src/thrift/main.cc \ + src/thrift/main.h \ + src/thrift/parse/parse.cc \ + src/thrift/parse/t_base_type.h \ + src/thrift/parse/t_const.h \ + src/thrift/parse/t_const_value.h \ + src/thrift/parse/t_container.h \ + src/thrift/parse/t_doc.h \ + src/thrift/parse/t_enum.h \ + src/thrift/parse/t_enum_value.h \ + src/thrift/parse/t_field.h \ + src/thrift/parse/t_function.h \ + src/thrift/parse/t_list.h \ + src/thrift/parse/t_map.h \ + src/thrift/parse/t_program.h \ + src/thrift/parse/t_scope.h \ + src/thrift/parse/t_service.h \ + src/thrift/parse/t_set.h \ + src/thrift/parse/t_struct.h \ + src/thrift/parse/t_type.h \ + src/thrift/parse/t_typedef.cc \ + src/thrift/parse/t_typedef.h \ + src/thrift/platform.h + +# Specific client generator source +thrift_SOURCES += src/thrift/generate/t_as3_generator.cc \ + src/thrift/generate/t_c_glib_generator.cc \ + src/thrift/generate/t_cl_generator.cc \ + src/thrift/generate/t_cpp_generator.cc \ + src/thrift/generate/t_csharp_generator.cc \ + src/thrift/generate/t_d_generator.cc \ + src/thrift/generate/t_dart_generator.cc \ + src/thrift/generate/t_delphi_generator.cc \ + src/thrift/generate/t_erl_generator.cc \ + src/thrift/generate/t_go_generator.cc \ + src/thrift/generate/t_gv_generator.cc \ + src/thrift/generate/t_haxe_generator.cc \ + src/thrift/generate/t_hs_generator.cc \ + src/thrift/generate/t_html_generator.cc \ + src/thrift/generate/t_java_generator.cc \ + src/thrift/generate/t_javame_generator.cc \ + src/thrift/generate/t_js_generator.cc \ + src/thrift/generate/t_json_generator.cc \ + src/thrift/generate/t_lua_generator.cc \ + src/thrift/generate/t_netcore_generator.cc \ + src/thrift/generate/t_netcore_generator.h \ + src/thrift/generate/t_netstd_generator.cc \ + src/thrift/generate/t_netstd_generator.h \ + src/thrift/generate/t_ocaml_generator.cc \ + src/thrift/generate/t_perl_generator.cc \ + src/thrift/generate/t_php_generator.cc \ + src/thrift/generate/t_py_generator.cc \ + src/thrift/generate/t_rb_generator.cc \ + src/thrift/generate/t_rs_generator.cc \ + src/thrift/generate/t_st_generator.cc \ + src/thrift/generate/t_swift_generator.cc \ + src/thrift/generate/t_xml_generator.cc \ + src/thrift/generate/t_xsd_generator.cc + +thrift_CPPFLAGS = -I$(srcdir)/src +thrift_CXXFLAGS = -Wall -Wextra -pedantic -Werror +thrift_LDADD = @LEXLIB@ src/thrift/libparse.a + +WINDOWS_DIST = \ + compiler.sln \ + compiler.vcxproj \ + compiler.vcxproj.filters + +EXTRA_DIST = \ + coding_standards.md \ + README.md \ + CMakeLists.txt \ + test \ + $(WINDOWS_DIST) + +clean-local: + $(RM) version.h + +src/thrift/main.cc: src/thrift/version.h + +style-local: + $(CPPSTYLE_CMD) diff --git a/src/jaegertracing/thrift/compiler/cpp/README.md b/src/jaegertracing/thrift/compiler/cpp/README.md new file mode 100644 index 000000000..5c04cd869 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/README.md @@ -0,0 +1,175 @@ +# Build Thrift IDL compiler using CMake + +<!-- TOC --> + +- [Build Thrift IDL compiler using CMake](#build-thrift-idl-compiler-using-cmake) + - [Build on Unix-like System](#build-on-unix-like-system) + - [Prerequisites](#prerequisites) + - [Build using CMake](#build-using-cmake) + - [Build with Eclipse IDE](#build-with-eclipse-ide) + - [Build with XCode IDE in MacOS](#build-with-xcode-ide-in-macos) + - [Usage of other IDEs](#usage-of-other-ides) + - [Build on Windows](#build-on-windows) + - [Prerequisites](#prerequisites-1) + - [Build using Git Bash](#build-using-git-bash) + - [Using Visual Studio and Win flex-bison](#using-visual-studio-and-win-flex-bison) + - [Cross compile using mingw32 and generate a Windows Installer with CPack](#cross-compile-using-mingw32-and-generate-a-windows-installer-with-cpack) +- [Other cases](#other-cases) + - [Building the Thrift IDL compiler in Windows without CMake](#building-the-thrift-idl-compiler-in-windows-without-cmake) +- [Unit tests for compiler](#unit-tests-for-compiler) + - [Using boost test](#using-boost-test) + - [Using Catch C++ test library](#using-catch-c-test-library) +- [Have a Happy free time and holidays](#have-a-happy-free-time-and-holidays) + +<!-- /TOC --> + +## Build on Unix-like System + +### Prerequisites +- Install CMake +- Install flex and bison + +### Build using CMake + +- Go to **thrift\compiler\cpp** +- Use the following steps to build using cmake: + +``` +mkdir cmake-build && cd cmake-build +cmake .. +make +``` + +#### Build with Eclipse IDE + +- Go to **thrift\compiler\cpp** +- Use the following steps to build using cmake: + +``` +mkdir cmake-ec && cd cmake-ec +cmake -G "Eclipse CDT4 - Unix Makefiles" .. +make +``` + +Now open the folder cmake-ec using eclipse. + +#### Build with XCode IDE in MacOS + +- Install/update flex, bison and cmake with brew + +``` +brew install cmake +brew install bison +``` + +- Go to **thrift\compiler\cpp** +- Run commands in command line: + +``` +mkdir cmake-build && cd cmake-build +cmake -G "Xcode" .. +cmake --build . +``` + +#### Usage of other IDEs + +Please check list of supported IDE + +``` +cmake --help +``` + +## Build on Windows + +### Prerequisites +- Install CMake - https://cmake.org/download/ +- In case if you want to build without Git Bash - install winflexbison - https://sourceforge.net/projects/winflexbison/ +- In case if you want to build with Visual Studio - install Visual Studio + - Better to use the latest stable Visual Studio Community Edition - https://www.visualstudio.com/vs/whatsnew/ (ensure that you installed workload "Desktop Development with C++" for VS2017) - Microsoft added some support for CMake and improving it in Visual Studio + +### Build using Git Bash + +Git Bash provides flex and bison + +- Go to **thrift\compiler\cpp** +- Use the following steps to build using cmake: + +``` +mkdir cmake-vs && cd cmake-vs +cmake -DWITH_SHARED_LIB=off .. +cmake --build . +``` + +### Using Visual Studio and Win flex-bison + +- Generate a Visual Studio project for version of Visual Studio which you have (**cmake --help** can show list of supportable VS versions): +- Run commands in command line: +``` +mkdir cmake-vs +cd cmake-vs +cmake -G "Visual Studio 15 2017" .. +``` +- Now open the folder cmake-vs using Visual Studio. + +### Cross compile using mingw32 and generate a Windows Installer with CPack + +``` +mkdir cmake-mingw32 && cd cmake-mingw32 +cmake -DCMAKE_TOOLCHAIN_FILE=../build/cmake/mingw32-toolchain.cmake -DBUILD_COMPILER=ON -DBUILD_LIBRARIES=OFF -DBUILD_TESTING=OFF .. +cpack +``` + +# Other cases + +## Building the Thrift IDL compiler in Windows without CMake + +If you don't want to use CMake you can use the already available Visual Studio 2010 solution. + +The Visual Studio project contains pre-build commands to generate the thriftl.cc, thrifty.cc and thrifty.hh files which are necessary to build the compiler. + +These depend on bison, flex and their dependencies to work properly. + +Download flex & bison as described above. + +Place these binaries somewhere in the path and rename win_flex.exe and win_bison.exe to flex.exe and bison.exe respectively. + +If this doesn't work on a system, try these manual pre-build steps. + +Open compiler.sln and remove the Pre-build commands under the project's: Properties -> Build Events -> Pre-Build Events. + +From a command prompt: +``` +cd thrift/compiler/cpp +flex -o src\thrift\thriftl.cc src\thrift\thriftl.ll +``` +In the generated thriftl.cc, comment out #include <unistd.h> + +Place a copy of bison.simple in thrift/compiler/cpp +``` +bison -y -o "src/thrift/thrifty.cc" --defines src/thrift/thrifty.yy +move src\thrift\thrifty.cc.hh src\thrift\thrifty.hh +``` + +Bison might generate the yacc header file "thrifty.cc.h" with just one h ".h" extension; in this case you'll have to rename to "thrifty.h". + +``` +move src\thrift\version.h.in src\thrift\version.h +``` + +Download inttypes.h from the interwebs and place it in an include path +location (e.g. thrift/compiler/cpp/src). + +Build the compiler in Visual Studio. + +# Unit tests for compiler + +## Using boost test +- pls check **test** folder + +## Using Catch C++ test library + +Added generic way to cover code by tests for many languages (you just need to make a correct header file for generator for your language - example in **netcore** implementation) + +- pls check **tests** folder + +# Have a Happy free time and holidays diff --git a/src/jaegertracing/thrift/compiler/cpp/coding_standards.md b/src/jaegertracing/thrift/compiler/cpp/coding_standards.md new file mode 100644 index 000000000..ea089467e --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/coding_standards.md @@ -0,0 +1,4 @@ +## Compiler Coding Standards + + * When making small change / bugfix - follow style as seen in nearby code. + * When making major refactor and / or adding new feature - follow style for C++ library
\ No newline at end of file diff --git a/src/jaegertracing/thrift/compiler/cpp/compiler.sln b/src/jaegertracing/thrift/compiler/cpp/compiler.sln new file mode 100644 index 000000000..94961aaef --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/compiler.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "compiler", "compiler.vcxproj", "{89975A1A-F799-4556-98B8-64E30AB39A90}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {89975A1A-F799-4556-98B8-64E30AB39A90}.Debug|Win32.ActiveCfg = Debug|Win32 + {89975A1A-F799-4556-98B8-64E30AB39A90}.Debug|Win32.Build.0 = Debug|Win32 + {89975A1A-F799-4556-98B8-64E30AB39A90}.Release|Win32.ActiveCfg = Release|Win32 + {89975A1A-F799-4556-98B8-64E30AB39A90}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/jaegertracing/thrift/compiler/cpp/compiler.vcxproj b/src/jaegertracing/thrift/compiler/cpp/compiler.vcxproj new file mode 100644 index 000000000..06c4eb4d8 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/compiler.vcxproj @@ -0,0 +1,251 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <ItemGroup> + <ClInclude Include="src\thrift\audit\t_audit.h" /> + <ClInclude Include="src\thrift\common.h" /> + <ClInclude Include="src\thrift\generate\t_generator.h" /> + <ClInclude Include="src\thrift\generate\t_generator_registry.h" /> + <ClInclude Include="src\thrift\generate\t_oop_generator.h" /> + <ClInclude Include="src\thrift\generate\t_html_generator.h" /> + <ClInclude Include="src\thrift\globals.h" /> + <ClInclude Include="src\thrift\main.h" /> + <ClInclude Include="src\thrift\parse\t_base_type.h" /> + <ClInclude Include="src\thrift\parse\t_const.h" /> + <ClInclude Include="src\thrift\parse\t_const_value.h" /> + <ClInclude Include="src\thrift\parse\t_container.h" /> + <ClInclude Include="src\thrift\parse\t_doc.h" /> + <ClInclude Include="src\thrift\parse\t_enum.h" /> + <ClInclude Include="src\thrift\parse\t_enum_value.h" /> + <ClInclude Include="src\thrift\parse\t_field.h" /> + <ClInclude Include="src\thrift\parse\t_function.h" /> + <ClInclude Include="src\thrift\parse\t_list.h" /> + <ClInclude Include="src\thrift\parse\t_map.h" /> + <ClInclude Include="src\thrift\parse\t_program.h" /> + <ClInclude Include="src\thrift\parse\t_scope.h" /> + <ClInclude Include="src\thrift\parse\t_service.h" /> + <ClInclude Include="src\thrift\parse\t_set.h" /> + <ClInclude Include="src\thrift\parse\t_struct.h" /> + <ClInclude Include="src\thrift\parse\t_type.h" /> + <ClInclude Include="src\thrift\parse\t_typedef.h" /> + <ClInclude Include="src\thrift\platform.h" /> + <ClInclude Include="src\thrift\thrifty.hh" /> + <ClInclude Include="src\thrift\windows\config.h" /> + <ClInclude Include="src\thrift\version.h" /> + </ItemGroup> + <ItemGroup> + <ClCompile Include="src\thrift\audit\t_audit.cpp" /> + <ClCompile Include="src\thrift\common.cc" /> + <ClCompile Include="src\thrift\generate\t_as3_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_c_glib_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_cl_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_cpp_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_csharp_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_d_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_dart_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_delphi_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_erl_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_go_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_gv_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_haxe_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_hs_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_html_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_java_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_javame_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_js_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_json_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_lua_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_netcore_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_netstd_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_ocaml_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_perl_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_php_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_py_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_rb_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_rs_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_st_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_swift_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_xml_generator.cc" /> + <ClCompile Include="src\thrift\generate\t_xsd_generator.cc" /> + <ClCompile Include="src\thrift\main.cc" /> + <ClCompile Include="src\thrift\parse\parse.cc" /> + <ClCompile Include="src\thrift\parse\t_typedef.cc" /> + <ClCompile Include="src\thrift\thriftl.cc" /> + <ClCompile Include="src\thrift\thrifty.cc" /> + </ItemGroup> + <ItemGroup> + <None Include="src\thrift\thriftl.ll" /> + <None Include="src\thrift\thrifty.yy" /> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{89975A1A-F799-4556-98B8-64E30AB39A90}</ProjectGuid> + <Keyword>Win32Proj</Keyword> + <RootNamespace>compiler</RootNamespace> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <CharacterSet>MultiByte</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <CharacterSet>MultiByte</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>MultiByte</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>MultiByte</CharacterSet> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LinkIncremental>true</LinkIncremental> + <IncludePath>$(ProjectDir)\src\;$(ProjectDir)\src\windows\;$(IncludePath)</IncludePath> + <TargetName>thrift</TargetName> + <ExecutablePath>$(ExecutablePath);C:\Program Files (x86)\Git\bin</ExecutablePath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <LinkIncremental>true</LinkIncremental> + <IncludePath>$(ProjectDir)\src\;$(ProjectDir)\src\windows\;$(IncludePath)</IncludePath> + <TargetName>thrift</TargetName> + <ExecutablePath>$(ExecutablePath);C:\Program Files (x86)\Git\bin</ExecutablePath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <LinkIncremental>false</LinkIncremental> + <IncludePath>$(ProjectDir)\src\;$(ProjectDir)\src\windows\;$(IncludePath)</IncludePath> + <TargetName>thrift</TargetName> + <ExecutablePath>$(ExecutablePath);C:\Program Files (x86)\Git\bin</ExecutablePath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <LinkIncremental>false</LinkIncremental> + <IncludePath>$(ProjectDir)\src\;$(ProjectDir)\src\windows\;$(IncludePath)</IncludePath> + <TargetName>thrift</TargetName> + <ExecutablePath>$(ExecutablePath);C:\Program Files (x86)\Git\bin</ExecutablePath> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <Optimization>Disabled</Optimization> + <PreprocessorDefinitions>WIN32;MINGW;YY_NO_UNISTD_H;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ForcedIncludeFiles>thrift\windows\config.h</ForcedIncludeFiles> + <CompileAs>CompileAsCpp</CompileAs> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + <PreBuildEvent> + <Command>flex -o "src\\thrift\\thriftl.cc" src/thrift/thriftl.ll && bison -y -o "src\\thrift\\thrifty.cc" --defines="src\\thrift\\thrifty.hh" src/thrift/thrifty.yy</Command> + </PreBuildEvent> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <ClCompile> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <Optimization>Disabled</Optimization> + <PreprocessorDefinitions>WIN32;MINGW;YY_NO_UNISTD_H;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ForcedIncludeFiles>thrift\windows\config.h</ForcedIncludeFiles> + <CompileAs>CompileAsCpp</CompileAs> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + <PreBuildEvent> + <Command>flex -o "src\\thrift\\thriftl.cc" src/thrift/thriftl.ll && bison -y -o "src\\thrift\\thrifty.cc" --defines="src\\thrift\\thrifty.hh" src/thrift/thrifty.yy</Command> + </PreBuildEvent> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <PrecompiledHeader> + </PrecompiledHeader> + <Optimization>MaxSpeed</Optimization> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <PreprocessorDefinitions>WIN32;MINGW;YY_NO_UNISTD_H;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ForcedIncludeFiles>thrift\windows\config.h</ForcedIncludeFiles> + <CompileAs>CompileAsCpp</CompileAs> + <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + </Link> + <PreBuildEvent> + <Command>flex -o "src\\thrift\\thriftl.cc" src/thrift/thriftl.ll && bison -y -o "src\\thrift\\thrifty.cc" --defines="src\\thrift\\thrifty.hh" src/thrift/thrifty.yy</Command> + </PreBuildEvent> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <PrecompiledHeader> + </PrecompiledHeader> + <Optimization>MaxSpeed</Optimization> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <PreprocessorDefinitions>WIN32;MINGW;YY_NO_UNISTD_H;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ForcedIncludeFiles>thrift\windows\config.h</ForcedIncludeFiles> + <CompileAs>CompileAsCpp</CompileAs> + <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + </Link> + <PreBuildEvent> + <Command>flex -o "src\\thrift\\thriftl.cc" src/thrift/thriftl.ll && bison -y -o "src\\thrift\\thrifty.cc" --defines="src\\thrift\\thrifty.hh" src/thrift/thrifty.yy</Command> + </PreBuildEvent> + </ItemDefinitionGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project> diff --git a/src/jaegertracing/thrift/compiler/cpp/compiler.vcxproj.filters b/src/jaegertracing/thrift/compiler/cpp/compiler.vcxproj.filters new file mode 100644 index 000000000..5a575894f --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/compiler.vcxproj.filters @@ -0,0 +1,211 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <ClInclude Include="src\audit\t_audit.h" /> + <ClInclude Include="src\generate\t_generator.h"> + <Filter>generate</Filter> + </ClInclude> + <ClInclude Include="src\generate\t_generator_registry.h"> + <Filter>generate</Filter> + </ClInclude> + <ClInclude Include="src\generate\t_oop_generator.h"> + <Filter>generate</Filter> + </ClInclude> + <ClInclude Include="src\generate\t_html_generator.h"> + <Filter>generate</Filter> + </ClInclude> + <ClInclude Include="src\globals.h" /> + <ClInclude Include="src\main.h" /> + <ClInclude Include="src\parse\t_base_type.h"> + <Filter>parse</Filter> + </ClInclude> + <ClInclude Include="src\parse\t_const.h"> + <Filter>parse</Filter> + </ClInclude> + <ClInclude Include="src\parse\t_const_value.h"> + <Filter>parse</Filter> + </ClInclude> + <ClInclude Include="src\parse\t_container.h"> + <Filter>parse</Filter> + </ClInclude> + <ClInclude Include="src\parse\t_doc.h"> + <Filter>parse</Filter> + </ClInclude> + <ClInclude Include="src\parse\t_enum.h"> + <Filter>parse</Filter> + </ClInclude> + <ClInclude Include="src\parse\t_enum_value.h"> + <Filter>parse</Filter> + </ClInclude> + <ClInclude Include="src\parse\t_field.h"> + <Filter>parse</Filter> + </ClInclude> + <ClInclude Include="src\parse\t_function.h"> + <Filter>parse</Filter> + </ClInclude> + <ClInclude Include="src\parse\t_list.h"> + <Filter>parse</Filter> + </ClInclude> + <ClInclude Include="src\parse\t_map.h"> + <Filter>parse</Filter> + </ClInclude> + <ClInclude Include="src\parse\t_program.h"> + <Filter>parse</Filter> + </ClInclude> + <ClInclude Include="src\parse\t_scope.h"> + <Filter>parse</Filter> + </ClInclude> + <ClInclude Include="src\parse\t_service.h"> + <Filter>parse</Filter> + </ClInclude> + <ClInclude Include="src\parse\t_set.h"> + <Filter>parse</Filter> + </ClInclude> + <ClInclude Include="src\parse\t_struct.h"> + <Filter>parse</Filter> + </ClInclude> + <ClInclude Include="src\parse\t_type.h"> + <Filter>parse</Filter> + </ClInclude> + <ClInclude Include="src\parse\t_typedef.h"> + <Filter>parse</Filter> + </ClInclude> + <ClInclude Include="src\platform.h" /> + <ClInclude Include="src\thrifty.hh" /> + <ClInclude Include="src\windows\config.h"> + <Filter>windows</Filter> + </ClInclude> + <ClInclude Include="src\windows\version.h"> + <Filter>windows</Filter> + </ClInclude> + </ItemGroup> + <ItemGroup> + <Filter Include="windows"> + <UniqueIdentifier>{ae9d0a15-57ae-4f01-87a4-81f790249b83}</UniqueIdentifier> + </Filter> + <Filter Include="parse"> + <UniqueIdentifier>{5df016bb-591b-420a-a535-4330d9187fbf}</UniqueIdentifier> + </Filter> + <Filter Include="generate"> + <UniqueIdentifier>{b5c626af-afa5-433c-8e10-ee734533cb68}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <ClCompile Include="src\audit\t_audit.cpp"/> + <ClCompile Include="src\generate\t_as3_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_cocoa_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_cpp_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_csharp_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_c_glib_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_d_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_dart_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_delphi_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_erl_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_go_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_gv_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_haxe_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_hs_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_html_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_javame_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_java_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_js_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_ocaml_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_perl_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_php_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_py_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_rb_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_netcore_generator.h"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_netstd_generator.h"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_netcore_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_netstd_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_rs_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_st_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_swift_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_xsd_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_xml_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\main.cc" /> + <ClCompile Include="src\parse\parse.cc"> + <Filter>parse</Filter> + </ClCompile> + <ClCompile Include="src\thriftl.cc" /> + <ClCompile Include="src\thrifty.cc" /> + <ClCompile Include="src\parse\t_typedef.cc"> + <Filter>parse</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_json_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + <ClCompile Include="src\generate\t_lua_generator.cc"> + <Filter>generate</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <None Include="src\thriftl.ll" /> + <None Include="src\thrifty.yy" /> + </ItemGroup> +</Project> diff --git a/src/jaegertracing/thrift/compiler/cpp/src/Makefile.am b/src/jaegertracing/thrift/compiler/cpp/src/Makefile.am new file mode 100644 index 000000000..0297708e1 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/Makefile.am @@ -0,0 +1,39 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# +# Contains some contributions under the Thrift Software License. +# Please see doc/old-thrift-license.txt in the Thrift distribution for +# details. + +AUTOMAKE_OPTIONS = subdir-objects nostdinc + +AM_YFLAGS = -d + +BUILT_SOURCES = thrift/thrifty.cc + +noinst_LIBRARIES = thrift/libparse.a + +thrift_libparse_a_CPPFLAGS = -I$(srcdir) +thrift_libparse_a_CXXFLAGS = -Wall -Wno-sign-compare -Wno-unused + +thrift_libparse_a_SOURCES = thrift/thrifty.yy \ + thrift/thriftl.ll + +clean-local: + $(RM) thrift/thriftl.cc thrift/thrifty.cc thrift/thrifty.h thrift/thrifty.hh diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/audit/t_audit.cpp b/src/jaegertracing/thrift/compiler/cpp/src/thrift/audit/t_audit.cpp new file mode 100644 index 000000000..fbce15c77 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/audit/t_audit.cpp @@ -0,0 +1,454 @@ + +#include <cassert> +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <time.h> +#include <string> +#include <algorithm> +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <limits.h> + +// Careful: must include globals first for extern definitions +#include "thrift/globals.h" + +#include "thrift/parse/t_program.h" +#include "thrift/parse/t_scope.h" +#include "thrift/parse/t_const.h" +#include "thrift/parse/t_field.h" + +#include "thrift/version.h" + +#include "thrift/audit/t_audit.h" + +extern int g_warn; +extern std::string g_curpath; +extern bool g_return_failure; + +void thrift_audit_warning(int level, const char* fmt, ...) { + if (g_warn < level) { + return; + } + va_list args; + printf("[Thrift Audit Warning:%s] ", g_curpath.c_str()); + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + printf("\n"); +} + +void thrift_audit_failure(const char* fmt, ...) { + va_list args; + fprintf(stderr, "[Thrift Audit Failure:%s] ", g_curpath.c_str()); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); + g_return_failure = true; +} + +void compare_namespace(t_program* newProgram, t_program* oldProgram) +{ + const std::map<std::string, std::string>& newNamespaceMap = newProgram->get_all_namespaces(); + const std::map<std::string, std::string>& oldNamespaceMap = oldProgram->get_all_namespaces(); + + for(const auto & oldNamespaceMapIt : oldNamespaceMap) + { + auto newNamespaceMapIt = newNamespaceMap.find(oldNamespaceMapIt.first); + if(newNamespaceMapIt == newNamespaceMap.end()) + { + thrift_audit_warning(1, "Language %s not found in new thrift file\n", (oldNamespaceMapIt.first).c_str()); + } + else if((newNamespaceMapIt->second) != oldNamespaceMapIt.second) + { + thrift_audit_warning(1, "Namespace %s changed in new thrift file\n", (oldNamespaceMapIt.second).c_str()); + } + } +} + +void compare_enum_values(t_enum* newEnum,t_enum* oldEnum) +{ + const std::vector<t_enum_value*>& oldEnumValues = oldEnum->get_constants(); + for(auto oldEnumValue : oldEnumValues) + { + int enumValue = oldEnumValue->get_value(); + t_enum_value* newEnumValue = newEnum->get_constant_by_value(enumValue); + if(newEnumValue != nullptr) + { + std::string enumName = oldEnumValue->get_name(); + if(enumName != newEnumValue->get_name()) + { + thrift_audit_warning(1, "Name of the value %d changed in enum %s\n", enumValue, oldEnum->get_name().c_str()); + } + } + else + { + thrift_audit_failure("Enum value %d missing in %s\n", enumValue, oldEnum->get_name().c_str()); + } + + } +} + +void compare_enums(const std::vector<t_enum*>& newEnumList, const std::vector<t_enum*>& oldEnumList) +{ + std::map<std::string,t_enum*> newEnumMap; + std::vector<t_enum*>::const_iterator newEnumIt; + for(newEnumIt = newEnumList.begin(); newEnumIt != newEnumList.end(); newEnumIt++) + { + newEnumMap[(*newEnumIt)->get_name()] = *newEnumIt; + } + std::vector<t_enum*>::const_iterator oldEnumIt; + for(oldEnumIt = oldEnumList.begin(); oldEnumIt != oldEnumList.end(); oldEnumIt++) + { + std::map<std::string,t_enum*>::iterator newEnumMapIt; + newEnumMapIt = newEnumMap.find((*oldEnumIt)->get_name()); + + if(newEnumMapIt == newEnumMap.end()) + { + thrift_audit_warning(1, "Enum %s not found in new thrift file\n",(*oldEnumIt)->get_name().c_str()); + } + else + { + compare_enum_values(newEnumMapIt->second, *oldEnumIt); + } + } +} + +//This function returns 'true' if the two arguements are of same types. +//Returns false if they are of different type +bool compare_type(t_type* newType, t_type* oldType) +{ + //Comparing names of two types will work when the newType and oldType are basic types or structs or enums. + //However, when they are containers, get_name() returns empty for which we have to compare the type of + //their elements as well. + if((newType->get_name()).empty() && (oldType->get_name()).empty()) + { + + if(newType->is_list() && oldType->is_list()) + { + t_type* newElementType = ((t_list*)newType)->get_elem_type(); + t_type* oldElementType = ((t_list*)oldType)->get_elem_type(); + return compare_type(newElementType, oldElementType); + } + else if(newType->is_map() && oldType->is_map()) + { + t_type* newKeyType = ((t_map*)newType)->get_key_type(); + t_type* oldKeyType = ((t_map*)oldType)->get_key_type(); + + t_type* newValType = ((t_map*)newType)->get_val_type(); + t_type* oldValType = ((t_map*)oldType)->get_val_type(); + + return (compare_type(newKeyType, oldKeyType) && compare_type(newValType, oldValType)); + } + else if(newType->is_set() && oldType->is_set()) + { + t_type* newElementType = ((t_set*)newType)->get_elem_type(); + t_type* oldElementType = ((t_set*)oldType)->get_elem_type(); + return compare_type(newElementType, oldElementType); + } + else + { + return false; + } + } + else if(newType->get_name() == oldType->get_name()) + { + return true; + } + else + { + return false; + } +} + +bool compare_pair(std::pair<t_const_value*, t_const_value*> newMapPair, std::pair<t_const_value*, t_const_value*> oldMapPair) +{ + return compare_defaults(newMapPair.first, oldMapPair.first) && compare_defaults(newMapPair.second, oldMapPair.second); +} + +// This function returns 'true' if the default values are same. Returns false if they are different. +bool compare_defaults(t_const_value* newStructDefault, t_const_value* oldStructDefault) +{ + if(newStructDefault == nullptr && oldStructDefault == nullptr) return true; + else if(newStructDefault == nullptr && oldStructDefault != nullptr) return false; + else if (newStructDefault != nullptr && oldStructDefault == nullptr) return false; + + if(newStructDefault->get_type() != oldStructDefault->get_type()) + { + return false; + } + + switch(newStructDefault->get_type()) + { + case t_const_value::CV_INTEGER: + return (newStructDefault->get_integer() == oldStructDefault->get_integer()); + case t_const_value::CV_DOUBLE: + return (newStructDefault->get_double() == oldStructDefault->get_double()); + case t_const_value::CV_STRING: + return (newStructDefault->get_string() == oldStructDefault->get_string()); + case t_const_value::CV_LIST: + { + const std::vector<t_const_value*>& oldDefaultList = oldStructDefault->get_list(); + const std::vector<t_const_value*>& newDefaultList = newStructDefault->get_list(); + bool defaultValuesCompare = (oldDefaultList.size() == newDefaultList.size()); + + return defaultValuesCompare && std::equal(newDefaultList.begin(), newDefaultList.end(), oldDefaultList.begin(), compare_defaults); + } + case t_const_value::CV_MAP: + { + const std::map<t_const_value*, t_const_value*, t_const_value::value_compare> newMap = newStructDefault->get_map(); + const std::map<t_const_value*, t_const_value*, t_const_value::value_compare> oldMap = oldStructDefault->get_map(); + + bool defaultValuesCompare = (oldMap.size() == newMap.size()); + + return defaultValuesCompare && std::equal(newMap.begin(), newMap.end(), oldMap.begin(), compare_pair); + } + case t_const_value::CV_IDENTIFIER: + return (newStructDefault->get_identifier() == oldStructDefault->get_identifier()); + default: + return false; + } + +} + +void compare_struct_field(t_field* newField, t_field* oldField, std::string oldStructName) +{ + t_type* newFieldType = newField->get_type(); + t_type* oldFieldType = oldField->get_type(); + if(!compare_type(newFieldType, oldFieldType)) + { + thrift_audit_failure("Struct Field Type Changed for Id = %d in %s \n", newField->get_key(), oldStructName.c_str()); + } + + // A Struct member can be optional if it is mentioned explicitly, or if it is assigned with default values. + bool newStructFieldOptional = (newField->get_req() != t_field::T_REQUIRED); + bool oldStructFieldOptional = (oldField->get_req() != t_field::T_REQUIRED); + + if(newStructFieldOptional != oldStructFieldOptional) + { + thrift_audit_failure("Struct Field Requiredness Changed for Id = %d in %s \n", newField->get_key(), oldStructName.c_str()); + } + if(newStructFieldOptional || oldStructFieldOptional) + { + if(!compare_defaults(newField->get_value(), oldField->get_value())) + { + thrift_audit_warning(1, "Default value changed for Id = %d in %s \n", newField->get_key(), oldStructName.c_str()); + } + } + + std::string fieldName = newField->get_name(); + if(fieldName != oldField->get_name()) + { + thrift_audit_warning(1, "Struct field name changed for Id = %d in %s\n", newField->get_key(), oldStructName.c_str()); + } + +} + +void compare_single_struct(t_struct* newStruct, t_struct* oldStruct, const std::string& oldStructName = std::string()) +{ + std::string structName = oldStructName.empty() ? oldStruct->get_name() : oldStructName; + const std::vector<t_field*>& oldStructMembersInIdOrder = oldStruct->get_sorted_members(); + const std::vector<t_field*>& newStructMembersInIdOrder = newStruct->get_sorted_members(); + auto oldStructMemberIt = oldStructMembersInIdOrder.begin(); + auto newStructMemberIt = newStructMembersInIdOrder.begin(); + + // Since we have the struct members in their ID order, comparing their IDs can be done by traversing the two member + // lists together. + while(!(oldStructMemberIt == oldStructMembersInIdOrder.end() && newStructMemberIt == newStructMembersInIdOrder.end())) + { + if(newStructMemberIt == newStructMembersInIdOrder.end() && oldStructMemberIt != oldStructMembersInIdOrder.end()) + { + // A field ID has been removed from the end. + thrift_audit_failure("Struct Field removed for Id = %d in %s \n", (*oldStructMemberIt)->get_key(), structName.c_str()); + oldStructMemberIt++; + } + else if(newStructMemberIt != newStructMembersInIdOrder.end() && oldStructMemberIt == oldStructMembersInIdOrder.end()) + { + //New field ID has been added to the end. + if((*newStructMemberIt)->get_req() == t_field::T_REQUIRED) + { + thrift_audit_failure("Required Struct Field Added for Id = %d in %s \n", (*newStructMemberIt)->get_key(), structName.c_str()); + } + newStructMemberIt++; + } + else if((*newStructMemberIt)->get_key() == (*oldStructMemberIt)->get_key()) + { + //Field ID found in both structs. Compare field types, default values. + compare_struct_field(*newStructMemberIt, *oldStructMemberIt, structName); + + newStructMemberIt++; + oldStructMemberIt++; + } + else if((*newStructMemberIt)->get_key() < (*oldStructMemberIt)->get_key()) + { + //New Field Id is inserted in between + //Adding fields to struct is fine, but adding them in the middle is suspicious. Error!! + thrift_audit_failure("Struct field is added in the middle with Id = %d in %s\n", (*newStructMemberIt)->get_key(), structName.c_str()); + newStructMemberIt++; + } + else if((*newStructMemberIt)->get_key() > (*oldStructMemberIt)->get_key()) + { + //A field is deleted in newStruct. + thrift_audit_failure("Struct Field removed for Id = %d in %s \n", (*oldStructMemberIt)->get_key(), structName.c_str()); + oldStructMemberIt++; + } + + } +} + +void compare_structs(const std::vector<t_struct*>& newStructList, const std::vector<t_struct*>& oldStructList) +{ + std::map<std::string,t_struct*> newStructMap; + std::vector<t_struct*>::const_iterator newStructListIt; + for(newStructListIt = newStructList.begin(); newStructListIt != newStructList.end(); newStructListIt++) + { + newStructMap[(*newStructListIt)->get_name()] = *newStructListIt; + } + + std::vector<t_struct*>::const_iterator oldStructListIt; + for(oldStructListIt = oldStructList.begin(); oldStructListIt != oldStructList.end(); oldStructListIt++) + { + std::map<std::string, t_struct*>::iterator newStructMapIt; + newStructMapIt = newStructMap.find((*oldStructListIt)->get_name()); + if(newStructMapIt == newStructMap.end()) + { + thrift_audit_failure("Struct %s not found in new thrift file\n", (*oldStructListIt)->get_name().c_str()); + } + else + { + compare_single_struct(newStructMapIt->second, *oldStructListIt); + } + } + +} + +void compare_single_function(t_function* newFunction, t_function* oldFunction) +{ + t_type* newFunctionReturnType = newFunction->get_returntype(); + + if(newFunction->is_oneway() != oldFunction->is_oneway()) + { + thrift_audit_failure("Oneway attribute changed for function %s\n",oldFunction->get_name().c_str()); + } + if(!compare_type(newFunctionReturnType, oldFunction->get_returntype())) + { + thrift_audit_failure("Return type changed for function %s\n",oldFunction->get_name().c_str()); + } + + //Compare function arguments. + compare_single_struct(newFunction->get_arglist(), oldFunction->get_arglist()); + std::string exceptionName = oldFunction->get_name(); + exceptionName += "_exception"; + compare_single_struct(newFunction->get_xceptions(), oldFunction->get_xceptions(), exceptionName); +} + +void compare_functions(const std::vector<t_function*>& newFunctionList, const std::vector<t_function*>& oldFunctionList) +{ + std::map<std::string, t_function*> newFunctionMap; + std::map<std::string, t_function*>::iterator newFunctionMapIt; + for(auto newFunctionIt : newFunctionList) + { + newFunctionMap[newFunctionIt->get_name()] = newFunctionIt; + } + + for(auto oldFunctionIt : oldFunctionList) + { + newFunctionMapIt = newFunctionMap.find(oldFunctionIt->get_name()); + if(newFunctionMapIt == newFunctionMap.end()) + { + thrift_audit_failure("New Thrift File has missing function %s\n",oldFunctionIt->get_name().c_str()); + continue; + } + else + { + //Function is found in both thrift files. Compare return type and argument list + compare_single_function(newFunctionMapIt->second, oldFunctionIt); + } + } + +} + +void compare_services(const std::vector<t_service*>& newServices, const std::vector<t_service*>& oldServices) +{ + std::vector<t_service*>::const_iterator oldServiceIt; + + std::map<std::string, t_service*> newServiceMap; + for(auto newService : newServices) + { + newServiceMap[newService->get_name()] = newService; + } + + + for(oldServiceIt = oldServices.begin(); oldServiceIt != oldServices.end(); oldServiceIt++) + { + const std::string oldServiceName = (*oldServiceIt)->get_name(); + auto newServiceMapIt = newServiceMap.find(oldServiceName); + + if(newServiceMapIt == newServiceMap.end()) + { + thrift_audit_failure("New Thrift file is missing a service %s\n", oldServiceName.c_str()); + } + else + { + t_service* oldServiceExtends = (*oldServiceIt)->get_extends(); + t_service* newServiceExtends = (newServiceMapIt->second)->get_extends(); + + if(oldServiceExtends == nullptr) + { + // It is fine to add extends. So if service in older thrift did not have any extends, we are fine. + // DO Nothing + } + else if(oldServiceExtends != nullptr && newServiceExtends == nullptr) + { + thrift_audit_failure("Change in Service inheritance for %s\n", oldServiceName.c_str()); + } + else + { + std::string oldExtendsName = oldServiceExtends->get_name(); + std::string newExtendsName = newServiceExtends->get_name(); + + if( newExtendsName != oldExtendsName) + { + thrift_audit_failure("Change in Service inheritance for %s\n", oldServiceName.c_str()); + } + } + + compare_functions((newServiceMapIt->second)->get_functions(), (*oldServiceIt)->get_functions()); + } + + } + +} + +void compare_consts(const std::vector<t_const*>& newConst, const std::vector<t_const*>& oldConst) +{ + std::vector<t_const*>::const_iterator newConstIt; + std::vector<t_const*>::const_iterator oldConstIt; + + std::map<std::string, t_const*> newConstMap; + + for(newConstIt = newConst.begin(); newConstIt != newConst.end(); newConstIt++) + { + newConstMap[(*newConstIt)->get_name()] = *newConstIt; + } + + std::map<std::string, t_const*>::const_iterator newConstMapIt; + for(oldConstIt = oldConst.begin(); oldConstIt != oldConst.end(); oldConstIt++) + { + newConstMapIt = newConstMap.find((*oldConstIt)->get_name()); + if(newConstMapIt == newConstMap.end()) + { + thrift_audit_warning(1, "Constants Missing %s \n", ((*oldConstIt)->get_name()).c_str()); + } + else if(!compare_type((newConstMapIt->second)->get_type(), (*oldConstIt)->get_type())) + { + thrift_audit_warning(1, "Constant %s is of different type \n", ((*oldConstIt)->get_name()).c_str()); + } + else if(!compare_defaults((newConstMapIt->second)->get_value(), (*oldConstIt)->get_value())) + { + thrift_audit_warning(1, "Constant %s has different value\n", ((*oldConstIt)->get_name()).c_str()); + } + } +} diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/audit/t_audit.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/audit/t_audit.h new file mode 100644 index 000000000..be79e3124 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/audit/t_audit.h @@ -0,0 +1,14 @@ +#ifndef T_AUDIT_H +#define T_AUDIT_H + +void compare_namespace(t_program* newProgram, t_program* oldProgram); +void compare_enums(const std::vector<t_enum*>& newEnumList, + const std::vector<t_enum*>& oldEnumList); +bool compare_defaults(t_const_value* newStructDefault, t_const_value* oldStructDefault); +void compare_structs(const std::vector<t_struct*>& newStructList, + const std::vector<t_struct*>& oldStructList); +void compare_services(const std::vector<t_service*>& newServices, + const std::vector<t_service*>& oldServices); +void compare_consts(const std::vector<t_const*>& newConst, const std::vector<t_const*>& oldConst); + +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/common.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/common.cc new file mode 100644 index 000000000..fb1832daf --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/common.cc @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "thrift/common.h" +#include "thrift/parse/t_base_type.h" + +t_type* g_type_void; +t_type* g_type_string; +t_type* g_type_binary; +t_type* g_type_slist; +t_type* g_type_bool; +t_type* g_type_i8; +t_type* g_type_i16; +t_type* g_type_i32; +t_type* g_type_i64; +t_type* g_type_double; + +void initGlobals() { + g_type_void = new t_base_type("void", t_base_type::TYPE_VOID); + g_type_string = new t_base_type("string", t_base_type::TYPE_STRING); + g_type_binary = new t_base_type("string", t_base_type::TYPE_STRING); + ((t_base_type*)g_type_binary)->set_binary(true); + g_type_slist = new t_base_type("string", t_base_type::TYPE_STRING); + ((t_base_type*)g_type_slist)->set_string_list(true); + g_type_bool = new t_base_type("bool", t_base_type::TYPE_BOOL); + g_type_i8 = new t_base_type("i8", t_base_type::TYPE_I8); + g_type_i16 = new t_base_type("i16", t_base_type::TYPE_I16); + g_type_i32 = new t_base_type("i32", t_base_type::TYPE_I32); + g_type_i64 = new t_base_type("i64", t_base_type::TYPE_I64); + g_type_double = new t_base_type("double", t_base_type::TYPE_DOUBLE); +} + +void clearGlobals() { + delete g_type_void; + delete g_type_string; + delete g_type_bool; + delete g_type_i8; + delete g_type_i16; + delete g_type_i32; + delete g_type_i64; + delete g_type_double; +} + +/** + * The location of the last parsed doctext comment. + */ +int g_doctext_lineno; +int g_program_doctext_lineno = 0; +PROGDOCTEXT_STATUS g_program_doctext_status = INVALID; diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/common.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/common.h new file mode 100644 index 000000000..694884636 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/common.h @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef T_COMMON_H +#define T_COMMON_H + +#include "thrift/parse/t_type.h" + +/** + * Global types for the parser to be able to reference + */ + +extern t_type* g_type_void; +extern t_type* g_type_string; +extern t_type* g_type_binary; +extern t_type* g_type_slist; +extern t_type* g_type_bool; +extern t_type* g_type_i8; +extern t_type* g_type_i16; +extern t_type* g_type_i32; +extern t_type* g_type_i64; +extern t_type* g_type_double; + +void initGlobals(); +void clearGlobals(); + +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_as3_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_as3_generator.cc new file mode 100644 index 000000000..dd4d166ca --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_as3_generator.cc @@ -0,0 +1,2592 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <sstream> +#include <string> +#include <fstream> +#include <iostream> +#include <vector> +#include <cctype> + +#include <sys/stat.h> +#include <stdexcept> + +#include "thrift/platform.h" +#include "thrift/generate/t_oop_generator.h" + +using std::map; +using std::ostream; +using std::ostringstream; +using std::string; +using std::stringstream; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes + +/** + * AS3 code generator. + * + */ +class t_as3_generator : public t_oop_generator { +public: + t_as3_generator(t_program* program, + const std::map<std::string, std::string>& parsed_options, + const std::string& option_string) + : t_oop_generator(program) { + (void)option_string; + std::map<std::string, std::string>::const_iterator iter; + + bindable_ = false; + for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + if( iter->first.compare("bindable") == 0) { + bindable_ = true; + } else { + throw "unknown option as3:" + iter->first; + } + } + + out_dir_base_ = "gen-as3"; + } + + /** + * Init and close methods + */ + + void init_generator() override; + void close_generator() override; + + void generate_consts(std::vector<t_const*> consts) override; + + /** + * Program-level generation functions + */ + + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_struct(t_struct* tstruct) override; + void generate_xception(t_struct* txception) override; + void generate_service(t_service* tservice) override; + + void print_const_value(std::ostream& out, + std::string name, + t_type* type, + t_const_value* value, + bool in_static, + bool defval = false); + std::string render_const_value(ostream& out, + std::string name, + t_type* type, + t_const_value* value); + + /** + * Service-level generation functions + */ + + void generate_as3_struct(t_struct* tstruct, bool is_exception); + + void generate_as3_struct_definition(std::ostream& out, + t_struct* tstruct, + bool is_xception = false, + bool in_class = false, + bool is_result = false); + // removed -- equality,compare_to + void generate_as3_struct_reader(std::ostream& out, t_struct* tstruct); + void generate_as3_validator(std::ostream& out, t_struct* tstruct); + void generate_as3_struct_result_writer(std::ostream& out, t_struct* tstruct); + void generate_as3_struct_writer(std::ostream& out, t_struct* tstruct); + void generate_as3_struct_tostring(std::ostream& out, t_struct* tstruct, bool bindable); + void generate_as3_meta_data_map(std::ostream& out, t_struct* tstruct); + void generate_field_value_meta_data(std::ostream& out, t_type* type); + std::string get_as3_type_string(t_type* type); + void generate_reflection_setters(std::ostringstream& out, + t_type* type, + std::string field_name, + std::string cap_name); + void generate_reflection_getters(std::ostringstream& out, + t_type* type, + std::string field_name, + std::string cap_name); + void generate_generic_field_getters_setters(std::ostream& out, t_struct* tstruct); + void generate_generic_isset_method(std::ostream& out, t_struct* tstruct); + void generate_as3_bean_boilerplate(std::ostream& out, t_struct* tstruct, bool bindable); + + void generate_function_helpers(t_function* tfunction); + std::string get_cap_name(std::string name); + std::string generate_isset_check(t_field* field); + std::string generate_isset_check(std::string field); + void generate_isset_set(ostream& out, t_field* field); + // removed std::string isset_field_id(t_field* field); + + void generate_service_interface(t_service* tservice); + void generate_service_helpers(t_service* tservice); + void generate_service_client(t_service* tservice); + void generate_service_server(t_service* tservice); + void generate_process_function(t_service* tservice, t_function* tfunction); + + /** + * Serialization constructs + */ + + void generate_deserialize_field(std::ostream& out, t_field* tfield, std::string prefix = ""); + + void generate_deserialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); + + void generate_deserialize_container(std::ostream& out, t_type* ttype, std::string prefix = ""); + + void generate_deserialize_set_element(std::ostream& out, t_set* tset, std::string prefix = ""); + + void generate_deserialize_map_element(std::ostream& out, t_map* tmap, std::string prefix = ""); + + void generate_deserialize_list_element(std::ostream& out, + t_list* tlist, + std::string prefix = ""); + + void generate_serialize_field(std::ostream& out, t_field* tfield, std::string prefix = ""); + + void generate_serialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); + + void generate_serialize_container(std::ostream& out, t_type* ttype, std::string prefix = ""); + + void generate_serialize_map_element(std::ostream& out, + t_map* tmap, + std::string iter, + std::string map); + + void generate_serialize_set_element(std::ostream& out, t_set* tmap, std::string iter); + + void generate_serialize_list_element(std::ostream& out, t_list* tlist, std::string iter); + + void generate_as3_doc(std::ostream& out, t_doc* tdoc); + + void generate_as3_doc(std::ostream& out, t_function* tdoc); + + /** + * Helper rendering functions + */ + + std::string as3_package(); + std::string as3_type_imports(); + std::string as3_thrift_imports(); + std::string as3_thrift_gen_imports(t_struct* tstruct, string& imports); + std::string as3_thrift_gen_imports(t_service* tservice); + std::string type_name(t_type* ttype, bool in_container = false, bool in_init = false); + std::string base_type_name(t_base_type* tbase, bool in_container = false); + std::string declare_field(t_field* tfield, bool init = false); + std::string function_signature(t_function* tfunction, std::string prefix = ""); + std::string argument_list(t_struct* tstruct); + std::string type_to_enum(t_type* ttype); + std::string get_enum_class_name(t_type* type) override; + + bool type_can_be_null(t_type* ttype) { + ttype = get_true_type(ttype); + + return ttype->is_container() || ttype->is_struct() || ttype->is_xception() + || ttype->is_string(); + } + + std::string constant_name(std::string name); + +private: + /** + * File streams + */ + + std::string package_name_; + ofstream_with_content_based_conditional_update f_service_; + std::string package_dir_; + + bool bindable_; +}; + +/** + * Prepares for file generation by opening up the necessary file output + * streams. + * + * @param tprogram The program to generate + */ +void t_as3_generator::init_generator() { + // Make output directory + MKDIR(get_out_dir().c_str()); + package_name_ = program_->get_namespace("as3"); + + string dir = package_name_; + string subdir = get_out_dir(); + string::size_type loc; + while ((loc = dir.find(".")) != string::npos) { + subdir = subdir + "/" + dir.substr(0, loc); + MKDIR(subdir.c_str()); + dir = dir.substr(loc + 1); + } + if (dir.size() > 0) { + subdir = subdir + "/" + dir; + MKDIR(subdir.c_str()); + } + + package_dir_ = subdir; +} + +/** + * Packages the generated file + * + * @return String of the package, i.e. "package org.apache.thriftdemo;" + */ +string t_as3_generator::as3_package() { + if (!package_name_.empty()) { + return string("package ") + package_name_ + " "; + } + return "package "; +} + +/** + * Prints standard as3 imports + * + * @return List of imports for As3 types that are used in here + */ +string t_as3_generator::as3_type_imports() { + return string() + "import org.apache.thrift.Set;\n" + "import flash.utils.ByteArray;\n" + + "import flash.utils.Dictionary;\n\n"; +} + +/** + * Prints standard as3 imports + * + * @return List of imports necessary for thrift + */ +string t_as3_generator::as3_thrift_imports() { + return string() + "import org.apache.thrift.*;\n" + "import org.apache.thrift.meta_data.*;\n" + + "import org.apache.thrift.protocol.*;\n\n"; +} + +/** + * Prints imports needed for a given type + * + * @return List of imports necessary for a given t_struct + */ +string t_as3_generator::as3_thrift_gen_imports(t_struct* tstruct, string& imports) { + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + // For each type check if it is from a differnet namespace + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_program* program = (*m_iter)->get_type()->get_program(); + if (program != NULL && program != program_) { + string package = program->get_namespace("as3"); + if (!package.empty()) { + if (imports.find(package + "." + (*m_iter)->get_type()->get_name()) == string::npos) { + imports.append("import " + package + "." + (*m_iter)->get_type()->get_name() + ";\n"); + } + } + } + } + return imports; +} + +/** + * Prints imports needed for a given type + * + * @return List of imports necessary for a given t_service + */ +string t_as3_generator::as3_thrift_gen_imports(t_service* tservice) { + string imports; + const vector<t_function*>& functions = tservice->get_functions(); + vector<t_function*>::const_iterator f_iter; + + // For each type check if it is from a differnet namespace + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_program* program = (*f_iter)->get_returntype()->get_program(); + if (program != NULL && program != program_) { + string package = program->get_namespace("as3"); + if (!package.empty()) { + if (imports.find(package + "." + (*f_iter)->get_returntype()->get_name()) == string::npos) { + imports.append("import " + package + "." + (*f_iter)->get_returntype()->get_name() + + ";\n"); + } + } + } + + as3_thrift_gen_imports((*f_iter)->get_arglist(), imports); + as3_thrift_gen_imports((*f_iter)->get_xceptions(), imports); + } + + return imports; +} + +/** + * Nothing in As3 + */ +void t_as3_generator::close_generator() { +} + +/** + * Generates a typedef. This is not done in As3, since it does + * not support arbitrary name replacements, and it'd be a wacky waste + * of overhead to make wrapper classes. + * + * @param ttypedef The type definition + */ +void t_as3_generator::generate_typedef(t_typedef* ttypedef) { + (void)ttypedef; +} + +/** + * Enums are a class with a set of static constants. + * + * @param tenum The enumeration + */ +void t_as3_generator::generate_enum(t_enum* tenum) { + // Make output file + string f_enum_name = package_dir_ + "/" + (tenum->get_name()) + ".as"; + ofstream_with_content_based_conditional_update f_enum; + f_enum.open(f_enum_name); + + // Comment and package it + f_enum << autogen_comment() << as3_package() << endl; + + scope_up(f_enum); + // Add as3 imports + f_enum << string() + "import org.apache.thrift.Set;" << endl << "import flash.utils.Dictionary;" + << endl; + + indent(f_enum) << "public class " << tenum->get_name() << " "; + scope_up(f_enum); + + vector<t_enum_value*> constants = tenum->get_constants(); + vector<t_enum_value*>::iterator c_iter; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + int value = (*c_iter)->get_value(); + indent(f_enum) << "public static const " << (*c_iter)->get_name() << ":int = " << value << ";" + << endl; + } + + // Create a static Set with all valid values for this enum + f_enum << endl; + + indent(f_enum) << "public static const VALID_VALUES:Set = new Set("; + indent_up(); + bool firstValue = true; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + // populate set + f_enum << (firstValue ? "" : ", ") << (*c_iter)->get_name(); + firstValue = false; + } + indent_down(); + f_enum << ");" << endl; + + indent(f_enum) << "public static const VALUES_TO_NAMES:Dictionary = new Dictionary();" << endl; + + scope_up(f_enum); + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + indent(f_enum) << "VALUES_TO_NAMES[" << (*c_iter)->get_name() << "] = \"" + << (*c_iter)->get_name() << "\";" << endl; + } + f_enum << endl; + + scope_down(f_enum); + + scope_down(f_enum); // end class + + scope_down(f_enum); // end package + + f_enum.close(); +} + +/** + * Generates a class that holds all the constants. + */ +void t_as3_generator::generate_consts(std::vector<t_const*> consts) { + if (consts.empty()) { + return; + } + + string f_consts_name = package_dir_ + "/" + program_name_ + "Constants.as"; + ofstream_with_content_based_conditional_update f_consts; + f_consts.open(f_consts_name); + + // Print header + f_consts << autogen_comment() << as3_package(); + + scope_up(f_consts); + f_consts << endl; + + f_consts << as3_type_imports(); + + indent(f_consts) << "public class " << program_name_ << "Constants {" << endl << endl; + indent_up(); + vector<t_const*>::iterator c_iter; + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + print_const_value(f_consts, + (*c_iter)->get_name(), + (*c_iter)->get_type(), + (*c_iter)->get_value(), + false); + } + indent_down(); + indent(f_consts) << "}" << endl; + scope_down(f_consts); + f_consts.close(); +} + +void t_as3_generator::print_const_value(std::ostream& out, + string name, + t_type* type, + t_const_value* value, + bool in_static, + bool defval) { + type = get_true_type(type); + + indent(out); + if (!defval) { + out << (in_static ? "var " : "public static const "); + } + if (type->is_base_type()) { + string v2 = render_const_value(out, name, type, value); + out << name; + if (!defval) { + out << ":" << type_name(type); + } + out << " = " << v2 << ";" << endl << endl; + } else if (type->is_enum()) { + out << name; + if (!defval) { + out << ":" << type_name(type); + } + out << " = " << value->get_integer() << ";" << endl << endl; + } else if (type->is_struct() || type->is_xception()) { + const vector<t_field*>& fields = ((t_struct*)type)->get_members(); + vector<t_field*>::const_iterator f_iter; + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + out << name << ":" << type_name(type) << " = new " << type_name(type, false, true) << "();" + << endl; + if (!in_static) { + indent(out) << "{" << endl; + indent_up(); + indent(out) << "new function():void {" << endl; + indent_up(); + } + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + t_type* field_type = NULL; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field_type = (*f_iter)->get_type(); + } + } + if (field_type == NULL) { + throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string(); + } + string val = render_const_value(out, name, field_type, v_iter->second); + indent(out) << name << "."; + out << v_iter->first->get_string() << " = " << val << ";" << endl; + } + if (!in_static) { + indent_down(); + indent(out) << "}();" << endl; + indent_down(); + indent(out) << "}" << endl; + } + out << endl; + } else if (type->is_map()) { + out << name; + if (!defval) { + out << ":" << type_name(type); + } + out << " = new " << type_name(type, false, true) << "();" << endl; + if (!in_static) { + indent(out) << "{" << endl; + indent_up(); + indent(out) << "new function():void {" << endl; + indent_up(); + } + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string key = render_const_value(out, name, ktype, v_iter->first); + string val = render_const_value(out, name, vtype, v_iter->second); + indent(out) << name << "[" << key << "] = " << val << ";" << endl; + } + if (!in_static) { + indent_down(); + indent(out) << "}();" << endl; + indent_down(); + indent(out) << "}" << endl; + } + out << endl; + } else if (type->is_list() || type->is_set()) { + out << name; + if (!defval) { + out << ":" << type_name(type); + } + out << " = new " << type_name(type, false, true) << "();" << endl; + if (!in_static) { + indent(out) << "{" << endl; + indent_up(); + indent(out) << "new function():void {" << endl; + indent_up(); + } + t_type* etype; + if (type->is_list()) { + etype = ((t_list*)type)->get_elem_type(); + } else { + etype = ((t_set*)type)->get_elem_type(); + } + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string val = render_const_value(out, name, etype, *v_iter); + indent(out) << name << "." << (type->is_list() ? "push" : "add") << "(" << val << ");" + << endl; + } + if (!in_static) { + indent_down(); + indent(out) << "}();" << endl; + indent_down(); + indent(out) << "}" << endl; + } + out << endl; + } else { + throw "compiler error: no const of type " + type->get_name(); + } +} + +string t_as3_generator::render_const_value(ostream& out, + string name, + t_type* type, + t_const_value* value) { + (void)name; + type = get_true_type(type); + std::ostringstream render; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + render << '"' << get_escaped_string(value) << '"'; + break; + case t_base_type::TYPE_BOOL: + render << ((value->get_integer() > 0) ? "true" : "false"); + break; + case t_base_type::TYPE_I8: + render << "(byte)" << value->get_integer(); + break; + case t_base_type::TYPE_I16: + render << "(short)" << value->get_integer(); + break; + case t_base_type::TYPE_I32: + render << value->get_integer(); + break; + case t_base_type::TYPE_I64: + render << value->get_integer() << "L"; + break; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) { + render << "(double)" << value->get_integer(); + } else { + render << value->get_double(); + } + break; + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + render << value->get_integer(); + } else { + string t = tmp("tmp"); + print_const_value(out, t, type, value, true); + render << t; + } + + return render.str(); +} + +/** + * Generates a struct definition for a thrift data type. This is a class + * with data members, read(), write(), and an inner Isset class. + * + * @param tstruct The struct definition + */ +void t_as3_generator::generate_struct(t_struct* tstruct) { + generate_as3_struct(tstruct, false); +} + +/** + * Exceptions are structs, but they inherit from Exception + * + * @param tstruct The struct definition + */ +void t_as3_generator::generate_xception(t_struct* txception) { + generate_as3_struct(txception, true); +} + +/** + * As3 struct definition. + * + * @param tstruct The struct definition + */ +void t_as3_generator::generate_as3_struct(t_struct* tstruct, bool is_exception) { + // Make output file + string f_struct_name = package_dir_ + "/" + (tstruct->get_name()) + ".as"; + ofstream_with_content_based_conditional_update f_struct; + f_struct.open(f_struct_name.c_str()); + + f_struct << autogen_comment() << as3_package(); + + scope_up(f_struct); + f_struct << endl; + + string imports; + + f_struct << as3_type_imports() << as3_thrift_imports() << as3_thrift_gen_imports(tstruct, imports) + << endl; + + if (bindable_ && !is_exception) { + f_struct << "import flash.events.Event;" << endl << "import flash.events.EventDispatcher;" + << endl << "import mx.events.PropertyChangeEvent;" << endl; + } + + generate_as3_struct_definition(f_struct, tstruct, is_exception); + + scope_down(f_struct); // end of package + f_struct.close(); +} + +/** + * As3 struct definition. This has various parameters, as it could be + * generated standalone or inside another class as a helper. If it + * is a helper than it is a static class. + * + * @param tstruct The struct definition + * @param is_exception Is this an exception? + * @param in_class If inside a class, needs to be static class + * @param is_result If this is a result it needs a different writer + */ +void t_as3_generator::generate_as3_struct_definition(ostream& out, + t_struct* tstruct, + bool is_exception, + bool in_class, + bool is_result) { + generate_as3_doc(out, tstruct); + + bool is_final = (tstruct->annotations_.find("final") != tstruct->annotations_.end()); + bool bindable = !is_exception && !in_class && bindable_; + + indent(out) << (in_class ? "" : "public ") << (is_final ? "final " : "") << "class " + << tstruct->get_name() << " "; + + if (is_exception) { + out << "extends Error "; + } else if (bindable) { + out << "extends EventDispatcher "; + } + out << "implements TBase "; + + scope_up(out); + + indent(out) << "private static const STRUCT_DESC:TStruct = new TStruct(\"" << tstruct->get_name() + << "\");" << endl; + + // Members are public for -as3, private for -as3bean + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + indent(out) << "private static const " << constant_name((*m_iter)->get_name()) + << "_FIELD_DESC:TField = new TField(\"" << (*m_iter)->get_name() << "\", " + << type_to_enum((*m_iter)->get_type()) << ", " << (*m_iter)->get_key() << ");" + << endl; + } + + out << endl; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + generate_as3_doc(out, *m_iter); + indent(out) << "private var _" << (*m_iter)->get_name() + ":" + type_name((*m_iter)->get_type()) + << ";" << endl; + + indent(out) << "public static const " << upcase_string((*m_iter)->get_name()) + << ":int = " << (*m_iter)->get_key() << ";" << endl; + } + + out << endl; + + // Inner Isset class + if (members.size() > 0) { + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if (!type_can_be_null((*m_iter)->get_type())) { + indent(out) << "private var __isset_" << (*m_iter)->get_name() << ":Boolean = false;" + << endl; + } + } + } + + out << endl; + + generate_as3_meta_data_map(out, tstruct); + + // Static initializer to populate global class to struct metadata map + indent(out) << "{" << endl; + indent_up(); + indent(out) << "FieldMetaData.addStructMetaDataMap(" << type_name(tstruct) << ", metaDataMap);" + << endl; + indent_down(); + indent(out) << "}" << endl << endl; + + // Default constructor + indent(out) << "public function " << tstruct->get_name() << "() {" << endl; + indent_up(); + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if ((*m_iter)->get_value() != NULL) { + indent(out) << "this._" << (*m_iter)->get_name() << " = " + << (*m_iter)->get_value()->get_integer() << ";" << endl; + } + } + indent_down(); + indent(out) << "}" << endl << endl; + + generate_as3_bean_boilerplate(out, tstruct, bindable); + generate_generic_field_getters_setters(out, tstruct); + generate_generic_isset_method(out, tstruct); + + generate_as3_struct_reader(out, tstruct); + if (is_result) { + generate_as3_struct_result_writer(out, tstruct); + } else { + generate_as3_struct_writer(out, tstruct); + } + generate_as3_struct_tostring(out, tstruct, bindable); + generate_as3_validator(out, tstruct); + scope_down(out); + out << endl; +} + +/** + * Generates a function to read all the fields of the struct. + * + * @param tstruct The struct definition + */ +void t_as3_generator::generate_as3_struct_reader(ostream& out, t_struct* tstruct) { + out << indent() << "public function read(iprot:TProtocol):void {" << endl; + indent_up(); + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + // Declare stack tmp variables and read struct header + out << indent() << "var field:TField;" << endl << indent() << "iprot.readStructBegin();" << endl; + + // Loop over reading in fields + indent(out) << "while (true)" << endl; + scope_up(out); + + // Read beginning field marker + indent(out) << "field = iprot.readFieldBegin();" << endl; + + // Check for field STOP marker and break + indent(out) << "if (field.type == TType.STOP) { " << endl; + indent_up(); + indent(out) << "break;" << endl; + indent_down(); + indent(out) << "}" << endl; + + // Switch statement on the field we are reading + indent(out) << "switch (field.id)" << endl; + + scope_up(out); + + // Generate deserialization code for known cases + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + indent(out) << "case " << upcase_string((*f_iter)->get_name()) << ":" << endl; + indent_up(); + indent(out) << "if (field.type == " << type_to_enum((*f_iter)->get_type()) << ") {" << endl; + indent_up(); + + generate_deserialize_field(out, *f_iter, "this."); + generate_isset_set(out, *f_iter); + indent_down(); + out << indent() << "} else { " << endl << indent() << " TProtocolUtil.skip(iprot, field.type);" + << endl << indent() << "}" << endl << indent() << "break;" << endl; + indent_down(); + } + + // In the default case we skip the field + out << indent() << "default:" << endl << indent() << " TProtocolUtil.skip(iprot, field.type);" + << endl << indent() << " break;" << endl; + + scope_down(out); + + // Read field end marker + indent(out) << "iprot.readFieldEnd();" << endl; + + scope_down(out); + + out << indent() << "iprot.readStructEnd();" << endl << endl; + + // in non-beans style, check for required fields of primitive type + // (which can be checked here but not in the general validate method) + out << endl << indent() << "// check for required fields of primitive type, which can't be " + "checked in the validate method" << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_REQUIRED && !type_can_be_null((*f_iter)->get_type())) { + out << indent() << "if (!__isset_" << (*f_iter)->get_name() << ") {" << endl << indent() + << " throw new TProtocolError(TProtocolError.UNKNOWN, \"Required field '" + << (*f_iter)->get_name() + << "' was not found in serialized data! Struct: \" + toString());" << endl << indent() + << "}" << endl; + } + } + + // performs various checks (e.g. check that all required fields are set) + indent(out) << "validate();" << endl; + + indent_down(); + out << indent() << "}" << endl << endl; +} + +// generates as3 method to perform various checks +// (e.g. check that all required fields are set) +void t_as3_generator::generate_as3_validator(ostream& out, t_struct* tstruct) { + indent(out) << "public function validate():void {" << endl; + indent_up(); + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + out << indent() << "// check for required fields" << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_REQUIRED) { + if (type_can_be_null((*f_iter)->get_type())) { + indent(out) << "if (" << (*f_iter)->get_name() << " == null) {" << endl; + indent(out) << " throw new TProtocolError(TProtocolError.UNKNOWN, \"Required field '" + << (*f_iter)->get_name() << "' was not present! Struct: \" + toString());" + << endl; + indent(out) << "}" << endl; + } else { + indent(out) << "// alas, we cannot check '" << (*f_iter)->get_name() + << "' because it's a primitive and you chose the non-beans generator." << endl; + } + } + } + + // check that fields of type enum have valid values + out << indent() << "// check that fields of type enum have valid values" << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = (*f_iter); + t_type* type = field->get_type(); + // if field is an enum, check that its value is valid + if (type->is_enum()) { + indent(out) << "if (" << generate_isset_check(field) << " && !" << get_enum_class_name(type) + << ".VALID_VALUES.contains(" << field->get_name() << ")){" << endl; + indent_up(); + indent(out) << "throw new TProtocolError(TProtocolError.UNKNOWN, \"The field '" + << field->get_name() << "' has been assigned the invalid value \" + " + << field->get_name() << ");" << endl; + indent_down(); + indent(out) << "}" << endl; + } + } + + indent_down(); + indent(out) << "}" << endl << endl; +} + +/** + * Generates a function to write all the fields of the struct + * + * @param tstruct The struct definition + */ +void t_as3_generator::generate_as3_struct_writer(ostream& out, t_struct* tstruct) { + out << indent() << "public function write(oprot:TProtocol):void {" << endl; + indent_up(); + + string name = tstruct->get_name(); + const vector<t_field*>& fields = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator f_iter; + + // performs various checks (e.g. check that all required fields are set) + indent(out) << "validate();" << endl << endl; + + indent(out) << "oprot.writeStructBegin(STRUCT_DESC);" << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + bool could_be_unset = (*f_iter)->get_req() == t_field::T_OPTIONAL; + if (could_be_unset) { + indent(out) << "if (" << generate_isset_check(*f_iter) << ") {" << endl; + indent_up(); + } + bool null_allowed = type_can_be_null((*f_iter)->get_type()); + if (null_allowed) { + out << indent() << "if (this." << (*f_iter)->get_name() << " != null) {" << endl; + indent_up(); + } + + indent(out) << "oprot.writeFieldBegin(" << constant_name((*f_iter)->get_name()) + << "_FIELD_DESC);" << endl; + + // Write field contents + generate_serialize_field(out, *f_iter, "this."); + + // Write field closer + indent(out) << "oprot.writeFieldEnd();" << endl; + + if (null_allowed) { + indent_down(); + indent(out) << "}" << endl; + } + if (could_be_unset) { + indent_down(); + indent(out) << "}" << endl; + } + } + // Write the struct map + out << indent() << "oprot.writeFieldStop();" << endl << indent() << "oprot.writeStructEnd();" + << endl; + + indent_down(); + out << indent() << "}" << endl << endl; +} + +/** + * Generates a function to write all the fields of the struct, + * which is a function result. These fields are only written + * if they are set in the Isset array, and only one of them + * can be set at a time. + * + * @param tstruct The struct definition + */ +void t_as3_generator::generate_as3_struct_result_writer(ostream& out, t_struct* tstruct) { + out << indent() << "public function write(oprot:TProtocol):void {" << endl; + indent_up(); + + string name = tstruct->get_name(); + const vector<t_field*>& fields = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator f_iter; + + indent(out) << "oprot.writeStructBegin(STRUCT_DESC);" << endl; + + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + out << endl << indent() << "if "; + } else { + out << " else if "; + } + + out << "(this." << generate_isset_check(*f_iter) << ") {" << endl; + + indent_up(); + + indent(out) << "oprot.writeFieldBegin(" << constant_name((*f_iter)->get_name()) + << "_FIELD_DESC);" << endl; + + // Write field contents + generate_serialize_field(out, *f_iter, "this."); + + // Write field closer + indent(out) << "oprot.writeFieldEnd();" << endl; + + indent_down(); + indent(out) << "}"; + } + // Write the struct map + out << endl << indent() << "oprot.writeFieldStop();" << endl << indent() + << "oprot.writeStructEnd();" << endl; + + indent_down(); + out << indent() << "}" << endl << endl; +} + +void t_as3_generator::generate_reflection_getters(ostringstream& out, + t_type* type, + string field_name, + string cap_name) { + (void)type; + (void)cap_name; + indent(out) << "case " << upcase_string(field_name) << ":" << endl; + indent_up(); + indent(out) << "return this." << field_name << ";" << endl; + indent_down(); +} + +void t_as3_generator::generate_reflection_setters(ostringstream& out, + t_type* type, + string field_name, + string cap_name) { + (void)type; + (void)cap_name; + indent(out) << "case " << upcase_string(field_name) << ":" << endl; + indent_up(); + indent(out) << "if (value == null) {" << endl; + indent(out) << " unset" << get_cap_name(field_name) << "();" << endl; + indent(out) << "} else {" << endl; + indent(out) << " this." << field_name << " = value;" << endl; + indent(out) << "}" << endl; + indent(out) << "break;" << endl << endl; + + indent_down(); +} + +void t_as3_generator::generate_generic_field_getters_setters(std::ostream& out, + t_struct* tstruct) { + + std::ostringstream getter_stream; + std::ostringstream setter_stream; + + // build up the bodies of both the getter and setter at once + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = *f_iter; + t_type* type = get_true_type(field->get_type()); + std::string field_name = field->get_name(); + std::string cap_name = get_cap_name(field_name); + + indent_up(); + generate_reflection_setters(setter_stream, type, field_name, cap_name); + generate_reflection_getters(getter_stream, type, field_name, cap_name); + indent_down(); + } + + // create the setter + indent(out) << "public function setFieldValue(fieldID:int, value:*):void {" << endl; + indent_up(); + + indent(out) << "switch (fieldID) {" << endl; + + out << setter_stream.str(); + + indent(out) << "default:" << endl; + indent(out) << " throw new ArgumentError(\"Field \" + fieldID + \" doesn't exist!\");" << endl; + + indent(out) << "}" << endl; + + indent_down(); + indent(out) << "}" << endl << endl; + + // create the getter + indent(out) << "public function getFieldValue(fieldID:int):* {" << endl; + indent_up(); + + indent(out) << "switch (fieldID) {" << endl; + + out << getter_stream.str(); + + indent(out) << "default:" << endl; + indent(out) << " throw new ArgumentError(\"Field \" + fieldID + \" doesn't exist!\");" << endl; + + indent(out) << "}" << endl; + + indent_down(); + + indent(out) << "}" << endl << endl; +} + +// Creates a generic isSet method that takes the field number as argument +void t_as3_generator::generate_generic_isset_method(std::ostream& out, t_struct* tstruct) { + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + // create the isSet method + indent(out) << "// Returns true if field corresponding to fieldID is set (has been assigned a " + "value) and false otherwise" << endl; + indent(out) << "public function isSet(fieldID:int):Boolean {" << endl; + indent_up(); + indent(out) << "switch (fieldID) {" << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = *f_iter; + indent(out) << "case " << upcase_string(field->get_name()) << ":" << endl; + indent_up(); + indent(out) << "return " << generate_isset_check(field) << ";" << endl; + indent_down(); + } + + indent(out) << "default:" << endl; + indent(out) << " throw new ArgumentError(\"Field \" + fieldID + \" doesn't exist!\");" << endl; + + indent(out) << "}" << endl; + + indent_down(); + indent(out) << "}" << endl << endl; +} + +/** + * Generates a set of As3 Bean boilerplate functions (setters, getters, etc.) + * for the given struct. + * + * @param tstruct The struct definition + */ +void t_as3_generator::generate_as3_bean_boilerplate(ostream& out, + t_struct* tstruct, + bool bindable) { + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = *f_iter; + t_type* type = get_true_type(field->get_type()); + std::string field_name = field->get_name(); + std::string cap_name = get_cap_name(field_name); + + // Simple getter + generate_as3_doc(out, field); + indent(out) << "public function get " << field_name << "():" << type_name(type) << " {" << endl; + indent_up(); + indent(out) << "return this._" << field_name << ";" << endl; + indent_down(); + indent(out) << "}" << endl << endl; + + // Simple setter + generate_as3_doc(out, field); + std::string propName = tmp("thriftPropertyChange"); + if (bindable) { + indent(out) << "[Bindable(event=\"" << propName << "\")]" << endl; + } + indent(out) << "public function set " << field_name << "(" << field_name << ":" + << type_name(type) << "):void {" << endl; + indent_up(); + indent(out) << "this._" << field_name << " = " << field_name << ";" << endl; + generate_isset_set(out, field); + + if (bindable) { + // We have to use a custom event rather than the default, because if you use the default, + // the setter only gets called if the value has changed - this means calling + // foo.setIntValue(0) + // will not cause foo.isIntValueSet() to return true since the value of foo._intValue wasn't + // changed + // so the setter was never called. + indent(out) << "dispatchEvent(new Event(\"" << propName << "\"));" << endl; + + // However, if you just use a custom event, then collections won't be able to detect when + // elements + // in the collections have changed since they listed for PropertyChangeEvents. So, we + // dispatch both. + indent(out) << "dispatchEvent(new PropertyChangeEvent(PropertyChangeEvent.PROPERTY_CHANGE));" + << endl; + } + indent_down(); + indent(out) << "}" << endl << endl; + + // Unsetter + indent(out) << "public function unset" << cap_name << "():void {" << endl; + indent_up(); + if (type_can_be_null(type)) { + indent(out) << "this." << field_name << " = null;" << endl; + } else { + indent(out) << "this.__isset_" << field_name << " = false;" << endl; + } + indent_down(); + indent(out) << "}" << endl << endl; + + // isSet method + indent(out) << "// Returns true if field " << field_name + << " is set (has been assigned a value) and false otherwise" << endl; + indent(out) << "public function is" << get_cap_name("set") << cap_name << "():Boolean {" + << endl; + indent_up(); + if (type_can_be_null(type)) { + indent(out) << "return this." << field_name << " != null;" << endl; + } else { + indent(out) << "return this.__isset_" << field_name << ";" << endl; + } + indent_down(); + indent(out) << "}" << endl << endl; + } +} + +/** + * Generates a toString() method for the given struct + * + * @param tstruct The struct definition + */ +void t_as3_generator::generate_as3_struct_tostring(ostream& out, + t_struct* tstruct, + bool bindable) { + // If it's bindable, it extends EventDispatcher so toString is an override. + out << indent() << "public " << (bindable ? "override " : "") << "function toString():String {" + << endl; + indent_up(); + + out << indent() << "var ret:String = new String(\"" << tstruct->get_name() << "(\");" << endl; + out << indent() << "var first:Boolean = true;" << endl << endl; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + bool could_be_unset = (*f_iter)->get_req() == t_field::T_OPTIONAL; + if (could_be_unset) { + indent(out) << "if (" << generate_isset_check(*f_iter) << ") {" << endl; + indent_up(); + } + + t_field* field = (*f_iter); + + if (!first) { + indent(out) << "if (!first) ret += \", \";" << endl; + } + indent(out) << "ret += \"" << (*f_iter)->get_name() << ":\";" << endl; + bool can_be_null = type_can_be_null(field->get_type()); + if (can_be_null) { + indent(out) << "if (this." << (*f_iter)->get_name() << " == null) {" << endl; + indent(out) << " ret += \"null\";" << endl; + indent(out) << "} else {" << endl; + indent_up(); + } + + if (field->get_type()->is_binary()) { + indent(out) << " ret += \"BINARY\";" << endl; + } else if (field->get_type()->is_enum()) { + indent(out) << "var " << field->get_name() + << "_name:String = " << get_enum_class_name(field->get_type()) + << ".VALUES_TO_NAMES[this." << (*f_iter)->get_name() << "];" << endl; + indent(out) << "if (" << field->get_name() << "_name != null) {" << endl; + indent(out) << " ret += " << field->get_name() << "_name;" << endl; + indent(out) << " ret += \" (\";" << endl; + indent(out) << "}" << endl; + indent(out) << "ret += this." << field->get_name() << ";" << endl; + indent(out) << "if (" << field->get_name() << "_name != null) {" << endl; + indent(out) << " ret += \")\";" << endl; + indent(out) << "}" << endl; + } else { + indent(out) << "ret += this." << (*f_iter)->get_name() << ";" << endl; + } + + if (can_be_null) { + indent_down(); + indent(out) << "}" << endl; + } + indent(out) << "first = false;" << endl; + + if (could_be_unset) { + indent_down(); + indent(out) << "}" << endl; + } + first = false; + } + out << indent() << "ret += \")\";" << endl << indent() << "return ret;" << endl; + + indent_down(); + indent(out) << "}" << endl << endl; +} + +/** + * Generates a static map with meta data to store information such as fieldID to + * fieldName mapping + * + * @param tstruct The struct definition + */ +void t_as3_generator::generate_as3_meta_data_map(ostream& out, t_struct* tstruct) { + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + // Static Map with fieldID -> FieldMetaData mappings + indent(out) << "public static const metaDataMap:Dictionary = new Dictionary();" << endl; + + if (fields.size() > 0) { + // Populate map + scope_up(out); + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = *f_iter; + std::string field_name = field->get_name(); + indent(out) << "metaDataMap[" << upcase_string(field_name) << "] = new FieldMetaData(\"" + << field_name << "\", "; + + // Set field requirement type (required, optional, etc.) + if (field->get_req() == t_field::T_REQUIRED) { + out << "TFieldRequirementType.REQUIRED, "; + } else if (field->get_req() == t_field::T_OPTIONAL) { + out << "TFieldRequirementType.OPTIONAL, "; + } else { + out << "TFieldRequirementType.DEFAULT, "; + } + + // Create value meta data + generate_field_value_meta_data(out, field->get_type()); + out << ");" << endl; + } + scope_down(out); + } +} + +/** + * Returns a string with the as3 representation of the given thrift type + * (e.g. for the type struct it returns "TType.STRUCT") + */ +std::string t_as3_generator::get_as3_type_string(t_type* type) { + if (type->is_list()) { + return "TType.LIST"; + } else if (type->is_map()) { + return "TType.MAP"; + } else if (type->is_set()) { + return "TType.SET"; + } else if (type->is_struct() || type->is_xception()) { + return "TType.STRUCT"; + } else if (type->is_enum()) { + return "TType.I32"; + } else if (type->is_typedef()) { + return get_as3_type_string(((t_typedef*)type)->get_type()); + } else if (type->is_base_type()) { + switch (((t_base_type*)type)->get_base()) { + case t_base_type::TYPE_VOID: + return "TType.VOID"; + break; + case t_base_type::TYPE_STRING: + return "TType.STRING"; + break; + case t_base_type::TYPE_BOOL: + return "TType.BOOL"; + break; + case t_base_type::TYPE_I8: + return "TType.BYTE"; + break; + case t_base_type::TYPE_I16: + return "TType.I16"; + break; + case t_base_type::TYPE_I32: + return "TType.I32"; + break; + case t_base_type::TYPE_I64: + return "TType.I64"; + break; + case t_base_type::TYPE_DOUBLE: + return "TType.DOUBLE"; + break; + default: + throw std::runtime_error("Unknown thrift type \"" + type->get_name() + + "\" passed to t_as3_generator::get_as3_type_string!"); + break; // This should never happen! + } + } else { + throw std::runtime_error( + "Unknown thrift type \"" + type->get_name() + + "\" passed to t_as3_generator::get_as3_type_string!"); // This should never happen! + } +} + +void t_as3_generator::generate_field_value_meta_data(std::ostream& out, t_type* type) { + out << endl; + indent_up(); + indent_up(); + if (type->is_struct() || type->is_xception()) { + indent(out) << "new StructMetaData(TType.STRUCT, " << type_name(type); + } else if (type->is_container()) { + if (type->is_list()) { + indent(out) << "new ListMetaData(TType.LIST, "; + t_type* elem_type = ((t_list*)type)->get_elem_type(); + generate_field_value_meta_data(out, elem_type); + } else if (type->is_set()) { + indent(out) << "new SetMetaData(TType.SET, "; + t_type* elem_type = ((t_list*)type)->get_elem_type(); + generate_field_value_meta_data(out, elem_type); + } else { // map + indent(out) << "new MapMetaData(TType.MAP, "; + t_type* key_type = ((t_map*)type)->get_key_type(); + t_type* val_type = ((t_map*)type)->get_val_type(); + generate_field_value_meta_data(out, key_type); + out << ", "; + generate_field_value_meta_data(out, val_type); + } + } else { + indent(out) << "new FieldValueMetaData(" << get_as3_type_string(type); + } + out << ")"; + indent_down(); + indent_down(); +} + +/** + * Generates a thrift service. In C++, this comprises an entirely separate + * header and source file. The header file defines the methods and includes + * the data types defined in the main header file, and the implementation + * file contains implementations of the basic printer and default interfaces. + * + * @param tservice The service definition + */ +void t_as3_generator::generate_service(t_service* tservice) { + // Make interface file + string f_service_name = package_dir_ + "/" + service_name_ + ".as"; + f_service_.open(f_service_name.c_str()); + + f_service_ << autogen_comment() << as3_package(); + + scope_up(f_service_); + + f_service_ << endl << as3_type_imports() << as3_thrift_imports() + << as3_thrift_gen_imports(tservice); + + if (tservice->get_extends() != NULL) { + t_type* parent = tservice->get_extends(); + string parent_namespace = parent->get_program()->get_namespace("as3"); + if (!parent_namespace.empty() && parent_namespace != package_name_) { + f_service_ << "import " << type_name(parent) << ";" << endl; + } + } + + f_service_ << endl; + + generate_service_interface(tservice); + + scope_down(f_service_); + f_service_.close(); + + // Now make the implementation/client file + f_service_name = package_dir_ + "/" + service_name_ + "Impl.as"; + f_service_.open(f_service_name.c_str()); + + f_service_ << autogen_comment() << as3_package(); + + scope_up(f_service_); + + f_service_ << endl << as3_type_imports() << as3_thrift_imports() + << as3_thrift_gen_imports(tservice); + + if (tservice->get_extends() != NULL) { + t_type* parent = tservice->get_extends(); + string parent_namespace = parent->get_program()->get_namespace("as3"); + if (!parent_namespace.empty() && parent_namespace != package_name_) { + f_service_ << "import " << type_name(parent) << "Impl;" << endl; + } + } + + f_service_ << endl; + + generate_service_client(tservice); + scope_down(f_service_); + + f_service_ << as3_type_imports(); + f_service_ << as3_thrift_imports(); + f_service_ << as3_thrift_gen_imports(tservice); + if (!package_name_.empty()) { + f_service_ << "import " << package_name_ << ".*;" << endl; + } + + generate_service_helpers(tservice); + + f_service_.close(); + + // Now make the processor/server file + f_service_name = package_dir_ + "/" + service_name_ + "Processor.as"; + f_service_.open(f_service_name.c_str()); + + f_service_ << autogen_comment() << as3_package(); + + scope_up(f_service_); + + f_service_ << endl << as3_type_imports() << as3_thrift_imports() + << as3_thrift_gen_imports(tservice) << endl; + + generate_service_server(tservice); + scope_down(f_service_); + + f_service_ << as3_type_imports(); + f_service_ << as3_thrift_imports(); + f_service_ << as3_thrift_gen_imports(tservice) << endl; + if (!package_name_.empty()) { + f_service_ << "import " << package_name_ << ".*;" << endl; + } + + generate_service_helpers(tservice); + + f_service_.close(); +} + +/** + * Generates a service interface definition. + * + * @param tservice The service to generate a header definition for + */ +void t_as3_generator::generate_service_interface(t_service* tservice) { + string extends_iface = ""; + if (tservice->get_extends() != NULL) { + extends_iface = " extends " + tservice->get_extends()->get_name(); + } + + generate_as3_doc(f_service_, tservice); + f_service_ << indent() << "public interface " << service_name_ << extends_iface << " {" << endl + << endl; + indent_up(); + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_as3_doc(f_service_, *f_iter); + if (!(*f_iter)->is_oneway()) { + if ((*f_iter)->get_returntype()->is_void()) { + indent(f_service_) << "//function onError(Error):void;" << endl; + indent(f_service_) << "//function onSuccess():void;" << endl; + } else { + indent(f_service_) << "//function onError(Error):void;" << endl; + indent(f_service_) << "//function onSuccess(" << type_name((*f_iter)->get_returntype()) + << "):void;" << endl; + } + } + indent(f_service_) << function_signature(*f_iter) << ";" << endl << endl; + } + indent_down(); + f_service_ << indent() << "}" << endl << endl; +} + +/** + * Generates structs for all the service args and return types + * + * @param tservice The service + */ +void t_as3_generator::generate_service_helpers(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* ts = (*f_iter)->get_arglist(); + generate_as3_struct_definition(f_service_, ts, false, true); + generate_function_helpers(*f_iter); + } +} + +/** + * Generates a service client definition. + * + * @param tservice The service to generate a server for. + */ +void t_as3_generator::generate_service_client(t_service* tservice) { + string extends = ""; + string extends_client = ""; + if (tservice->get_extends() != NULL) { + extends = tservice->get_extends()->get_name(); + extends_client = " extends " + extends + "Impl"; + } + + indent(f_service_) << "public class " << service_name_ << "Impl" << extends_client + << " implements " << service_name_ << " {" << endl; + indent_up(); + + indent(f_service_) << "public function " << service_name_ << "Impl" + << "(iprot:TProtocol, oprot:TProtocol=null)" << endl; + scope_up(f_service_); + if (extends.empty()) { + f_service_ << indent() << "iprot_ = iprot;" << endl; + f_service_ << indent() << "if (oprot == null) {" << endl; + indent_up(); + f_service_ << indent() << "oprot_ = iprot;" << endl; + indent_down(); + f_service_ << indent() << "} else {" << endl; + indent_up(); + f_service_ << indent() << "oprot_ = oprot;" << endl; + indent_down(); + f_service_ << indent() << "}"; + } else { + f_service_ << indent() << "super(iprot, oprot);" << endl; + } + scope_down(f_service_); + f_service_ << endl; + + if (extends.empty()) { + f_service_ << indent() << "protected var iprot_:TProtocol;" << endl << indent() + << "protected var oprot_:TProtocol;" << endl << endl << indent() + << "protected var seqid_:int;" << endl << endl; + + indent(f_service_) << "public function getInputProtocol():TProtocol" << endl; + scope_up(f_service_); + indent(f_service_) << "return this.iprot_;" << endl; + scope_down(f_service_); + f_service_ << endl; + + indent(f_service_) << "public function getOutputProtocol():TProtocol" << endl; + scope_up(f_service_); + indent(f_service_) << "return this.oprot_;" << endl; + scope_down(f_service_); + f_service_ << endl; + } + + // Generate client method implementations + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + string funname = (*f_iter)->get_name(); + + // Open function + if (!(*f_iter)->is_oneway()) { + if ((*f_iter)->get_returntype()->is_void()) { + indent(f_service_) << "//function onError(Error):void;" << endl; + indent(f_service_) << "//function onSuccess():void;" << endl; + } else { + indent(f_service_) << "//function onError(Error):void;" << endl; + indent(f_service_) << "//function onSuccess(" << type_name((*f_iter)->get_returntype()) + << "):void;" << endl; + } + } + indent(f_service_) << "public " << function_signature(*f_iter) << endl; + scope_up(f_service_); + + // Get the struct of function call params + t_struct* arg_struct = (*f_iter)->get_arglist(); + + string argsname = (*f_iter)->get_name() + "_args"; + vector<t_field*>::const_iterator fld_iter; + const vector<t_field*>& fields = arg_struct->get_members(); + + // Serialize the request + f_service_ << indent() << "oprot_.writeMessageBegin(new TMessage(\"" << funname << "\", " + << ((*f_iter)->is_oneway() ? "TMessageType.ONEWAY" : "TMessageType.CALL") + << ", seqid_));" << endl << indent() << "var args:" << argsname << " = new " + << argsname << "();" << endl; + + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + f_service_ << indent() << "args." << (*fld_iter)->get_name() << " = " + << (*fld_iter)->get_name() << ";" << endl; + } + + f_service_ << indent() << "args.write(oprot_);" << endl << indent() + << "oprot_.writeMessageEnd();" << endl; + + if ((*f_iter)->is_oneway()) { + f_service_ << indent() << "oprot_.getTransport().flush();" << endl; + } else { + f_service_ << indent() << "oprot_.getTransport().flush(function(error:Error):void {" << endl; + indent_up(); + f_service_ << indent() << "try {" << endl; + indent_up(); + string resultname = (*f_iter)->get_name() + "_result"; + f_service_ << indent() << "if (error != null) {" << endl << indent() + << " if (onError != null) onError(error);" << endl << indent() << " return;" + << endl << indent() << "}" << endl << indent() + << "var msg:TMessage = iprot_.readMessageBegin();" << endl << indent() + << "if (msg.type == TMessageType.EXCEPTION) {" << endl << indent() + << " var x:TApplicationError = TApplicationError.read(iprot_);" << endl + << indent() << " iprot_.readMessageEnd();" << endl << indent() + << " if (onError != null) onError(x);" << endl << indent() << " return;" << endl + << indent() << "}" << endl << indent() << "var result :" << resultname << " = new " + << resultname << "();" << endl << indent() << "result.read(iprot_);" << endl + << indent() << "iprot_.readMessageEnd();" << endl; + + // Careful, only return _result if not a void function + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_ << indent() << "if (result." << generate_isset_check("success") << ") {" << endl + << indent() << " if (onSuccess != null) onSuccess(result.success);" << endl + << indent() << " return;" << endl << indent() << "}" << endl; + } + + t_struct* xs = (*f_iter)->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + f_service_ << indent() << "if (result." << (*x_iter)->get_name() << " != null) {" << endl + << indent() << " if (onError != null) onError(result." << (*x_iter)->get_name() + << ");" << endl << indent() << " return;" << endl << indent() << "}" << endl; + } + + // If you get here it's an exception, unless a void function + if ((*f_iter)->get_returntype()->is_void()) { + f_service_ << indent() << "if (onSuccess != null) onSuccess();" << endl << indent() + << "return;" << endl; + } else { + + f_service_ << indent() << "if (onError != null) onError(new " + "TApplicationError(TApplicationError.MISSING_RESULT, \"" + << (*f_iter)->get_name() << " failed: unknown result\"));" << endl; + } + indent_down(); + f_service_ << indent() << "} catch (e:TError) {" << endl << indent() + << " if (onError != null) onError(e);" << endl << indent() << "}" << endl; + + indent_down(); + indent(f_service_) << "});" << endl; + } + // Close function + scope_down(f_service_); + f_service_ << endl; + } + + indent_down(); + indent(f_service_) << "}" << endl; +} + +/** + * Generates a service server definition. + * + * @param tservice The service to generate a server for. + */ +void t_as3_generator::generate_service_server(t_service* tservice) { + // Generate the dispatch methods + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + // Extends stuff + string extends = ""; + string extends_processor = ""; + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + extends_processor = " extends " + extends + "Processor"; + } + + // Generate the header portion + indent(f_service_) << "public class " << service_name_ << "Processor" << extends_processor + << " implements TProcessor {" << endl; + indent_up(); + + indent(f_service_) << "public function " << service_name_ << "Processor(iface:" << service_name_ + << ")" << endl; + scope_up(f_service_); + if (!extends.empty()) { + f_service_ << indent() << "super(iface);" << endl; + } + f_service_ << indent() << "iface_ = iface;" << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + f_service_ << indent() << "PROCESS_MAP[\"" << (*f_iter)->get_name() + << "\"] = " << (*f_iter)->get_name() << "();" << endl; + } + + scope_down(f_service_); + f_service_ << endl; + + f_service_ << indent() << "private var iface_:" << service_name_ << ";" << endl; + + if (extends.empty()) { + f_service_ << indent() << "protected const PROCESS_MAP:Dictionary = new Dictionary();" << endl; + } + + f_service_ << endl; + + // Generate the server implementation + string override = ""; + if (tservice->get_extends() != NULL) { + override = "override "; + } + indent(f_service_) << override + << "public function process(iprot:TProtocol, oprot:TProtocol):Boolean" << endl; + scope_up(f_service_); + + f_service_ << indent() << "var msg:TMessage = iprot.readMessageBegin();" << endl; + + // TODO(mcslee): validate message, was the seqid etc. legit? + // AS- If all method is oneway: + // do you have an oprot? + // do you you need nullcheck? + f_service_ + << indent() << "var fn:Function = PROCESS_MAP[msg.name];" << endl << indent() + << "if (fn == null) {" << endl << indent() << " TProtocolUtil.skip(iprot, TType.STRUCT);" + << endl << indent() << " iprot.readMessageEnd();" << endl << indent() + << " var x:TApplicationError = new TApplicationError(TApplicationError.UNKNOWN_METHOD, " + "\"Invalid method name: '\"+msg.name+\"'\");" << endl << indent() + << " oprot.writeMessageBegin(new TMessage(msg.name, TMessageType.EXCEPTION, msg.seqid));" + << endl << indent() << " x.write(oprot);" << endl << indent() << " oprot.writeMessageEnd();" + << endl << indent() << " oprot.getTransport().flush();" << endl << indent() + << " return true;" << endl << indent() << "}" << endl << indent() + << "fn.call(this,msg.seqid, iprot, oprot);" << endl; + + f_service_ << indent() << "return true;" << endl; + + scope_down(f_service_); + f_service_ << endl; + + // Generate the process subfunctions + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_process_function(tservice, *f_iter); + } + + indent_down(); + indent(f_service_) << "}" << endl << endl; +} + +/** + * Generates a struct and helpers for a function. + * + * @param tfunction The function + */ +void t_as3_generator::generate_function_helpers(t_function* tfunction) { + if (tfunction->is_oneway()) { + return; + } + + t_struct result(program_, tfunction->get_name() + "_result"); + t_field success(tfunction->get_returntype(), "success", 0); + if (!tfunction->get_returntype()->is_void()) { + result.append(&success); + } + + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& fields = xs->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + result.append(*f_iter); + } + + generate_as3_struct_definition(f_service_, &result, false, true, true); +} + +/** + * Generates a process function definition. + * + * @param tfunction The function to write a dispatcher for + */ +void t_as3_generator::generate_process_function(t_service* tservice, t_function* tfunction) { + (void)tservice; + // Open class + indent(f_service_) << "private function " << tfunction->get_name() << "():Function {" << endl; + indent_up(); + + // Open function + indent(f_service_) << "return function(seqid:int, iprot:TProtocol, oprot:TProtocol):void" << endl; + scope_up(f_service_); + + string argsname = tfunction->get_name() + "_args"; + string resultname = tfunction->get_name() + "_result"; + + f_service_ << indent() << "var args:" << argsname << " = new " << argsname << "();" << endl + << indent() << "args.read(iprot);" << endl << indent() << "iprot.readMessageEnd();" + << endl; + + t_struct* xs = tfunction->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + + // Declare result for non oneway function + if (!tfunction->is_oneway()) { + f_service_ << indent() << "var result:" << resultname << " = new " << resultname << "();" + << endl; + } + + // Try block for a function with exceptions + if (xceptions.size() > 0) { + f_service_ << indent() << "try {" << endl; + indent_up(); + } + + // Generate the function call + t_struct* arg_struct = tfunction->get_arglist(); + const std::vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + + f_service_ << indent(); + if (tfunction->is_oneway()) { + f_service_ << "iface_." << tfunction->get_name() << "("; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << "args." << (*f_iter)->get_name(); + } + f_service_ << ");" << endl; + } else { + f_service_ << "// sorry this operation is not supported yet" << endl; + f_service_ << indent() << "throw new Error(\"This is not yet supported\");" << endl; + } + + // Set isset on success field + if (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void() + && !type_can_be_null(tfunction->get_returntype())) { + f_service_ << indent() << "result.set" << get_cap_name("success") << get_cap_name("isSet") + << "(true);" << endl; + } + + if (!tfunction->is_oneway() && xceptions.size() > 0) { + indent_down(); + f_service_ << indent() << "}"; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + f_service_ << " catch (" << (*x_iter)->get_name() << ":" + << type_name((*x_iter)->get_type(), false, false) << ") {" << endl; + if (!tfunction->is_oneway()) { + indent_up(); + f_service_ << indent() << "result." << (*x_iter)->get_name() << " = " + << (*x_iter)->get_name() << ";" << endl; + indent_down(); + f_service_ << indent() << "}"; + } else { + f_service_ << "}"; + } + } + f_service_ << " catch (th:Error) {" << endl; + indent_up(); + f_service_ << indent() << "trace(\"Internal error processing " << tfunction->get_name() + << "\", th);" << endl << indent() + << "var x:TApplicationError = new " + "TApplicationError(TApplicationError.INTERNAL_ERROR, \"Internal error processing " + << tfunction->get_name() << "\");" << endl << indent() + << "oprot.writeMessageBegin(new TMessage(\"" << tfunction->get_name() + << "\", TMessageType.EXCEPTION, seqid));" << endl << indent() << "x.write(oprot);" + << endl << indent() << "oprot.writeMessageEnd();" << endl << indent() + << "oprot.getTransport().flush();" << endl << indent() << "return;" << endl; + indent_down(); + f_service_ << indent() << "}" << endl; + } + + // Shortcut out here for oneway functions + if (tfunction->is_oneway()) { + f_service_ << indent() << "return;" << endl; + scope_down(f_service_); + + // Close class + indent_down(); + f_service_ << indent() << "}" << endl << endl; + return; + } + + f_service_ << indent() << "oprot.writeMessageBegin(new TMessage(\"" << tfunction->get_name() + << "\", TMessageType.REPLY, seqid));" << endl << indent() << "result.write(oprot);" + << endl << indent() << "oprot.writeMessageEnd();" << endl << indent() + << "oprot.getTransport().flush();" << endl; + + // Close function + scope_down(f_service_); + f_service_ << endl; + + // Close class + indent_down(); + f_service_ << indent() << "}" << endl << endl; +} + +/** + * Deserializes a field of any type. + * + * @param tfield The field + * @param prefix The variable name or container for this field + */ +void t_as3_generator::generate_deserialize_field(ostream& out, t_field* tfield, string prefix) { + t_type* type = get_true_type(tfield->get_type()); + + if (type->is_void()) { + throw "CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name(); + } + + string name = prefix + tfield->get_name(); + + if (type->is_struct() || type->is_xception()) { + generate_deserialize_struct(out, (t_struct*)type, name); + } else if (type->is_container()) { + generate_deserialize_container(out, type, name); + } else if (type->is_base_type() || type->is_enum()) { + + indent(out) << name << " = iprot."; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + out << "readBinary();"; + } else { + out << "readString();"; + } + break; + case t_base_type::TYPE_BOOL: + out << "readBool();"; + break; + case t_base_type::TYPE_I8: + out << "readByte();"; + break; + case t_base_type::TYPE_I16: + out << "readI16();"; + break; + case t_base_type::TYPE_I32: + out << "readI32();"; + break; + case t_base_type::TYPE_I64: + out << "readI64();"; + break; + case t_base_type::TYPE_DOUBLE: + out << "readDouble();"; + break; + default: + throw "compiler error: no As3 name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "readI32();"; + } + out << endl; + } else { + printf("DO NOT KNOW HOW TO DESERIALIZE FIELD '%s' TYPE '%s'\n", + tfield->get_name().c_str(), + type_name(type).c_str()); + } +} + +/** + * Generates an unserializer for a struct, invokes read() + */ +void t_as3_generator::generate_deserialize_struct(ostream& out, t_struct* tstruct, string prefix) { + out << indent() << prefix << " = new " << type_name(tstruct) << "();" << endl << indent() + << prefix << ".read(iprot);" << endl; +} + +/** + * Deserializes a container by reading its size and then iterating + */ +void t_as3_generator::generate_deserialize_container(ostream& out, t_type* ttype, string prefix) { + scope_up(out); + + string obj; + + if (ttype->is_map()) { + obj = tmp("_map"); + } else if (ttype->is_set()) { + obj = tmp("_set"); + } else if (ttype->is_list()) { + obj = tmp("_list"); + } + + // Declare variables, read header + if (ttype->is_map()) { + indent(out) << "var " << obj << ":TMap = iprot.readMapBegin();" << endl; + } else if (ttype->is_set()) { + indent(out) << "var " << obj << ":TSet = iprot.readSetBegin();" << endl; + } else if (ttype->is_list()) { + indent(out) << "var " << obj << ":TList = iprot.readListBegin();" << endl; + } + + indent(out) << prefix << " = new " << type_name(ttype, false, true) + // size the collection correctly + << "(" + << ");" << endl; + + // For loop iterates over elements + string i = tmp("_i"); + indent(out) << "for (var " << i << ":int = 0; " << i << " < " << obj << ".size" + << "; " + << "++" << i << ")" << endl; + + scope_up(out); + + if (ttype->is_map()) { + generate_deserialize_map_element(out, (t_map*)ttype, prefix); + } else if (ttype->is_set()) { + generate_deserialize_set_element(out, (t_set*)ttype, prefix); + } else if (ttype->is_list()) { + generate_deserialize_list_element(out, (t_list*)ttype, prefix); + } + + scope_down(out); + + // Read container end + if (ttype->is_map()) { + indent(out) << "iprot.readMapEnd();" << endl; + } else if (ttype->is_set()) { + indent(out) << "iprot.readSetEnd();" << endl; + } else if (ttype->is_list()) { + indent(out) << "iprot.readListEnd();" << endl; + } + + scope_down(out); +} + +/** + * Generates code to deserialize a map + */ +void t_as3_generator::generate_deserialize_map_element(ostream& out, t_map* tmap, string prefix) { + string key = tmp("_key"); + string val = tmp("_val"); + t_field fkey(tmap->get_key_type(), key); + t_field fval(tmap->get_val_type(), val); + + indent(out) << declare_field(&fkey) << endl; + indent(out) << declare_field(&fval) << endl; + + generate_deserialize_field(out, &fkey); + generate_deserialize_field(out, &fval); + + indent(out) << prefix << "[" << key << "] = " << val << ";" << endl; +} + +/** + * Deserializes a set element + */ +void t_as3_generator::generate_deserialize_set_element(ostream& out, t_set* tset, string prefix) { + string elem = tmp("_elem"); + t_field felem(tset->get_elem_type(), elem); + + indent(out) << declare_field(&felem) << endl; + + generate_deserialize_field(out, &felem); + + indent(out) << prefix << ".add(" << elem << ");" << endl; +} + +/** + * Deserializes a list element + */ +void t_as3_generator::generate_deserialize_list_element(ostream& out, + t_list* tlist, + string prefix) { + string elem = tmp("_elem"); + t_field felem(tlist->get_elem_type(), elem); + + indent(out) << declare_field(&felem) << endl; + + generate_deserialize_field(out, &felem); + + indent(out) << prefix << ".push(" << elem << ");" << endl; +} + +/** + * Serializes a field of any type. + * + * @param tfield The field to serialize + * @param prefix Name to prepend to field name + */ +void t_as3_generator::generate_serialize_field(ostream& out, t_field* tfield, string prefix) { + t_type* type = get_true_type(tfield->get_type()); + + // Do nothing for void types + if (type->is_void()) { + throw "CANNOT GENERATE SERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name(); + } + + if (type->is_struct() || type->is_xception()) { + generate_serialize_struct(out, (t_struct*)type, prefix + tfield->get_name()); + } else if (type->is_container()) { + generate_serialize_container(out, type, prefix + tfield->get_name()); + } else if (type->is_base_type() || type->is_enum()) { + + string name = prefix + tfield->get_name(); + indent(out) << "oprot."; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + out << "writeBinary(" << name << ");"; + } else { + out << "writeString(" << name << ");"; + } + break; + case t_base_type::TYPE_BOOL: + out << "writeBool(" << name << ");"; + break; + case t_base_type::TYPE_I8: + out << "writeByte(" << name << ");"; + break; + case t_base_type::TYPE_I16: + out << "writeI16(" << name << ");"; + break; + case t_base_type::TYPE_I32: + out << "writeI32(" << name << ");"; + break; + case t_base_type::TYPE_I64: + out << "writeI64(" << name << ");"; + break; + case t_base_type::TYPE_DOUBLE: + out << "writeDouble(" << name << ");"; + break; + default: + throw "compiler error: no As3 name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "writeI32(" << name << ");"; + } + out << endl; + } else { + printf("DO NOT KNOW HOW TO SERIALIZE FIELD '%s%s' TYPE '%s'\n", + prefix.c_str(), + tfield->get_name().c_str(), + type_name(type).c_str()); + } +} + +/** + * Serializes all the members of a struct. + * + * @param tstruct The struct to serialize + * @param prefix String prefix to attach to all fields + */ +void t_as3_generator::generate_serialize_struct(ostream& out, t_struct* tstruct, string prefix) { + (void)tstruct; + out << indent() << prefix << ".write(oprot);" << endl; +} + +/** + * Serializes a container by writing its size then the elements. + * + * @param ttype The type of container + * @param prefix String prefix for fields + */ +void t_as3_generator::generate_serialize_container(ostream& out, t_type* ttype, string prefix) { + scope_up(out); + + if (ttype->is_map()) { + string iter = tmp("_key"); + string counter = tmp("_sizeCounter"); + indent(out) << "var " << counter << ":int = 0;" << endl; + indent(out) << "for (var " << iter << ":* in " << prefix << ") {" << endl; + indent(out) << " " << counter << +"++;" << endl; + indent(out) << "}" << endl; + + indent(out) << "oprot.writeMapBegin(new TMap(" << type_to_enum(((t_map*)ttype)->get_key_type()) + << ", " << type_to_enum(((t_map*)ttype)->get_val_type()) << ", " << counter << "));" + << endl; + } else if (ttype->is_set()) { + indent(out) << "oprot.writeSetBegin(new TSet(" << type_to_enum(((t_set*)ttype)->get_elem_type()) + << ", " << prefix << ".size));" << endl; + } else if (ttype->is_list()) { + indent(out) << "oprot.writeListBegin(new TList(" + << type_to_enum(((t_list*)ttype)->get_elem_type()) << ", " << prefix << ".length));" + << endl; + } + + string iter = tmp("elem"); + if (ttype->is_map()) { + indent(out) << "for (var " << iter << ":* in " << prefix << ")"; + } else if (ttype->is_set()) { + indent(out) << "for each (var " << iter << ":* in " << prefix << ".toArray())"; + } else if (ttype->is_list()) { + indent(out) << "for each (var " << iter << ":* in " << prefix << ")"; + } + + scope_up(out); + + if (ttype->is_map()) { + generate_serialize_map_element(out, (t_map*)ttype, iter, prefix); + } else if (ttype->is_set()) { + generate_serialize_set_element(out, (t_set*)ttype, iter); + } else if (ttype->is_list()) { + generate_serialize_list_element(out, (t_list*)ttype, iter); + } + + scope_down(out); + + if (ttype->is_map()) { + indent(out) << "oprot.writeMapEnd();" << endl; + } else if (ttype->is_set()) { + indent(out) << "oprot.writeSetEnd();" << endl; + } else if (ttype->is_list()) { + indent(out) << "oprot.writeListEnd();" << endl; + } + + scope_down(out); +} + +/** + * Serializes the members of a map. + */ +void t_as3_generator::generate_serialize_map_element(ostream& out, + t_map* tmap, + string iter, + string map) { + t_field kfield(tmap->get_key_type(), iter); + generate_serialize_field(out, &kfield, ""); + t_field vfield(tmap->get_val_type(), map + "[" + iter + "]"); + generate_serialize_field(out, &vfield, ""); +} + +/** + * Serializes the members of a set. + */ +void t_as3_generator::generate_serialize_set_element(ostream& out, t_set* tset, string iter) { + t_field efield(tset->get_elem_type(), iter); + generate_serialize_field(out, &efield, ""); +} + +/** + * Serializes the members of a list. + */ +void t_as3_generator::generate_serialize_list_element(ostream& out, t_list* tlist, string iter) { + t_field efield(tlist->get_elem_type(), iter); + generate_serialize_field(out, &efield, ""); +} + +/** + * Returns a As3 type name + * + * @param ttype The type + * @param container Is the type going inside a container? + * @return As3 type name, i.e. HashMap<Key,Value> + */ +string t_as3_generator::type_name(t_type* ttype, bool in_container, bool in_init) { + (void)in_init; + // In As3 typedefs are just resolved to their real type + ttype = get_true_type(ttype); + string prefix; + + if (ttype->is_base_type()) { + return base_type_name((t_base_type*)ttype, in_container); + } else if (ttype->is_enum()) { + return "int"; + } else if (ttype->is_map()) { + return "Dictionary"; + } else if (ttype->is_set()) { + return "Set"; + } else if (ttype->is_list()) { + return "Array"; + } + + // Check for namespacing + t_program* program = ttype->get_program(); + if (program != NULL && program != program_) { + string package = program->get_namespace("as3"); + if (!package.empty()) { + return package + "." + ttype->get_name(); + } + } + + return ttype->get_name(); +} + +/** + * Returns the AS3 type that corresponds to the thrift type. + * + * @param tbase The base type + * @param container Is it going in a As3 container? + */ +string t_as3_generator::base_type_name(t_base_type* type, bool in_container) { + (void)in_container; + t_base_type::t_base tbase = type->get_base(); + + switch (tbase) { + case t_base_type::TYPE_VOID: + return "void"; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + return "ByteArray"; + } else { + return "String"; + } + case t_base_type::TYPE_BOOL: + return "Boolean"; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + return "int"; + case t_base_type::TYPE_I64: + throw "i64 is not yet supported in as3"; + case t_base_type::TYPE_DOUBLE: + return "Number"; + default: + throw "compiler error: no As3 name for base type " + t_base_type::t_base_name(tbase); + } +} + +/** + * Declares a field, which may include initialization as necessary. + * + * @param ttype The type + */ +string t_as3_generator::declare_field(t_field* tfield, bool init) { + // TODO(mcslee): do we ever need to initialize the field? + string result = "var " + tfield->get_name() + ":" + type_name(tfield->get_type()); + if (init) { + t_type* ttype = get_true_type(tfield->get_type()); + if (ttype->is_base_type() && tfield->get_value() != NULL) { + std::ofstream dummy; + result += " = " + render_const_value(dummy, tfield->get_name(), ttype, tfield->get_value()); + } else if (ttype->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)ttype)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + result += " = null"; + break; + case t_base_type::TYPE_BOOL: + result += " = false"; + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + result += " = 0"; + break; + case t_base_type::TYPE_DOUBLE: + result += " = (double)0"; + break; + } + + } else if (ttype->is_enum()) { + result += " = 0"; + } else if (ttype->is_container()) { + result += " = new " + type_name(ttype, false, true) + "()"; + } else { + result += " = new " + type_name(ttype, false, true) + "()"; + ; + } + } + return result + ";"; +} + +/** + * Renders a function signature of the form 'type name(args)' + * + * @param tfunction Function definition + * @return String of rendered function definition + */ +string t_as3_generator::function_signature(t_function* tfunction, string prefix) { + std::string arguments = argument_list(tfunction->get_arglist()); + if (!tfunction->is_oneway()) { + if (arguments != "") { + arguments += ", "; + } + arguments += "onError:Function, onSuccess:Function"; + } + + std::string result = "function " + prefix + tfunction->get_name() + "(" + arguments + "):void"; + return result; +} + +/** + * Renders a comma separated field list, with type names + */ +string t_as3_generator::argument_list(t_struct* tstruct) { + string result = ""; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + result += ", "; + } + result += (*f_iter)->get_name() + ":" + type_name((*f_iter)->get_type()); + } + return result; +} + +/** + * Converts the parse type to a C++ enum string for the given type. + */ +string t_as3_generator::type_to_enum(t_type* type) { + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + return "TType.STRING"; + case t_base_type::TYPE_BOOL: + return "TType.BOOL"; + case t_base_type::TYPE_I8: + return "TType.BYTE"; + case t_base_type::TYPE_I16: + return "TType.I16"; + case t_base_type::TYPE_I32: + return "TType.I32"; + case t_base_type::TYPE_I64: + return "TType.I64"; + case t_base_type::TYPE_DOUBLE: + return "TType.DOUBLE"; + } + } else if (type->is_enum()) { + return "TType.I32"; + } else if (type->is_struct() || type->is_xception()) { + return "TType.STRUCT"; + } else if (type->is_map()) { + return "TType.MAP"; + } else if (type->is_set()) { + return "TType.SET"; + } else if (type->is_list()) { + return "TType.LIST"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +/** + * Applies the correct style to a string based on the value of nocamel_style_ + */ +std::string t_as3_generator::get_cap_name(std::string name) { + name[0] = toupper(name[0]); + return name; +} + +string t_as3_generator::constant_name(string name) { + string constant_name; + + bool is_first = true; + bool was_previous_char_upper = false; + for (char character : name) { + bool is_upper = isupper(character); + + if (is_upper && !is_first && !was_previous_char_upper) { + constant_name += '_'; + } + constant_name += toupper(character); + + is_first = false; + was_previous_char_upper = is_upper; + } + + return constant_name; +} + +/** + * Emits a As3Doc comment if the provided object has a doc in Thrift + */ +void t_as3_generator::generate_as3_doc(ostream& out, t_doc* tdoc) { + if (tdoc->has_doc()) { + generate_docstring_comment(out, "/**\n", " * ", tdoc->get_doc(), " */\n"); + } +} + +/** + * Emits a As3Doc comment if the provided function object has a doc in Thrift + */ +void t_as3_generator::generate_as3_doc(ostream& out, t_function* tfunction) { + if (tfunction->has_doc()) { + stringstream ss; + ss << tfunction->get_doc(); + const vector<t_field*>& fields = tfunction->get_arglist()->get_members(); + vector<t_field*>::const_iterator p_iter; + for (p_iter = fields.begin(); p_iter != fields.end(); ++p_iter) { + t_field* p = *p_iter; + ss << "\n@param " << p->get_name(); + if (p->has_doc()) { + ss << " " << p->get_doc(); + } + } + generate_docstring_comment(out, "/**\n", " * ", ss.str(), " */\n"); + } +} + +std::string t_as3_generator::generate_isset_check(t_field* field) { + return generate_isset_check(field->get_name()); +} + +std::string t_as3_generator::generate_isset_check(std::string field_name) { + return "is" + get_cap_name("set") + get_cap_name(field_name) + "()"; +} + +void t_as3_generator::generate_isset_set(ostream& out, t_field* field) { + if (!type_can_be_null(field->get_type())) { + indent(out) << "this.__isset_" << field->get_name() << " = true;" << endl; + } +} + +std::string t_as3_generator::get_enum_class_name(t_type* type) { + string package = ""; + t_program* program = type->get_program(); + if (program != NULL && program != program_) { + package = program->get_namespace("as3") + "."; + } + return package + type->get_name(); +} + +THRIFT_REGISTER_GENERATOR( + as3, + "AS3", + " bindable: Add [bindable] metadata to all the struct classes.\n") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_c_glib_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_c_glib_generator.cc new file mode 100644 index 000000000..f1531cc83 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_c_glib_generator.cc @@ -0,0 +1,4583 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +#include <fstream> +#include <iostream> +#include <stdexcept> +#include <string> +#include <vector> + +#include <ctype.h> + +#include "thrift/platform.h" +#include "thrift/generate/t_oop_generator.h" + +using std::map; +using std::ostream; +using std::ostringstream; +using std::string; +using std::stringstream; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes + +/* forward declarations */ +string initial_caps_to_underscores(string name); +string underscores_to_initial_caps(string name); +string to_upper_case(string name); +string to_lower_case(string name); + +/** + * C code generator, using glib for C typing. + */ +class t_c_glib_generator : public t_oop_generator { +public: + /* constructor */ + t_c_glib_generator(t_program* program, + const map<string, string>& parsed_options, + const string& option_string) + : t_oop_generator(program) { + (void)option_string; + std::map<std::string, std::string>::const_iterator iter; + + /* set the output directory */ + this->out_dir_base_ = "gen-c_glib"; + + /* no options yet */ + for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + throw "unknown option c_glib:" + iter->first; + } + + /* set the namespace */ + this->nspace = program_->get_namespace("c_glib"); + + if (this->nspace.empty()) { + this->nspace = ""; + this->nspace_u = ""; + this->nspace_uc = ""; + this->nspace_lc = ""; + } else { + /* replace dots with underscores */ + char* tmp = strdup(this->nspace.c_str()); + for (unsigned int i = 0; i < strlen(tmp); i++) { + if (tmp[i] == '.') { + tmp[i] = '_'; + } + } + this->nspace = string(tmp, strlen(tmp)); + free(tmp); + + /* clean up the namespace for C. + * An input of 'namespace foo' should result in: + * - nspace = foo - for thrift objects and typedefs + * - nspace_u = Foo - for internal GObject prefixes + * - nspace_uc = FOO_ - for macro prefixes + * - nspace_lc = foo_ - for filename and method prefixes + * The underscores are there since uc and lc strings are used as file and + * variable prefixes. + */ + this->nspace_u = initial_caps_to_underscores(this->nspace); + this->nspace_uc = to_upper_case(this->nspace_u) + "_"; + this->nspace_lc = to_lower_case(this->nspace_u) + "_"; + } + } + + /* initialization and destruction */ + void init_generator() override; + void close_generator() override; + + /* generation functions */ + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_consts(vector<t_const*> consts) override; + void generate_struct(t_struct* tstruct) override; + void generate_service(t_service* tservice) override; + void generate_xception(t_struct* tstruct) override; + +private: + /* file streams */ + ofstream_with_content_based_conditional_update f_types_; + ofstream_with_content_based_conditional_update f_types_impl_; + ofstream_with_content_based_conditional_update f_header_; + ofstream_with_content_based_conditional_update f_service_; + + /* namespace variables */ + string nspace; + string nspace_u; + string nspace_uc; + string nspace_lc; + + /* helper functions */ + bool is_complex_type(t_type* ttype); + bool is_numeric(t_type* ttype); + string type_name(t_type* ttype, bool in_typedef = false, bool is_const = false); + string property_type_name(t_type* ttype, bool in_typedef = false, bool is_const = false); + string base_type_name(t_type* type); + string type_to_enum(t_type* type); + string constant_literal(t_type* type, t_const_value* value); + string constant_value(string name, t_type* type, t_const_value* value); + string constant_value_with_storage(string name, t_type* type, t_const_value* value); + string function_signature(t_function* tfunction); + string argument_list(t_struct* tstruct); + string xception_list(t_struct* tstruct); + string declare_field(t_field* tfield, + bool init = false, + bool pointer = false, + bool constant = false, + bool reference = false); + void declare_local_variable(ostream& out, t_type* ttype, string& base_name, bool for_hash_table); + void declore_local_variable_for_write(ostream& out, t_type* ttype, string& base_name); + + /* generation functions */ + void generate_const_initializer(string name, + t_type* type, + t_const_value* value, + bool top_level = false); + void generate_service_helpers(t_service* tservice); + void generate_service_client(t_service* tservice); + void generate_service_handler(t_service* tservice); + void generate_service_processor(t_service* tservice); + void generate_service_server(t_service* tservice); + void generate_object(t_struct* tstruct); + void generate_struct_writer(ostream& out, + t_struct* tstruct, + string this_name, + string this_get = "", + bool is_function = true); + void generate_struct_reader(ostream& out, + t_struct* tstruct, + string this_name, + string this_get = "", + bool is_function = true); + + void generate_serialize_field(ostream& out, + t_field* tfield, + string prefix, + string suffix, + int error_ret); + void generate_serialize_struct(ostream& out, t_struct* tstruct, string prefix, int error_ret); + void generate_serialize_container(ostream& out, t_type* ttype, string prefix, int error_ret); + void generate_serialize_map_element(ostream& out, + t_map* tmap, + string key, + string value, + int error_ret); + void generate_serialize_set_element(ostream& out, t_set* tset, string element, int error_ret); + void generate_serialize_list_element(ostream& out, + t_list* tlist, + string list, + string index, + int error_ret); + + void generate_deserialize_field(ostream& out, + t_field* tfield, + string prefix, + string suffix, + int error_ret, + bool allocate = true); + void generate_deserialize_struct(ostream& out, + t_struct* tstruct, + string prefix, + int error_ret, + bool allocate = true); + void generate_deserialize_container(ostream& out, t_type* ttype, string prefix, int error_ret); + void generate_deserialize_map_element(ostream& out, t_map* tmap, string prefix, int error_ret); + void generate_deserialize_set_element(ostream& out, t_set* tset, string prefix, int error_ret); + void generate_deserialize_list_element(ostream& out, + t_list* tlist, + string prefix, + string index, + int error_ret); + + string generate_new_hash_from_type(t_type* key, t_type* value); + string generate_new_array_from_type(t_type* ttype); + + string generate_free_func_from_type(t_type* ttype); + string generate_hash_func_from_type(t_type* ttype); + string generate_cmp_func_from_type(t_type* ttype); +}; + +/** + * Prepare for file generation by opening up the necessary file + * output streams. + */ +void t_c_glib_generator::init_generator() { + /* create output directory */ + MKDIR(get_out_dir().c_str()); + + string program_name_u = initial_caps_to_underscores(program_name_); + string program_name_uc = to_upper_case(program_name_u); + string program_name_lc = to_lower_case(program_name_u); + + /* create output files */ + string f_types_name = get_out_dir() + this->nspace_lc + program_name_lc + "_types.h"; + f_types_.open(f_types_name.c_str()); + string f_types_impl_name = get_out_dir() + this->nspace_lc + program_name_lc + "_types.c"; + f_types_impl_.open(f_types_impl_name.c_str()); + + /* add thrift boilerplate headers */ + f_types_ << autogen_comment(); + f_types_impl_ << autogen_comment(); + + /* include inclusion guard */ + f_types_ << "#ifndef " << this->nspace_uc << program_name_uc << "_TYPES_H" << endl << "#define " + << this->nspace_uc << program_name_uc << "_TYPES_H" << endl << endl; + + /* include base types */ + f_types_ << "/* base includes */" << endl << "#include <glib-object.h>" << endl + << "#include <thrift/c_glib/thrift_struct.h>" << endl + << "#include <thrift/c_glib/protocol/thrift_protocol.h>" << endl; + + /* include other thrift includes */ + const vector<t_program*>& includes = program_->get_includes(); + if (!includes.empty()) { + f_types_ << "/* other thrift includes */" << endl; + + for (auto include : includes) { + const std::string& include_nspace = include->get_namespace("c_glib"); + std::string include_nspace_prefix = + include_nspace.empty() ? "" : initial_caps_to_underscores(include_nspace) + "_"; + + f_types_ << "#include \"" << include_nspace_prefix + << initial_caps_to_underscores(include->get_name()) << "_types.h\"" << endl; + } + f_types_ << endl; + } + + /* include custom headers */ + const vector<string>& c_includes = program_->get_c_includes(); + f_types_ << "/* custom thrift includes */" << endl; + for (const auto & c_include : c_includes) { + if (c_include[0] == '<') { + f_types_ << "#include " << c_include << endl; + } else { + f_types_ << "#include \"" << c_include << "\"" << endl; + } + } + f_types_ << endl; + + /* include math.h (for "INFINITY") in the implementation file, in case we + encounter a struct with a member of type double */ + f_types_impl_ << endl << "#include <math.h>" << endl; + + // include the types file + f_types_impl_ << endl << "#include \"" << this->nspace_lc << program_name_u << "_types.h\"" + << endl << "#include <thrift/c_glib/thrift.h>" << endl << endl; + + f_types_ << "/* begin types */" << endl << endl; +} + +/** + * Finish up generation and close all file streams. + */ +void t_c_glib_generator::close_generator() { + string program_name_uc = to_upper_case(initial_caps_to_underscores(program_name_)); + + /* end the header inclusion guard */ + f_types_ << "#endif /* " << this->nspace_uc << program_name_uc << "_TYPES_H */" << endl; + + /* close output file */ + f_types_.close(); + f_types_impl_.close(); +} + +/** + * Generates a Thrift typedef in C code. For example: + * + * Thrift: + * typedef map<i32,i32> SomeMap + * + * C: + * typedef GHashTable * ThriftSomeMap; + */ +void t_c_glib_generator::generate_typedef(t_typedef* ttypedef) { + f_types_ << indent() << "typedef " << type_name(ttypedef->get_type(), true) << " " << this->nspace + << ttypedef->get_symbolic() << ";" << endl << endl; +} + +/** + * Generates a C enumeration. For example: + * + * Thrift: + * enum MyEnum { + * ONE = 1, + * TWO + * } + * + * C: + * enum _ThriftMyEnum { + * THRIFT_MY_ENUM_ONE = 1, + * THRIFT_MY_ENUM_TWO + * }; + * typedef enum _ThriftMyEnum ThriftMyEnum; + */ +void t_c_glib_generator::generate_enum(t_enum* tenum) { + string name = tenum->get_name(); + string name_uc = to_upper_case(initial_caps_to_underscores(name)); + + f_types_ << indent() << "enum _" << this->nspace << name << " {" << endl; + + indent_up(); + + vector<t_enum_value*> constants = tenum->get_constants(); + vector<t_enum_value*>::iterator c_iter; + bool first = true; + + /* output each of the enumeration elements */ + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + if (first) { + first = false; + } else { + f_types_ << "," << endl; + } + + f_types_ << indent() << this->nspace_uc << name_uc << "_" << (*c_iter)->get_name(); + f_types_ << " = " << (*c_iter)->get_value(); + } + + indent_down(); + f_types_ << endl << "};" << endl << "typedef enum _" << this->nspace << name << " " + << this->nspace << name << ";" << endl << endl; + + f_types_ << "/* return the name of the constant */" << endl; + f_types_ << "const char *" << endl; + f_types_ << "toString_" << name << "(int value); " << endl << endl; + ; + f_types_impl_ << "/* return the name of the constant */" << endl; + f_types_impl_ << "const char *" << endl; + f_types_impl_ << "toString_" << name << "(int value) " << endl; + f_types_impl_ << "{" << endl; + f_types_impl_ << " static __thread char buf[16] = {0};" << endl; + f_types_impl_ << " switch(value) {" << endl; + std::set<int> done; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + int value = (*c_iter)->get_value(); + // Skipping duplicate value + if (done.find(value) == done.end()) { + done.insert(value); + f_types_impl_ << " case " << this->nspace_uc << name_uc << "_" << (*c_iter)->get_name() + << ":" + << "return \"" << this->nspace_uc << name_uc << "_" << (*c_iter)->get_name() + << "\";" << endl; + } + } + f_types_impl_ << " default: g_snprintf(buf, 16, \"%d\", value); return buf;" << endl; + f_types_impl_ << " }" << endl; + f_types_impl_ << "}" << endl << endl; +} + +/** + * Generates Thrift constants in C code. + */ +void t_c_glib_generator::generate_consts(vector<t_const*> consts) { + f_types_ << "/* constants */" << endl; + f_types_impl_ << "/* constants */" << endl; + + vector<t_const*>::iterator c_iter; + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + string name = (*c_iter)->get_name(); + string name_uc = to_upper_case(name); + string name_lc = to_lower_case(name); + t_type* type = (*c_iter)->get_type(); + t_const_value* value = (*c_iter)->get_value(); + + if (is_complex_type(type)) { + f_types_ << type_name(type) << indent() << this->nspace_lc << name_lc + << "_constant();" << endl; + } + + f_types_ << indent() << "#define " << this->nspace_uc << name_uc << " " + << constant_value(name_lc, type, value) << endl; + + generate_const_initializer(name_lc, type, value, true); + } + + f_types_ << endl; + f_types_impl_ << endl; +} + +/** + * Generate Thrift structs in C code, as GObjects. Example: + * + * Thrift: + * struct Bonk + * { + * 1: string message, + * 2: i32 type + * } + * + * C GObject instance header: + * struct _ThriftBonk + * { + * GObject parent; + * + * gchar * message; + * gint32 type; + * }; + * typedef struct _ThriftBonk ThriftBonk + * // ... additional GObject boilerplate ... + */ +void t_c_glib_generator::generate_struct(t_struct* tstruct) { + f_types_ << "/* struct " << tstruct->get_name() << " */" << endl; + generate_object(tstruct); +} + +/** + * Generate C code to represent Thrift services. Creates a new GObject + * which can be used to access the service. + */ +void t_c_glib_generator::generate_service(t_service* tservice) { + string svcname_u = initial_caps_to_underscores(tservice->get_name()); + string svcname_uc = this->nspace_uc + to_upper_case(svcname_u); + string filename = this->nspace_lc + to_lower_case(svcname_u); + + // make output files + string f_header_name = get_out_dir() + filename + ".h"; + f_header_.open(f_header_name.c_str()); + + string program_name_u = initial_caps_to_underscores(program_name_); + string program_name_lc = to_lower_case(program_name_u); + + // add header file boilerplate + f_header_ << autogen_comment(); + + // add an inclusion guard + f_header_ << "#ifndef " << svcname_uc << "_H" << endl << "#define " << svcname_uc << "_H" << endl + << endl; + + // add standard includes + f_header_ << "#include <thrift/c_glib/processor/thrift_dispatch_processor.h>" << endl << endl; + f_header_ << "#include \"" << this->nspace_lc << program_name_lc << "_types.h\"" << endl; + + // if we are inheriting from another service, include its header + t_service* extends_service = tservice->get_extends(); + if (extends_service != NULL) { + f_header_ << "#include \"" << this->nspace_lc + << to_lower_case(initial_caps_to_underscores(extends_service->get_name())) << ".h\"" + << endl; + } + f_header_ << endl; + + // create the service implementation + string f_service_name = get_out_dir() + filename + ".c"; + f_service_.open(f_service_name.c_str()); + + // add the boilerplace header + f_service_ << autogen_comment(); + + // include the headers + f_service_ << "#include <string.h>" << endl << "#include <thrift/c_glib/thrift.h>" << endl + << "#include <thrift/c_glib/thrift_application_exception.h>" << endl << "#include \"" + << filename << ".h\"" << endl << endl; + + // generate the service-helper classes + generate_service_helpers(tservice); + + // generate the client objects + generate_service_client(tservice); + + // generate the server objects + generate_service_server(tservice); + + // end the header inclusion guard + f_header_ << "#endif /* " << svcname_uc << "_H */" << endl; + + // close the files + f_service_.close(); + f_header_.close(); +} + +/** + * + */ +void t_c_glib_generator::generate_xception(t_struct* tstruct) { + string name = tstruct->get_name(); + string name_u = initial_caps_to_underscores(name); + string name_lc = to_lower_case(name_u); + string name_uc = to_upper_case(name_u); + + generate_object(tstruct); + + f_types_ << "/* exception */" << endl + << "typedef enum" << endl + << "{" << endl; + indent_up(); + f_types_ << indent() << this->nspace_uc << name_uc << "_ERROR_CODE" << endl; + indent_down(); + f_types_ << "} " << this->nspace << name << "Error;" << endl + << endl + << "GQuark " << this->nspace_lc << name_lc + << "_error_quark (void);" << endl + << "#define " << this->nspace_uc << name_uc << "_ERROR (" + << this->nspace_lc << name_lc << "_error_quark())" << endl + << endl + << endl; + + f_types_impl_ << "/* define the GError domain for exceptions */" << endl << "#define " + << this->nspace_uc << name_uc << "_ERROR_DOMAIN \"" << this->nspace_lc << name_lc + << "_error_quark\"" << endl << "GQuark" << endl << this->nspace_lc << name_lc + << "_error_quark (void)" << endl << "{" << endl + << " return g_quark_from_static_string (" << this->nspace_uc << name_uc + << "_ERROR_DOMAIN);" << endl << "}" << endl << endl; +} + +/******************** + * HELPER FUNCTIONS * + ********************/ + +/** + * Returns true if ttype is not a primitive. + */ +bool t_c_glib_generator::is_complex_type(t_type* ttype) { + ttype = get_true_type(ttype); + + return ttype->is_container() || ttype->is_struct() || ttype->is_xception(); +} + +bool t_c_glib_generator::is_numeric(t_type* ttype) { + return ttype->is_enum() || (ttype->is_base_type() && !ttype->is_string()); +} + +/** + * Maps a Thrift t_type to a C type. + */ +string t_c_glib_generator::type_name(t_type* ttype, bool in_typedef, bool is_const) { + if (ttype->is_base_type()) { + string bname = base_type_name(ttype); + + if (is_const) { + return "const " + bname; + } else { + return bname; + } + } + + if (ttype->is_container()) { + string cname; + + t_container* tcontainer = (t_container*)ttype; + if (tcontainer->has_cpp_name()) { + cname = tcontainer->get_cpp_name(); + } else if (ttype->is_map()) { + cname = "GHashTable"; + } else if (ttype->is_set()) { + // since a set requires unique elements, use a GHashTable, and + // populate the keys and values with the same data, using keys for + // the actual writes and reads. + // TODO: discuss whether or not to implement TSet, THashSet or GHashSet + cname = "GHashTable"; + } else if (ttype->is_list()) { + t_type* etype = get_true_type(((t_list*)ttype)->get_elem_type()); + if (etype->is_void()) { + throw std::runtime_error("compiler error: list element type cannot be void"); + } + // TODO: investigate other implementations besides GPtrArray + cname = is_numeric(etype) ? "GArray" : "GPtrArray"; + } + + /* Omit the dereference operator if we are aliasing this type within a + typedef, to allow the type to be used more naturally in client code; + otherwise, include it */ + if (!in_typedef) { + cname += " *"; + } + + if (is_const) { + return "const " + cname; + } else { + return cname; + } + } + + // check for a namespace + t_program* tprogram = ttype->get_program(); + string pname = (tprogram ? tprogram->get_namespace("c_glib") : "") + ttype->get_name(); + + if (is_complex_type(ttype)) { + pname += " *"; + } + + if (is_const) { + return "const " + pname; + } else { + return pname; + } +} + +/** + * Maps a Thrift primitive to the type needed to hold its value when used as an + * object property. + * + * This method is needed because all integer properties of width less than 64 + * bits map to the same type, gint, as opposed to their width-specific type + * (gint8, gint16 or gint32). + */ +string t_c_glib_generator::property_type_name(t_type* ttype, bool in_typedef, bool is_const) { + string result; + + if (ttype->is_base_type()) { + switch (((t_base_type*)ttype)->get_base()) { + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + if (is_const) { + result = "const gint"; + } else { + result = "gint"; + } + break; + + default: + result = type_name(ttype, in_typedef, is_const); + } + } else { + result = type_name(ttype, in_typedef, is_const); + } + + return result; +} + +/** + * Maps a Thrift primitive to a C primitive. + */ +string t_c_glib_generator::base_type_name(t_type* type) { + if (type->is_enum()) { + return type_name(type); + } + if (!type->is_base_type()) { + throw std::invalid_argument("Only base types are suppported."); + } + t_base_type* base_type = reinterpret_cast<t_base_type*>(type); + t_base_type::t_base tbase = base_type->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + return "void"; + case t_base_type::TYPE_STRING: + if (base_type->is_binary()) { + return "GByteArray *"; + } else { + return "gchar *"; + } + case t_base_type::TYPE_BOOL: + return "gboolean"; + case t_base_type::TYPE_I8: + return "gint8"; + case t_base_type::TYPE_I16: + return "gint16"; + case t_base_type::TYPE_I32: + return "gint32"; + case t_base_type::TYPE_I64: + return "gint64"; + case t_base_type::TYPE_DOUBLE: + return "gdouble"; + default: + throw std::logic_error("compiler error: no C base type name for base type " + + t_base_type::t_base_name(tbase)); + } +} + +/** + * Returns a member of the ThriftType C enumeration in thrift_protocol.h + * for a Thrift type. + */ +string t_c_glib_generator::type_to_enum(t_type* type) { + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + return "T_STRING"; + case t_base_type::TYPE_BOOL: + return "T_BOOL"; + case t_base_type::TYPE_I8: + return "T_BYTE"; + case t_base_type::TYPE_I16: + return "T_I16"; + case t_base_type::TYPE_I32: + return "T_I32"; + case t_base_type::TYPE_I64: + return "T_I64"; + case t_base_type::TYPE_DOUBLE: + return "T_DOUBLE"; + } + } else if (type->is_enum()) { + return "T_I32"; + } else if (type->is_struct()) { + return "T_STRUCT"; + } else if (type->is_xception()) { + return "T_STRUCT"; + } else if (type->is_map()) { + return "T_MAP"; + } else if (type->is_set()) { + return "T_SET"; + } else if (type->is_list()) { + return "T_LIST"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +/** + * Returns a Thrift constant formatted as a literal for inclusion in C code. + */ +string t_c_glib_generator::constant_literal(t_type* type, t_const_value* value) { + ostringstream render; + + if (type->is_base_type()) { + /* primitives */ + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + + switch (tbase) { + case t_base_type::TYPE_STRING: + render << "\"" + value->get_string() + "\""; + break; + case t_base_type::TYPE_BOOL: + render << ((value->get_integer() != 0) ? "TRUE" : "FALSE"); + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + render << value->get_integer(); + break; + case t_base_type::TYPE_DOUBLE: + render << value->get_double(); + break; + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + } else { + t_const_value::t_const_value_type value_type = value->get_type(); + + switch (value_type) { + case t_const_value::CV_IDENTIFIER: + render << value->get_integer(); + break; + case t_const_value::CV_LIST: + render << "{ "; + { + t_type* elem_type = ((t_list*)type)->get_elem_type(); + const vector<t_const_value*>& list = value->get_list(); + vector<t_const_value*>::const_iterator list_iter; + + if (list.size() > 0) { + list_iter = list.begin(); + render << constant_literal(elem_type, *list_iter); + + while (++list_iter != list.end()) { + render << ", " << constant_literal(elem_type, *list_iter); + } + } + } + render << " }"; + break; + case t_const_value::CV_MAP: + default: + render << "NULL /* not supported */"; + } + } + + return render.str(); +} + +/** + * Returns C code that represents a Thrift constant. + */ +string t_c_glib_generator::constant_value(string name, t_type* type, t_const_value* value) { + ostringstream render; + + if (type->is_base_type()) { + /* primitives */ + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + render << "g_strdup (\"" + value->get_string() + "\")"; + break; + case t_base_type::TYPE_BOOL: + render << ((value->get_integer() != 0) ? 1 : 0); + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + render << value->get_integer(); + break; + case t_base_type::TYPE_I64: + render << "G_GINT64_CONSTANT (" << value->get_integer() << ")"; + break; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) { + render << value->get_integer(); + } else { + render << value->get_double(); + } + break; + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + render << "(" << type_name(type) << ")" << value->get_integer(); + } else if (is_complex_type(type)) { + render << "(" << this->nspace_lc << to_lower_case(name) << "_constant())"; + } else { + render << "NULL /* not supported */"; + } + + return render.str(); +} + +/** + * Renders a function signature of the form 'type name(args)' + * + * @param tfunction Function definition + * @return String of rendered function definition + */ +string t_c_glib_generator::function_signature(t_function* tfunction) { + t_type* ttype = tfunction->get_returntype(); + t_struct* arglist = tfunction->get_arglist(); + t_struct* xlist = tfunction->get_xceptions(); + string fname = initial_caps_to_underscores(tfunction->get_name()); + + bool has_return = !ttype->is_void(); + bool has_args = arglist->get_members().size() == 0; + bool has_xceptions = xlist->get_members().size() == 0; + return "gboolean " + this->nspace_lc + fname + " (" + this->nspace + service_name_ + "If * iface" + + (has_return ? ", " + type_name(ttype) + "* _return" : "") + + (has_args ? "" : (", " + argument_list(arglist))) + + (has_xceptions ? "" : (", " + xception_list(xlist))) + ", GError ** error)"; +} + +/** + * Renders a field list + * + * @param tstruct The struct definition + * @return Comma sepearated list of all field names in that struct + */ +string t_c_glib_generator::argument_list(t_struct* tstruct) { + string result = ""; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + result += ", "; + } + result += type_name((*f_iter)->get_type(), false, true) + " " + (*f_iter)->get_name(); + } + return result; +} + +/** + * Renders mutable exception lists + * + * @param tstruct The struct definition + * @return Comma sepearated list of all field names in that struct + */ +string t_c_glib_generator::xception_list(t_struct* tstruct) { + string result = ""; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + result += ", "; + } + result += type_name((*f_iter)->get_type(), false, false) + "* " + (*f_iter)->get_name(); + } + return result; +} + +/** + * Declares a field, including any necessary initialization. + */ +string t_c_glib_generator::declare_field(t_field* tfield, + bool init, + bool pointer, + bool constant, + bool reference) { + string result = ""; + if (constant) { + result += "const "; + } + result += type_name(tfield->get_type()); + if (pointer) { + result += "*"; + } + if (reference) { + result += "*"; + } + result += " " + tfield->get_name(); + if (init) { + t_type* type = get_true_type(tfield->get_type()); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + break; + case t_base_type::TYPE_BOOL: + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + result += " = 0"; + break; + case t_base_type::TYPE_DOUBLE: + result += " = (gdouble) 0"; + break; + case t_base_type::TYPE_STRING: + result += " = NULL"; + break; + default: + throw "compiler error: no C intializer for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + result += " = (" + type_name(type) + ") 0"; + } else if (type->is_struct() || type->is_container()) { + result += " = NULL"; + } + } + + if (!reference) { + result += ";"; + } + + return result; +} + +string t_c_glib_generator::constant_value_with_storage(string fname, + t_type* etype, + t_const_value* value) { + ostringstream render; + if (is_numeric(etype)) { + render << " " << type_name(etype) << " *" << fname << " = " + << "g_new (" << base_type_name(etype) << ", 1);" << endl + << " *" << fname << " = " << constant_value(fname, (t_type*)etype, value) << ";" + << endl; + } else { + render << " " << type_name(etype) << " " << fname << " = " + << constant_value(fname, (t_type*)etype, value) << ";" << endl; + } + return render.str(); +} + +/** + * Generates C code that initializes complex constants. + */ +void t_c_glib_generator::generate_const_initializer(string name, + t_type* type, + t_const_value* value, + bool top_level) { + string name_u = initial_caps_to_underscores(name); + string name_lc = to_lower_case(name_u); + string type_u = initial_caps_to_underscores(type->get_name()); + string type_uc = to_upper_case(type_u); + string maybe_static = top_level ? "" : "static "; + + if (type->is_struct() || type->is_xception()) { + const vector<t_field*>& fields = ((t_struct*)type)->get_members(); + vector<t_field*>::const_iterator f_iter; + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + ostringstream initializers; + + // initialize any constants that may be referenced by this initializer + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + t_type* field_type = NULL; + string field_name = ""; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field_type = (*f_iter)->get_type(); + field_name = (*f_iter)->get_name(); + break; + } + } + if (field_type == NULL) { + throw "type error: " + type->get_name() + " has no field " + + v_iter->first->get_string(); + } + field_name = tmp(field_name); + + generate_const_initializer(name + "_constant_" + field_name, + field_type, + v_iter->second); + initializers << " constant->" << v_iter->first->get_string() << " = " + << constant_value(name + "_constant_" + field_name, + field_type, + v_iter->second) << ";" << endl + << " constant->__isset_" << v_iter->first->get_string() + << " = TRUE;" << endl; + } + + // implement the initializer + f_types_impl_ << maybe_static << this->nspace << type->get_name() << " *" + << endl + << this->nspace_lc << name_lc << "_constant (void)" << endl; + scope_up(f_types_impl_); + f_types_impl_ << indent() << "static " << this->nspace << type->get_name() + << " *constant = NULL;" << endl + << indent() << "if (constant == NULL)" << endl; + scope_up(f_types_impl_); + f_types_impl_ << indent() << "constant = g_object_new (" << this->nspace_uc + << "TYPE_" << type_uc << ", NULL);" << endl + << initializers.str(); + scope_down(f_types_impl_); + + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + t_type* field_type = NULL; + string field_name = ""; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field_type = (*f_iter)->get_type(); + field_name = (*f_iter)->get_name(); + break; + } + } + if (field_type == NULL) { + throw "type error: " + type->get_name() + " has no field " + + v_iter->first->get_string(); + } + field_name = tmp(field_name); + } + + f_types_impl_ << indent() << "return constant;" << endl; + scope_down(f_types_impl_); + f_types_impl_ << endl; + } else if (type->is_list()) { + string list_type = "GPtrArray *"; + string free_func + = generate_free_func_from_type(reinterpret_cast<t_list*>(type)->get_elem_type()); + string list_initializer = "g_ptr_array_new_with_free_func (" + free_func + ");"; + string list_appender = "g_ptr_array_add"; + bool list_variable = false; + + t_type* etype = ((t_list*)type)->get_elem_type(); + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + ostringstream initializers; + ostringstream appenders; + + list_initializer = generate_new_array_from_type(etype); + if (etype->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)etype)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot determine array type"; + case t_base_type::TYPE_BOOL: + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + case t_base_type::TYPE_DOUBLE: + list_type = "GArray *"; + list_appender = "g_array_append_val"; + list_variable = true; + break; + case t_base_type::TYPE_STRING: + break; + default: + throw "compiler error: no array info for type"; + } + } else if (etype->is_enum()) { + list_type = "GArray *"; + list_appender = "g_array_append_val"; + list_variable = true; + } + + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string fname = tmp(name); + + generate_const_initializer(fname, etype, (*v_iter)); + if (list_variable) { + initializers << " " << type_name(etype) << " " << fname << " = " + << constant_value(fname, (t_type*)etype, (*v_iter)) << ";" + << endl; + appenders << " " << list_appender << "(constant, " << fname << ");" + << endl; + } else { + appenders << " " << list_appender << "(constant, " + << constant_value(fname, (t_type*)etype, (*v_iter)) << ");" + << endl; + } + } + + f_types_impl_ << maybe_static << list_type << endl + << this->nspace_lc << name_lc << "_constant (void)" << endl; + scope_up(f_types_impl_); + f_types_impl_ << indent() << "static " << list_type << " constant = NULL;" + << endl + << indent() << "if (constant == NULL)" << endl; + scope_up(f_types_impl_); + if (!initializers.str().empty()) { + f_types_impl_ << initializers.str() + << endl; + } + f_types_impl_ << indent() << "constant = " << list_initializer << endl + << appenders.str(); + scope_down(f_types_impl_); + f_types_impl_ << indent() << "return constant;" << endl; + scope_down(f_types_impl_); + f_types_impl_ << endl; + } else if (type->is_set()) { + t_type* etype = ((t_set*)type)->get_elem_type(); + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + ostringstream initializers; + ostringstream appenders; + + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string fname = tmp(name); + string ptr = is_numeric(etype) ? "*" : ""; + generate_const_initializer(fname, etype, (*v_iter)); + initializers << constant_value_with_storage(fname, (t_type*)etype, *v_iter); + appenders << " g_hash_table_insert (constant, " << fname << ", 0);" << endl; + } + + f_types_impl_ << maybe_static << "GHashTable *" << endl + << this->nspace_lc << name_lc << "_constant (void)" << endl; + scope_up(f_types_impl_); + f_types_impl_ << indent() << "static GHashTable *constant = NULL;" << endl + << indent() << "if (constant == NULL)" << endl; + scope_up(f_types_impl_); + f_types_impl_ << initializers.str() << endl + << indent() << "constant = " << generate_new_hash_from_type(etype, NULL) << endl + << appenders.str(); + scope_down(f_types_impl_); + f_types_impl_ << indent() << "return constant;" << endl; + scope_down(f_types_impl_); + f_types_impl_ << endl; + } else if (type->is_map()) { + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + ostringstream initializers; + ostringstream appenders; + + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string fname = tmp(name); + string kname = fname + "key"; + string vname = fname + "val"; + generate_const_initializer(kname, ktype, v_iter->first); + generate_const_initializer(vname, vtype, v_iter->second); + + initializers << constant_value_with_storage(kname, (t_type*)ktype, v_iter->first); + initializers << constant_value_with_storage(vname, (t_type*)vtype, v_iter->second); + appenders << " g_hash_table_insert (constant, " << kname << ", " << vname << ");" << endl; + } + + f_types_impl_ << maybe_static << "GHashTable *" << endl + << this->nspace_lc << name_lc << "_constant (void)" << endl; + scope_up(f_types_impl_); + f_types_impl_ << indent() << "static GHashTable *constant = NULL;" << endl + << indent() << "if (constant == NULL)" << endl; + scope_up(f_types_impl_); + f_types_impl_ << initializers.str() << endl + << indent() << "constant = " << generate_new_hash_from_type(ktype, vtype) << endl + << appenders.str(); + scope_down(f_types_impl_); + f_types_impl_ << indent() << "return constant;" << endl; + scope_down(f_types_impl_); + f_types_impl_ << endl; + } +} + +/** + * Generates helper classes for a service, consisting of a ThriftStruct subclass + * for the arguments to and the result from each method. + * + * @param tservice The service for which to generate helper classes + */ +void t_c_glib_generator::generate_service_helpers(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator function_iter; + + // Iterate through the service's methods + for (function_iter = functions.begin(); function_iter != functions.end(); ++function_iter) { + string function_name = (*function_iter)->get_name(); + t_struct* arg_list = (*function_iter)->get_arglist(); + string arg_list_name_orig = arg_list->get_name(); + + // Generate the arguments class + arg_list->set_name(tservice->get_name() + underscores_to_initial_caps(function_name) + "Args"); + generate_struct(arg_list); + + arg_list->set_name(arg_list_name_orig); + + // Generate the result class + if (!(*function_iter)->is_oneway()) { + t_struct result(program_, + tservice->get_name() + underscores_to_initial_caps(function_name) + "Result"); + t_field success((*function_iter)->get_returntype(), "success", 0); + success.set_req(t_field::T_OPTIONAL); + if (!(*function_iter)->get_returntype()->is_void()) { + result.append(&success); + } + + t_struct* xs = (*function_iter)->get_xceptions(); + const vector<t_field*>& fields = xs->get_members(); + vector<t_field*>::const_iterator field_iter; + for (field_iter = fields.begin(); field_iter != fields.end(); ++field_iter) { + (*field_iter)->set_req(t_field::T_OPTIONAL); + result.append(*field_iter); + } + + generate_struct(&result); + } + } +} + +/** + * Generates C code that represents a Thrift service client. + */ +void t_c_glib_generator::generate_service_client(t_service* tservice) { + /* get some C friendly service names */ + string service_name_lc = to_lower_case(initial_caps_to_underscores(service_name_)); + string service_name_uc = to_upper_case(service_name_lc); + + string parent_service_name; + string parent_service_name_lc; + string parent_service_name_uc; + + string parent_class_name = "GObject"; + string parent_type_name = "G_TYPE_OBJECT"; + + // The service this service extends, or NULL if it extends no + // service + t_service* extends_service = tservice->get_extends(); + if (extends_service) { + // The name of the parent service + parent_service_name = extends_service->get_name(); + parent_service_name_lc = to_lower_case(initial_caps_to_underscores(parent_service_name)); + parent_service_name_uc = to_upper_case(parent_service_name_lc); + + // The names of the client class' parent class and type + parent_class_name = this->nspace + parent_service_name + "Client"; + parent_type_name = this->nspace_uc + "TYPE_" + parent_service_name_uc + "_CLIENT"; + } + + // The base service (the topmost in the "extends" hierarchy), on + // whose client class the "input_protocol" and "output_protocol" + // properties are defined + t_service* base_service = tservice; + while (base_service->get_extends()) { + base_service = base_service->get_extends(); + } + + string base_service_name = base_service->get_name(); + string base_service_name_lc = to_lower_case(initial_caps_to_underscores(base_service_name)); + string base_service_name_uc = to_upper_case(base_service_name_lc); + + // Generate the client interface dummy object in the header. + f_header_ << "/* " << service_name_ << " service interface */" << endl << "typedef struct _" + << this->nspace << service_name_ << "If " << this->nspace << service_name_ << "If; " + << " /* dummy object */" << endl << endl; + + // Generate the client interface object in the header. + f_header_ << "struct _" << this->nspace << service_name_ << "IfInterface" << endl << "{" << endl + << " GTypeInterface parent;" << endl << endl; + + /* write out the functions for this interface */ + indent_up(); + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + /* make the function name C friendly */ + string funname = initial_caps_to_underscores((*f_iter)->get_name()); + t_type* ttype = (*f_iter)->get_returntype(); + t_struct* arglist = (*f_iter)->get_arglist(); + t_struct* xlist = (*f_iter)->get_xceptions(); + bool has_return = !ttype->is_void(); + bool has_args = arglist->get_members().size() == 0; + bool has_xceptions = xlist->get_members().size() == 0; + + string params = "(" + this->nspace + service_name_ + "If *iface" + + (has_return ? ", " + type_name(ttype) + "* _return" : "") + + (has_args ? "" : (", " + argument_list(arglist))) + + (has_xceptions ? "" : (", " + xception_list(xlist))) + ", GError **error)"; + + indent(f_header_) << "gboolean (*" << funname << ") " << params << ";" << endl; + } + indent_down(); + + f_header_ << "};" << endl << "typedef struct _" << this->nspace << service_name_ << "IfInterface " + << this->nspace << service_name_ << "IfInterface;" << endl << endl; + + // generate all the interface boilerplate + f_header_ << "GType " << this->nspace_lc << service_name_lc << "_if_get_type (void);" << endl + << "#define " << this->nspace_uc << "TYPE_" << service_name_uc << "_IF " + << "(" << this->nspace_lc << service_name_lc << "_if_get_type())" << endl << "#define " + << this->nspace_uc << service_name_uc << "_IF(obj) " + << "(G_TYPE_CHECK_INSTANCE_CAST ((obj), " << this->nspace_uc << "TYPE_" + << service_name_uc << "_IF, " << this->nspace << service_name_ << "If))" << endl + << "#define " << this->nspace_uc << "IS_" << service_name_uc << "_IF(obj) " + << "(G_TYPE_CHECK_INSTANCE_TYPE ((obj), " << this->nspace_uc << "TYPE_" + << service_name_uc << "_IF))" << endl << "#define " << this->nspace_uc + << service_name_uc << "_IF_GET_INTERFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), " + << this->nspace_uc << "TYPE_" << service_name_uc << "_IF, " << this->nspace + << service_name_ << "IfInterface))" << endl << endl; + + // write out all the interface function prototypes + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + /* make the function name C friendly */ + string funname = initial_caps_to_underscores((*f_iter)->get_name()); + t_type* ttype = (*f_iter)->get_returntype(); + t_struct* arglist = (*f_iter)->get_arglist(); + t_struct* xlist = (*f_iter)->get_xceptions(); + bool has_return = !ttype->is_void(); + bool has_args = arglist->get_members().size() == 0; + bool has_xceptions = xlist->get_members().size() == 0; + + string params = "(" + this->nspace + service_name_ + "If *iface" + + (has_return ? ", " + type_name(ttype) + "* _return" : "") + + (has_args ? "" : (", " + argument_list(arglist))) + + (has_xceptions ? "" : (", " + xception_list(xlist))) + ", GError **error)"; + + f_header_ << "gboolean " << this->nspace_lc << service_name_lc << "_if_" << funname << " " + << params << ";" << endl; + } + f_header_ << endl; + + // Generate the client object instance definition in the header. + f_header_ << "/* " << service_name_ << " service client */" << endl << "struct _" << this->nspace + << service_name_ << "Client" << endl << "{" << endl << " " << parent_class_name + << " parent;" << endl; + if (!extends_service) { + // Define "input_protocol" and "output_protocol" properties only + // for base services; child service-client classes will inherit + // these + f_header_ << endl << " ThriftProtocol *input_protocol;" << endl + << " ThriftProtocol *output_protocol;" << endl; + } + f_header_ << "};" << endl << "typedef struct _" << this->nspace << service_name_ << "Client " + << this->nspace << service_name_ << "Client;" << endl << endl; + + // Generate the class definition in the header. + f_header_ << "struct _" << this->nspace << service_name_ << "ClientClass" << endl << "{" << endl + << " " << parent_class_name << "Class parent;" << endl << "};" << endl + << "typedef struct _" << this->nspace << service_name_ << "ClientClass " << this->nspace + << service_name_ << "ClientClass;" << endl << endl; + + // Create all the GObject boilerplate + f_header_ << "GType " << this->nspace_lc << service_name_lc << "_client_get_type (void);" << endl + << "#define " << this->nspace_uc << "TYPE_" << service_name_uc << "_CLIENT " + << "(" << this->nspace_lc << service_name_lc << "_client_get_type())" << endl + << "#define " << this->nspace_uc << service_name_uc << "_CLIENT(obj) " + << "(G_TYPE_CHECK_INSTANCE_CAST ((obj), " << this->nspace_uc << "TYPE_" + << service_name_uc << "_CLIENT, " << this->nspace << service_name_ << "Client))" << endl + << "#define " << this->nspace_uc << service_name_uc << "_CLIENT_CLASS(c) " + << "(G_TYPE_CHECK_CLASS_CAST ((c), " << this->nspace_uc << "TYPE_" << service_name_uc + << "_CLIENT, " << this->nspace << service_name_ << "ClientClass))" << endl << "#define " + << this->nspace_uc << service_name_uc << "_IS_CLIENT(obj) " + << "(G_TYPE_CHECK_INSTANCE_TYPE ((obj), " << this->nspace_uc << "TYPE_" + << service_name_uc << "_CLIENT))" << endl << "#define " << this->nspace_uc + << service_name_uc << "_IS_CLIENT_CLASS(c) " + << "(G_TYPE_CHECK_CLASS_TYPE ((c), " << this->nspace_uc << "TYPE_" << service_name_uc + << "_CLIENT))" << endl << "#define " << this->nspace_uc << service_name_uc + << "_CLIENT_GET_CLASS(obj) " + << "(G_TYPE_INSTANCE_GET_CLASS ((obj), " << this->nspace_uc << "TYPE_" + << service_name_uc << "_CLIENT, " << this->nspace << service_name_ << "ClientClass))" + << endl << endl; + + /* write out the function prototypes */ + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + /* make the function name C friendly */ + string funname = to_lower_case(initial_caps_to_underscores((*f_iter)->get_name())); + + t_function service_function((*f_iter)->get_returntype(), + service_name_lc + string("_client_") + funname, + (*f_iter)->get_arglist(), + (*f_iter)->get_xceptions()); + indent(f_header_) << function_signature(&service_function) << ";" << endl; + + t_function send_function(g_type_void, + service_name_lc + string("_client_send_") + funname, + (*f_iter)->get_arglist()); + indent(f_header_) << function_signature(&send_function) << ";" << endl; + + // implement recv if not a oneway service + if (!(*f_iter)->is_oneway()) { + t_struct noargs(program_); + t_function recv_function((*f_iter)->get_returntype(), + service_name_lc + string("_client_recv_") + funname, + &noargs, + (*f_iter)->get_xceptions()); + indent(f_header_) << function_signature(&recv_function) << ";" << endl; + } + } + + /* write out the get/set function prototypes */ + f_header_ << "void " + service_name_lc + "_client_set_property (GObject *object, guint " + "property_id, const GValue *value, GParamSpec *pspec);" + << endl; + f_header_ << "void " + service_name_lc + "_client_get_property (GObject *object, guint " + "property_id, GValue *value, GParamSpec *pspec);" + << endl; + + f_header_ << endl; + // end of header code + + // Generate interface method implementations + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + /* make the function name C friendly */ + string funname = initial_caps_to_underscores((*f_iter)->get_name()); + t_type* ttype = (*f_iter)->get_returntype(); + t_struct* arglist = (*f_iter)->get_arglist(); + t_struct* xlist = (*f_iter)->get_xceptions(); + bool has_return = !ttype->is_void(); + bool has_args = arglist->get_members().size() == 0; + bool has_xceptions = xlist->get_members().size() == 0; + + string params = "(" + this->nspace + service_name_ + "If *iface" + + (has_return ? ", " + type_name(ttype) + "* _return" : "") + + (has_args ? "" : (", " + argument_list(arglist))) + + (has_xceptions ? "" : (", " + xception_list(xlist))) + ", GError **error)"; + + string params_without_type = string("iface, ") + (has_return ? "_return, " : ""); + + const vector<t_field*>& fields = arglist->get_members(); + vector<t_field*>::const_iterator f_iter_field; + for (f_iter_field = fields.begin(); f_iter_field != fields.end(); ++f_iter_field) { + params_without_type += (*f_iter_field)->get_name(); + params_without_type += ", "; + } + + const vector<t_field*>& xceptions = xlist->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + params_without_type += (*x_iter)->get_name(); + params_without_type += ", "; + } + + f_service_ << "gboolean" << endl << this->nspace_lc << service_name_lc << "_if_" << funname + << " " << params << endl << "{" << endl << " return " << this->nspace_uc + << service_name_uc << "_IF_GET_INTERFACE (iface)->" << funname << " (" + << params_without_type << "error);" << endl << "}" << endl << endl; + } + + // Generate interface boilerplate + f_service_ << "GType" << endl << this->nspace_lc << service_name_lc << "_if_get_type (void)" + << endl << "{" << endl << " static GType type = 0;" << endl << " if (type == 0)" + << endl << " {" << endl << " static const GTypeInfo type_info =" << endl << " {" + << endl << " sizeof (" << this->nspace << service_name_ << "IfInterface)," << endl + << " NULL, /* base_init */" << endl << " NULL, /* base_finalize */" << endl + << " NULL, /* class_init */" << endl << " NULL, /* class_finalize */" + << endl << " NULL, /* class_data */" << endl + << " 0, /* instance_size */" << endl << " 0, /* n_preallocs */" + << endl << " NULL, /* instance_init */" << endl + << " NULL /* value_table */" << endl << " };" << endl + << " type = g_type_register_static (G_TYPE_INTERFACE," << endl + << " \"" << this->nspace << service_name_ << "If\"," + << endl << " &type_info, 0);" << endl << " }" + << endl << " return type;" << endl << "}" << endl << endl; + + // Generate client boilerplate + f_service_ << "static void " << endl << this->nspace_lc << service_name_lc + << "_if_interface_init (" << this->nspace << service_name_ << "IfInterface *iface);" + << endl << endl << "G_DEFINE_TYPE_WITH_CODE (" << this->nspace << service_name_ + << "Client, " << this->nspace_lc << service_name_lc << "_client," << endl + << " " << parent_type_name << ", " << endl + << " G_IMPLEMENT_INTERFACE (" << this->nspace_uc << "TYPE_" + << service_name_uc << "_IF," << endl + << " " << this->nspace_lc + << service_name_lc << "_if_interface_init))" << endl << endl; + + // Generate property-related code only for base services---child + // service-client classes have only properties inherited from their + // parent class + if (!extends_service) { + // Generate client properties + f_service_ << "enum _" << this->nspace << service_name_ << "ClientProperties" << endl << "{" + << endl << " PROP_0," << endl << " PROP_" << this->nspace_uc << service_name_uc + << "_CLIENT_INPUT_PROTOCOL," << endl << " PROP_" << this->nspace_uc + << service_name_uc << "_CLIENT_OUTPUT_PROTOCOL" << endl << "};" << endl << endl; + + // generate property setter + f_service_ << "void" << endl << this->nspace_lc << service_name_lc << "_client_set_property (" + << "GObject *object, guint property_id, const GValue *value, " + << "GParamSpec *pspec)" << endl << "{" << endl << " " << this->nspace + << service_name_ << "Client *client = " << this->nspace_uc << service_name_uc + << "_CLIENT (object);" << endl << endl << " THRIFT_UNUSED_VAR (pspec);" << endl + << endl << " switch (property_id)" << endl << " {" << endl << " case PROP_" + << this->nspace_uc << service_name_uc << "_CLIENT_INPUT_PROTOCOL:" << endl + << " client->input_protocol = g_value_get_object (value);" << endl + << " break;" << endl << " case PROP_" << this->nspace_uc << service_name_uc + << "_CLIENT_OUTPUT_PROTOCOL:" << endl + << " client->output_protocol = g_value_get_object (value);" << endl + << " break;" << endl << " }" << endl << "}" << endl << endl; + + // generate property getter + f_service_ << "void" << endl << this->nspace_lc << service_name_lc << "_client_get_property (" + << "GObject *object, guint property_id, GValue *value, " + << "GParamSpec *pspec)" << endl << "{" << endl << " " << this->nspace + << service_name_ << "Client *client = " << this->nspace_uc << service_name_uc + << "_CLIENT (object);" << endl << endl << " THRIFT_UNUSED_VAR (pspec);" << endl + << endl << " switch (property_id)" << endl << " {" << endl << " case PROP_" + << this->nspace_uc << service_name_uc << "_CLIENT_INPUT_PROTOCOL:" << endl + << " g_value_set_object (value, client->input_protocol);" << endl + << " break;" << endl << " case PROP_" << this->nspace_uc << service_name_uc + << "_CLIENT_OUTPUT_PROTOCOL:" << endl + << " g_value_set_object (value, client->output_protocol);" << endl + << " break;" << endl << " }" << endl << "}" << endl << endl; + } + + // Generate client method implementations + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + string name = (*f_iter)->get_name(); + string funname = initial_caps_to_underscores(name); + + // Get the struct of function call params and exceptions + t_struct* arg_struct = (*f_iter)->get_arglist(); + + // Function for sending + t_function send_function(g_type_void, + service_name_lc + string("_client_send_") + funname, + (*f_iter)->get_arglist()); + + // Open the send function + indent(f_service_) << function_signature(&send_function) << endl; + scope_up(f_service_); + + string reqType = (*f_iter)->is_oneway() ? "T_ONEWAY" : "T_CALL"; + + // Serialize the request + f_service_ << indent() << "gint32 cseqid = 0;" << endl << indent() + << "ThriftProtocol * protocol = " << this->nspace_uc << base_service_name_uc + << "_CLIENT (iface)->output_protocol;" << endl << endl << indent() + << "if (thrift_protocol_write_message_begin (protocol, \"" << name << "\", " + << reqType << ", cseqid, error) < 0)" << endl << indent() << " return FALSE;" + << endl << endl; + + generate_struct_writer(f_service_, arg_struct, "", "", false); + + f_service_ << indent() << "if (thrift_protocol_write_message_end (protocol, error) < 0)" << endl + << indent() << " return FALSE;" << endl << indent() + << "if (!thrift_transport_flush (protocol->transport, error))" << endl << indent() + << " return FALSE;" << endl << indent() + << "if (!thrift_transport_write_end (protocol->transport, error))" << endl + << indent() << " return FALSE;" << endl << endl << indent() << "return TRUE;" + << endl; + + scope_down(f_service_); + f_service_ << endl; + + // Generate recv function only if not an async function + if (!(*f_iter)->is_oneway()) { + t_struct noargs(program_); + t_function recv_function((*f_iter)->get_returntype(), + service_name_lc + string("_client_recv_") + funname, + &noargs, + (*f_iter)->get_xceptions()); + // Open function + indent(f_service_) << function_signature(&recv_function) << endl; + scope_up(f_service_); + + f_service_ << indent() << "gint32 rseqid;" << endl + << indent() << "gchar * fname = NULL;" << endl + << indent() << "ThriftMessageType mtype;" << endl + << indent() << "ThriftProtocol * protocol = " + << this->nspace_uc << base_service_name_uc + << "_CLIENT (iface)->input_protocol;" << endl + << indent() << "ThriftApplicationException *xception;" << endl + << endl + << indent() << "if (thrift_protocol_read_message_begin " + "(protocol, &fname, &mtype, &rseqid, error) < 0) {" << endl; + indent_up(); + f_service_ << indent() << "if (fname) g_free (fname);" << endl + << indent() << "return FALSE;" << endl; + indent_down(); + f_service_ << indent() << "}" << endl + << endl + << indent() << "if (mtype == T_EXCEPTION) {" << endl; + indent_up(); + f_service_ << indent() << "if (fname) g_free (fname);" << endl + << indent() << "xception = g_object_new " + "(THRIFT_TYPE_APPLICATION_EXCEPTION, NULL);" << endl + << indent() << "thrift_struct_read (THRIFT_STRUCT (xception), " + "protocol, NULL);" << endl + << indent() << "thrift_protocol_read_message_end " + "(protocol, NULL);" << endl + << indent() << "thrift_transport_read_end " + "(protocol->transport, NULL);" << endl + << indent() << "g_set_error (error, " + "THRIFT_APPLICATION_EXCEPTION_ERROR,xception->type, " + "\"application error: %s\", xception->message);" << endl + << indent() << "g_object_unref (xception);" << endl + << indent() << "return FALSE;" << endl; + indent_down(); + f_service_ << indent() << "} else if (mtype != T_REPLY) {" << endl; + indent_up(); + f_service_ << indent() << "if (fname) g_free (fname);" << endl + << indent() << "thrift_protocol_skip (protocol, T_STRUCT, " + "NULL);" << endl + << indent() << "thrift_protocol_read_message_end (protocol, " + "NULL);" << endl + << indent() << "thrift_transport_read_end (" + "protocol->transport, NULL);" << endl + << indent() << "g_set_error (error, " + "THRIFT_APPLICATION_EXCEPTION_ERROR, " + "THRIFT_APPLICATION_EXCEPTION_ERROR_INVALID_MESSAGE_TYPE, " + "\"invalid message type %d, expected T_REPLY\", mtype);" + << endl + << indent() << "return FALSE;" << endl; + indent_down(); + f_service_ << indent() << "} else if (strncmp (fname, \"" << name + << "\", " << name.length() << ") != 0) {" << endl; + indent_up(); + f_service_ << indent() << "thrift_protocol_skip (protocol, T_STRUCT, " + "NULL);" << endl + << indent() << "thrift_protocol_read_message_end (protocol," + "error);" << endl + << indent() << "thrift_transport_read_end (" + "protocol->transport, error);" << endl + << indent() << "g_set_error (error, " + "THRIFT_APPLICATION_EXCEPTION_ERROR, " + "THRIFT_APPLICATION_EXCEPTION_ERROR_WRONG_METHOD_NAME, " + "\"wrong method name %s, expected " << name + << "\", fname);" << endl + << indent() << "if (fname) g_free (fname);" << endl + << indent() << "return FALSE;" << endl; + indent_down(); + f_service_ << indent() << "}" << endl + << indent() << "if (fname) g_free (fname);" << endl + << endl; + + t_struct* xs = (*f_iter)->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + + { + t_struct result(program_, tservice->get_name() + "_" + (*f_iter)->get_name() + "_result"); + t_field success((*f_iter)->get_returntype(), "*_return", 0); + if (!(*f_iter)->get_returntype()->is_void()) { + result.append(&success); + } + + // add readers for exceptions, dereferencing the pointer. + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); x_iter++) { + t_field* xception = new t_field((*x_iter)->get_type(), + "*" + (*x_iter)->get_name(), + (*x_iter)->get_key()); + result.append(xception); + } + + generate_struct_reader(f_service_, &result, "", "", false); + } + + f_service_ << indent() << "if (thrift_protocol_read_message_end (protocol, error) < 0)" + << endl << indent() << " return FALSE;" << endl << endl << indent() + << "if (!thrift_transport_read_end (protocol->transport, error))" << endl + << indent() << " return FALSE;" << endl << endl; + + // copy over any throw exceptions and return failure + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); x_iter++) { + f_service_ << indent() << "if (*" << (*x_iter)->get_name() << " != NULL)" << endl + << indent() << "{" << endl << indent() << " g_set_error (error, " + << this->nspace_uc + << to_upper_case(initial_caps_to_underscores((*x_iter)->get_type()->get_name())) + << "_ERROR, " << this->nspace_uc + << to_upper_case(initial_caps_to_underscores((*x_iter)->get_type()->get_name())) + << "_ERROR_CODE, \"" << (*x_iter)->get_type()->get_name() << "\");" << endl + << indent() << " return FALSE;" << endl << indent() << "}" << endl; + } + // Close function + indent(f_service_) << "return TRUE;" << endl; + scope_down(f_service_); + f_service_ << endl; + } + + // Open function + t_function service_function((*f_iter)->get_returntype(), + service_name_lc + string("_client_") + funname, + (*f_iter)->get_arglist(), + (*f_iter)->get_xceptions()); + indent(f_service_) << function_signature(&service_function) << endl; + scope_up(f_service_); + + // wrap each function + f_service_ << indent() << "if (!" << this->nspace_lc << service_name_lc << "_client_send_" + << funname << " (iface"; + + // Declare the function arguments + const vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator fld_iter; + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + f_service_ << ", " << (*fld_iter)->get_name(); + } + f_service_ << ", error))" << endl << indent() << " return FALSE;" << endl; + + // if not oneway, implement recv + if (!(*f_iter)->is_oneway()) { + string ret = (*f_iter)->get_returntype()->is_void() ? "" : "_return, "; + + const vector<t_field*>& xceptions = (*f_iter)->get_xceptions()->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + ret += (*x_iter)->get_name(); + ret += ", "; + } + + f_service_ << indent() << "if (!" << this->nspace_lc << service_name_lc << "_client_recv_" + << funname << " (iface, " << ret << "error))" << endl << indent() + << " return FALSE;" << endl; + } + + // return TRUE which means all functions were called OK + indent(f_service_) << "return TRUE;" << endl; + scope_down(f_service_); + f_service_ << endl; + } + + // create the interface initializer + f_service_ << "static void" << endl + << this->nspace_lc << service_name_lc << "_if_interface_init (" + << this->nspace << service_name_ << "IfInterface *iface)" << endl; + scope_up(f_service_); + if (functions.size() > 0) { + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + /* make the function name C friendly */ + string funname = initial_caps_to_underscores((*f_iter)->get_name()); + + f_service_ << indent() << "iface->" << funname << " = " << this->nspace_lc + << service_name_lc << "_client_" << funname << ";" << endl; + } + } + else { + f_service_ << indent() << "THRIFT_UNUSED_VAR (iface);" << endl; + } + scope_down(f_service_); + f_service_ << endl; + + // create the client instance initializer + f_service_ << "static void" << endl + << this->nspace_lc << service_name_lc << "_client_init (" + << this->nspace << service_name_ << "Client *client)" << endl; + scope_up(f_service_); + if (!extends_service) { + f_service_ << indent() << "client->input_protocol = NULL;" << endl + << indent() << "client->output_protocol = NULL;" << endl; + } + else { + f_service_ << indent() << "THRIFT_UNUSED_VAR (client);" << endl; + } + scope_down(f_service_); + f_service_ << endl; + + // create the client class initializer + f_service_ << "static void" << endl << this->nspace_lc << service_name_lc + << "_client_class_init (" << this->nspace << service_name_ << "ClientClass *cls)" + << endl << "{" << endl; + if (!extends_service) { + f_service_ << " GObjectClass *gobject_class = G_OBJECT_CLASS (cls);" << endl + << " GParamSpec *param_spec;" << endl << endl + << " gobject_class->set_property = " << this->nspace_lc << service_name_lc + << "_client_set_property;" << endl + << " gobject_class->get_property = " << this->nspace_lc << service_name_lc + << "_client_get_property;" << endl << endl + << " param_spec = g_param_spec_object (\"input_protocol\"," << endl + << " \"input protocol (construct)\"," << endl + << " \"Set the client input protocol\"," << endl + << " THRIFT_TYPE_PROTOCOL," << endl + << " G_PARAM_READWRITE);" << endl + << " g_object_class_install_property (gobject_class," << endl + << " PROP_" << this->nspace_uc << service_name_uc + << "_CLIENT_INPUT_PROTOCOL, param_spec);" << endl << endl + << " param_spec = g_param_spec_object (\"output_protocol\"," << endl + << " \"output protocol (construct)\"," << endl + << " \"Set the client output protocol\"," << endl + << " THRIFT_TYPE_PROTOCOL," << endl + << " G_PARAM_READWRITE);" << endl + << " g_object_class_install_property (gobject_class," << endl + << " PROP_" << this->nspace_uc << service_name_uc + << "_CLIENT_OUTPUT_PROTOCOL, param_spec);" << endl; + } + else { + f_service_ << " THRIFT_UNUSED_VAR (cls);" << endl; + } + f_service_ << "}" << endl << endl; +} + +/** + * Generates C code that represents a Thrift service handler. + * + * @param tservice The service for which to generate a handler. + */ +void t_c_glib_generator::generate_service_handler(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator function_iter; + + string service_name_lc = to_lower_case(initial_caps_to_underscores(service_name_)); + string service_name_uc = to_upper_case(service_name_lc); + + string service_handler_name = service_name_ + "Handler"; + + string class_name = this->nspace + service_handler_name; + string class_name_lc = this->nspace_lc + initial_caps_to_underscores(service_handler_name); + string class_name_uc = to_upper_case(class_name_lc); + + string parent_class_name; + string parent_type_name; + + string args_indent; + + // The service this service extends, or NULL if it extends no service + t_service* extends_service = tservice->get_extends(); + + // Determine the name of our parent service (if any) and the handler class' + // parent class name and type + if (extends_service) { + string parent_service_name = extends_service->get_name(); + string parent_service_name_lc = to_lower_case(initial_caps_to_underscores(parent_service_name)); + string parent_service_name_uc = to_upper_case(parent_service_name_lc); + + parent_class_name = this->nspace + parent_service_name + "Handler"; + parent_type_name = this->nspace_uc + "TYPE_" + parent_service_name_uc + "_HANDLER"; + } else { + parent_class_name = "GObject"; + parent_type_name = "G_TYPE_OBJECT"; + } + + // Generate the handler class' definition in the header file + + // Generate the handler instance definition + f_header_ << "/* " << service_name_ << " handler (abstract base class) */" << endl << "struct _" + << class_name << endl << "{" << endl; + indent_up(); + f_header_ << indent() << parent_class_name << " parent;" << endl; + indent_down(); + f_header_ << "};" << endl << "typedef struct _" << class_name << " " << class_name << ";" << endl + << endl; + + // Generate the handler class definition, including its class members + // (methods) + f_header_ << "struct _" << class_name << "Class" << endl << "{" << endl; + indent_up(); + f_header_ << indent() << parent_class_name << "Class parent;" << endl << endl; + + for (function_iter = functions.begin(); function_iter != functions.end(); ++function_iter) { + string method_name = initial_caps_to_underscores((*function_iter)->get_name()); + t_type* return_type = (*function_iter)->get_returntype(); + t_struct* arg_list = (*function_iter)->get_arglist(); + t_struct* x_list = (*function_iter)->get_xceptions(); + bool has_return = !return_type->is_void(); + bool has_args = arg_list->get_members().size() == 0; + bool has_xceptions = x_list->get_members().size() == 0; + + string params = "(" + this->nspace + service_name_ + "If *iface" + + (has_return ? ", " + type_name(return_type) + "* _return" : "") + + (has_args ? "" : (", " + argument_list(arg_list))) + + (has_xceptions ? "" : (", " + xception_list(x_list))) + ", GError **error)"; + + indent(f_header_) << "gboolean (*" << method_name << ") " << params << ";" << endl; + } + indent_down(); + + f_header_ << "};" << endl << "typedef struct _" << class_name << "Class " << class_name + << "Class;" << endl << endl; + + // Generate the remaining header boilerplate + f_header_ << "GType " << class_name_lc << "_get_type (void);" << endl << "#define " + << this->nspace_uc << "TYPE_" << service_name_uc << "_HANDLER " + << "(" << class_name_lc << "_get_type())" << endl << "#define " << class_name_uc + << "(obj) " + << "(G_TYPE_CHECK_INSTANCE_CAST ((obj), " << this->nspace_uc << "TYPE_" + << service_name_uc << "_HANDLER, " << class_name << "))" << endl << "#define " + << this->nspace_uc << "IS_" << service_name_uc << "_HANDLER(obj) " + << "(G_TYPE_CHECK_INSTANCE_TYPE ((obj), " << this->nspace_uc << "TYPE_" + << service_name_uc << "_HANDLER))" << endl << "#define " << class_name_uc + << "_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), " << this->nspace_uc << "TYPE_" + << service_name_uc << "_HANDLER, " << class_name << "Class))" << endl << "#define " + << this->nspace_uc << "IS_" << service_name_uc << "_HANDLER_CLASS(c) " + << "(G_TYPE_CHECK_CLASS_TYPE ((c), " << this->nspace_uc << "TYPE_" << service_name_uc + << "_HANDLER))" << endl << "#define " << this->nspace_uc << service_name_uc + << "_HANDLER_GET_CLASS(obj) " + << "(G_TYPE_INSTANCE_GET_CLASS ((obj), " << this->nspace_uc << "TYPE_" + << service_name_uc << "_HANDLER, " << class_name << "Class))" << endl << endl; + + // Generate the handler class' method definitions + for (function_iter = functions.begin(); function_iter != functions.end(); ++function_iter) { + string method_name = initial_caps_to_underscores((*function_iter)->get_name()); + t_type* return_type = (*function_iter)->get_returntype(); + t_struct* arg_list = (*function_iter)->get_arglist(); + t_struct* x_list = (*function_iter)->get_xceptions(); + bool has_return = !return_type->is_void(); + bool has_args = arg_list->get_members().size() == 0; + bool has_xceptions = x_list->get_members().size() == 0; + + string params = "(" + this->nspace + service_name_ + "If *iface" + + (has_return ? ", " + type_name(return_type) + "* _return" : "") + + (has_args ? "" : (", " + argument_list(arg_list))) + + (has_xceptions ? "" : (", " + xception_list(x_list))) + ", GError **error)"; + + f_header_ << "gboolean " << class_name_lc << "_" << method_name << " " << params << ";" << endl; + } + f_header_ << endl; + + // Generate the handler's implementation in the implementation file + + // Generate the implementation boilerplate + f_service_ << "static void" << endl << class_name_lc << "_" << service_name_lc + << "_if_interface_init (" << this->nspace << service_name_ << "IfInterface *iface);" + << endl << endl; + + args_indent = string(25, ' '); + f_service_ << "G_DEFINE_TYPE_WITH_CODE (" << class_name << ", " << endl << args_indent + << class_name_lc << "," << endl << args_indent << parent_type_name << "," << endl + << args_indent << "G_IMPLEMENT_INTERFACE (" << this->nspace_uc << "TYPE_" + << service_name_uc << "_IF," << endl; + args_indent += string(23, ' '); + f_service_ << args_indent << class_name_lc << "_" << service_name_lc << "_if_interface_init))" + << endl << endl; + + // Generate the handler method implementations + for (function_iter = functions.begin(); function_iter != functions.end(); ++function_iter) { + string function_name = (*function_iter)->get_name(); + string method_name = initial_caps_to_underscores(function_name); + t_type* return_type = (*function_iter)->get_returntype(); + t_struct* arg_list = (*function_iter)->get_arglist(); + t_struct* x_list = (*function_iter)->get_xceptions(); + + const vector<t_field*>& args = arg_list->get_members(); + const vector<t_field*>& xceptions = x_list->get_members(); + + vector<t_field*>::const_iterator field_iter; + + t_function implementing_function(return_type, + service_name_lc + "_handler_" + method_name, + arg_list, + x_list, + (*function_iter)->is_oneway()); + + indent(f_service_) << function_signature(&implementing_function) << endl; + scope_up(f_service_); + f_service_ << indent() << "g_return_val_if_fail (" << this->nspace_uc << "IS_" + << service_name_uc << "_HANDLER (iface), FALSE);" << endl << endl << indent() + << "return " << class_name_uc << "_GET_CLASS (iface)" + << "->" << method_name << " (iface, "; + + if (!return_type->is_void()) { + f_service_ << "_return, "; + } + for (field_iter = args.begin(); field_iter != args.end(); ++field_iter) { + f_service_ << (*field_iter)->get_name() << ", "; + } + for (field_iter = xceptions.begin(); field_iter != xceptions.end(); ++field_iter) { + f_service_ << (*field_iter)->get_name() << ", "; + } + f_service_ << "error);" << endl; + scope_down(f_service_); + f_service_ << endl; + } + + // Generate the handler interface initializer + f_service_ << "static void" << endl << class_name_lc << "_" << service_name_lc + << "_if_interface_init (" << this->nspace << service_name_ << "IfInterface *iface)" + << endl; + scope_up(f_service_); + if (functions.size() > 0) { + for (function_iter = functions.begin(); function_iter != functions.end(); ++function_iter) { + string method_name = initial_caps_to_underscores((*function_iter)->get_name()); + + f_service_ << indent() << "iface->" << method_name << " = " << class_name_lc << "_" + << method_name << ";" << endl; + } + } + else { + f_service_ << "THRIFT_UNUSED_VAR (iface);" << endl; + } + scope_down(f_service_); + f_service_ << endl; + + // Generate the handler instance initializer + f_service_ << "static void" << endl << class_name_lc << "_init (" << class_name << " *self)" + << endl; + scope_up(f_service_); + f_service_ << indent() << "THRIFT_UNUSED_VAR (self);" << endl; + scope_down(f_service_); + f_service_ << endl; + + // Generate the handler class initializer + f_service_ << "static void" << endl + << class_name_lc << "_class_init (" << class_name << "Class *cls)" + << endl; + scope_up(f_service_); + if (functions.size() > 0) { + for (function_iter = functions.begin(); + function_iter != functions.end(); + ++function_iter) { + string function_name = (*function_iter)->get_name(); + string method_name = initial_caps_to_underscores(function_name); + + // All methods are pure virtual and must be implemented by subclasses + f_service_ << indent() << "cls->" << method_name << " = NULL;" << endl; + } + } + else { + f_service_ << indent() << "THRIFT_UNUSED_VAR (cls);" << endl; + } + scope_down(f_service_); + f_service_ << endl; +} + +/** + * Generates C code that represents a Thrift service processor. + * + * @param tservice The service for which to generate a processor + */ +void t_c_glib_generator::generate_service_processor(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator function_iter; + + string service_name_lc = to_lower_case(initial_caps_to_underscores(service_name_)); + string service_name_uc = to_upper_case(service_name_lc); + + string service_processor_name = service_name_ + "Processor"; + + string class_name = this->nspace + service_processor_name; + string class_name_lc = this->nspace_lc + initial_caps_to_underscores(service_processor_name); + string class_name_uc = to_upper_case(class_name_lc); + + string parent_class_name; + string parent_type_name; + + string handler_class_name = this->nspace + service_name_ + "Handler"; + string handler_class_name_lc = initial_caps_to_underscores(handler_class_name); + + string process_function_type_name = class_name + "ProcessFunction"; + string process_function_def_type_name = + class_name_lc + "_process_function_def"; + + string function_name; + string args_indent; + + // The service this service extends, or NULL if it extends no service + t_service* extends_service = tservice->get_extends(); + + // Determine the name of our parent service (if any) and the + // processor class' parent class name and type + if (extends_service) { + string parent_service_name = extends_service->get_name(); + string parent_service_name_lc = to_lower_case(initial_caps_to_underscores(parent_service_name)); + string parent_service_name_uc = to_upper_case(parent_service_name_lc); + + parent_class_name = this->nspace + parent_service_name + "Processor"; + parent_type_name = this->nspace_uc + "TYPE_" + parent_service_name_uc + "_PROCESSOR"; + } else { + parent_class_name = "ThriftDispatchProcessor"; + parent_type_name = "THRIFT_TYPE_DISPATCH_PROCESSOR"; + } + + // Generate the processor class' definition in the header file + + // Generate the processor instance definition + f_header_ << "/* " << service_name_ << " processor */" << endl << "struct _" << class_name << endl + << "{" << endl; + indent_up(); + f_header_ << indent() << parent_class_name << " parent;" << endl << endl << indent() + << "/* protected */" << endl << indent() + << this->nspace + service_name_ + "Handler *handler;" << endl << indent() + << "GHashTable *process_map;" << endl; + indent_down(); + f_header_ << "};" << endl << "typedef struct _" << class_name << " " << class_name << ";" << endl + << endl; + + // Generate the processor class definition + f_header_ << "struct _" << class_name << "Class" << endl << "{" << endl; + indent_up(); + f_header_ << indent() << parent_class_name << "Class parent;" << endl << endl << indent() + << "/* protected */" << endl << indent() + << "gboolean (*dispatch_call) (ThriftDispatchProcessor *processor," << endl; + args_indent = indent() + string(27, ' '); + f_header_ << args_indent << "ThriftProtocol *in," << endl << args_indent << "ThriftProtocol *out," + << endl << args_indent << "gchar *fname," << endl << args_indent << "gint32 seqid," + << endl << args_indent << "GError **error);" << endl; + indent_down(); + f_header_ << "};" << endl << "typedef struct _" << class_name << "Class " << class_name + << "Class;" << endl << endl; + + // Generate the remaining header boilerplate + f_header_ << "GType " << class_name_lc << "_get_type (void);" << endl << "#define " + << this->nspace_uc << "TYPE_" << service_name_uc << "_PROCESSOR " + << "(" << class_name_lc << "_get_type())" << endl << "#define " << class_name_uc + << "(obj) " + << "(G_TYPE_CHECK_INSTANCE_CAST ((obj), " << this->nspace_uc << "TYPE_" + << service_name_uc << "_PROCESSOR, " << class_name << "))" << endl << "#define " + << this->nspace_uc << "IS_" << service_name_uc << "_PROCESSOR(obj) " + << "(G_TYPE_CHECK_INSTANCE_TYPE ((obj), " << this->nspace_uc << "TYPE_" + << service_name_uc << "_PROCESSOR))" << endl << "#define " << class_name_uc + << "_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), " << this->nspace_uc << "TYPE_" + << service_name_uc << "_PROCESSOR, " << class_name << "Class))" << endl << "#define " + << this->nspace_uc << "IS_" << service_name_uc << "_PROCESSOR_CLASS(c) " + << "(G_TYPE_CHECK_CLASS_TYPE ((c), " << this->nspace_uc << "TYPE_" << service_name_uc + << "_PROCESSOR))" << endl << "#define " << this->nspace_uc << service_name_uc + << "_PROCESSOR_GET_CLASS(obj) " + << "(G_TYPE_INSTANCE_GET_CLASS ((obj), " << this->nspace_uc << "TYPE_" + << service_name_uc << "_PROCESSOR, " << class_name << "Class))" << endl << endl; + + // Generate the processor's implementation in the implementation file + + // Generate the processor's properties enum + f_service_ << "enum _" << class_name << "Properties" << endl << "{" << endl; + indent_up(); + f_service_ << indent() << "PROP_" << class_name_uc << "_0," << endl << indent() << "PROP_" + << class_name_uc << "_HANDLER" << endl; + indent_down(); + f_service_ << "};" << endl << endl; + + // Generate the implementation boilerplate + args_indent = string(15, ' '); + f_service_ << "G_DEFINE_TYPE (" << class_name << "," << endl << args_indent << class_name_lc + << "," << endl << args_indent << parent_type_name << ")" << endl << endl; + + // Generate the processor's processing-function type + args_indent = string(process_function_type_name.length() + 23, ' '); + f_service_ << "typedef gboolean (* " << process_function_type_name << ") (" + << class_name << " *, " << endl + << args_indent << "gint32," << endl + << args_indent << "ThriftProtocol *," << endl + << args_indent << "ThriftProtocol *," << endl + << args_indent << "GError **);" << endl + << endl; + + // Generate the processor's processing-function-definition type + f_service_ << "typedef struct" << endl + << "{" << endl; + indent_up(); + f_service_ << indent() << "gchar *name;" << endl + << indent() << process_function_type_name << " function;" << endl; + indent_down(); + f_service_ << "} " << process_function_def_type_name << ";" << endl + << endl; + + // Generate forward declarations of the processor's processing functions so we + // can refer to them in the processing-function-definition struct below and + // keep all of the processor's declarations in one place + for (function_iter = functions.begin(); + function_iter != functions.end(); + ++function_iter) { + function_name = class_name_lc + "_process_" + + initial_caps_to_underscores((*function_iter)->get_name()); + + args_indent = string(function_name.length() + 2, ' '); + f_service_ << "static gboolean" << endl + << function_name << " (" + << class_name << " *," << endl + << args_indent << "gint32," << endl + << args_indent << "ThriftProtocol *," << endl + << args_indent << "ThriftProtocol *," << endl + << args_indent << "GError **);" << endl; + } + f_service_ << endl; + + // Generate the processor's processing-function definitions, if the service + // defines any methods + if (functions.size() > 0) { + f_service_ << indent() << "static " << process_function_def_type_name + << endl + << indent() << class_name_lc << "_process_function_defs[" + << functions.size() << "] = {" << endl; + indent_up(); + for (function_iter = functions.begin(); + function_iter != functions.end(); + ++function_iter) { + string service_function_name = (*function_iter)->get_name(); + string process_function_name = class_name_lc + "_process_" + + initial_caps_to_underscores(service_function_name); + + f_service_ << indent() << "{" << endl; + indent_up(); + f_service_ << indent() << "\"" << service_function_name << "\"," << endl + << indent() << process_function_name << endl; + indent_down(); + f_service_ << indent() << "}" + << (function_iter == --functions.end() ? "" : ",") << endl; + } + indent_down(); + f_service_ << indent() << "};" << endl + << endl; + } + + // Generate the processor's processing functions + for (function_iter = functions.begin(); function_iter != functions.end(); ++function_iter) { + string service_function_name = (*function_iter)->get_name(); + string service_function_name_ic = underscores_to_initial_caps(service_function_name); + string service_function_name_lc = initial_caps_to_underscores(service_function_name); + string service_function_name_uc = to_upper_case(service_function_name_lc); + + t_type* return_type = (*function_iter)->get_returntype(); + bool has_return_value = !return_type->is_void(); + + t_struct* arg_list = (*function_iter)->get_arglist(); + const vector<t_field*>& args = arg_list->get_members(); + vector<t_field*>::const_iterator arg_iter; + + const vector<t_field*>& xceptions = (*function_iter)->get_xceptions()->get_members(); + vector<t_field*>::const_iterator xception_iter; + + string args_class_name = this->nspace + service_name_ + service_function_name_ic + "Args"; + string args_class_type = this->nspace_uc + "TYPE_" + service_name_uc + "_" + + service_function_name_uc + "_ARGS"; + + string result_class_name = this->nspace + service_name_ + service_function_name_ic + "Result"; + string result_class_type = this->nspace_uc + "TYPE_" + service_name_uc + "_" + + service_function_name_uc + "_RESULT"; + + string handler_function_name = handler_class_name_lc + "_" + service_function_name_lc; + + function_name = class_name_lc + "_process_" + + initial_caps_to_underscores(service_function_name); + + args_indent = string(function_name.length() + 2, ' '); + f_service_ << "static gboolean" << endl << function_name << " (" << class_name << " *self," + << endl << args_indent << "gint32 sequence_id," << endl << args_indent + << "ThriftProtocol *input_protocol," << endl << args_indent + << "ThriftProtocol *output_protocol," << endl << args_indent << "GError **error)" + << endl; + scope_up(f_service_); + f_service_ << indent() << "gboolean result = TRUE;" << endl + << indent() << "ThriftTransport * transport;" << endl + << indent() << "ThriftApplicationException *xception;" << endl + << indent() << args_class_name + " * args =" << endl; + indent_up(); + f_service_ << indent() << "g_object_new (" << args_class_type << ", NULL);" << endl << endl; + indent_down(); + if ((*function_iter)->is_oneway()) { + f_service_ << indent() << "THRIFT_UNUSED_VAR (sequence_id);" << endl << indent() + << "THRIFT_UNUSED_VAR (output_protocol);" << endl << endl; + } + f_service_ << indent() << "g_object_get (input_protocol, \"transport\", " + << "&transport, NULL);" << endl << endl; + + // Read the method's arguments from the caller + f_service_ << indent() << "if ((thrift_struct_read (THRIFT_STRUCT (args), " + << "input_protocol, error) != -1) &&" << endl << indent() + << " (thrift_protocol_read_message_end (input_protocol, " + << "error) != -1) &&" << endl << indent() + << " (thrift_transport_read_end (transport, error) != FALSE))" << endl; + scope_up(f_service_); + + for (arg_iter = args.begin(); arg_iter != args.end(); ++arg_iter) { + f_service_ << indent() << property_type_name((*arg_iter)->get_type()) << " " + << (*arg_iter)->get_name() << ";" << endl; + } + for (xception_iter = xceptions.begin(); xception_iter != xceptions.end(); ++xception_iter) { + f_service_ << indent() << type_name((*xception_iter)->get_type()) << " " + << initial_caps_to_underscores((*xception_iter)->get_name()) << " = NULL;" << endl; + } + if (has_return_value) { + f_service_ << indent() << property_type_name(return_type) << " return_value;" << endl; + } + if (!(*function_iter)->is_oneway()) { + f_service_ << indent() << result_class_name << " * result_struct;" << endl; + } + f_service_ << endl; + + if (args.size() > 0) { + f_service_ << indent() << "g_object_get (args," << endl; + args_indent = indent() + string(14, ' '); + for (arg_iter = args.begin(); arg_iter != args.end(); ++arg_iter) { + string arg_name = (*arg_iter)->get_name(); + + f_service_ << args_indent << "\"" << arg_name << "\", &" << arg_name << "," << endl; + } + f_service_ << args_indent << "NULL);" << endl << endl; + } + + if (!(*function_iter)->is_oneway()) { + f_service_ << indent() << "g_object_unref (transport);" << endl << indent() + << "g_object_get (output_protocol, \"transport\", " + << "&transport, NULL);" << endl << endl << indent() + << "result_struct = g_object_new (" << result_class_type << ", NULL);" << endl; + if (has_return_value) { + f_service_ << indent() << "g_object_get (result_struct, " + "\"success\", &return_value, NULL);" << endl; + } + f_service_ << endl; + } + + // Pass the arguments to the corresponding method in the handler + f_service_ << indent() << "if (" << handler_function_name << " (" << this->nspace_uc + << service_name_uc << "_IF (self->handler)," << endl; + args_indent = indent() + string(handler_function_name.length() + 6, ' '); + if (has_return_value) { + string return_type_name = type_name(return_type); + + f_service_ << args_indent; + + // Cast return_value if it was declared as a type other than the return + // value's actual type---this is true for integer values 32 bits or fewer + // in width, for which GLib requires a plain gint type be used when + // storing or retrieving as an object property + if (return_type_name != property_type_name(return_type)) { + if (return_type_name[return_type_name.length() - 1] != '*') { + return_type_name += ' '; + } + return_type_name += '*'; + + f_service_ << "(" << return_type_name << ")"; + } + + f_service_ << "&return_value," << endl; + } + for (arg_iter = args.begin(); arg_iter != args.end(); ++arg_iter) { + f_service_ << args_indent << (*arg_iter)->get_name() << "," << endl; + } + for (xception_iter = xceptions.begin(); xception_iter != xceptions.end(); ++xception_iter) { + f_service_ << args_indent << "&" << initial_caps_to_underscores((*xception_iter)->get_name()) + << "," << endl; + } + f_service_ << args_indent << "error) == TRUE)" << endl; + scope_up(f_service_); + + // The handler reported success; return the result, if any, to the caller + if (!(*function_iter)->is_oneway()) { + if (has_return_value) { + f_service_ << indent() << "g_object_set (result_struct, \"success\", "; + if (type_name(return_type) != property_type_name(return_type)) { + // Roundtrip cast to fix the position of sign bit. + f_service_ << "(" << property_type_name(return_type) << ")" + << "(" << type_name(return_type) << ")"; + } + f_service_ << "return_value, " + << "NULL);" << endl; + + // Deallocate (or unref) return_value + return_type = get_true_type(return_type); + if (return_type->is_base_type()) { + t_base_type* base_type = ((t_base_type*)return_type); + + if (base_type->get_base() == t_base_type::TYPE_STRING) { + f_service_ << indent() << "if (return_value != NULL)" << endl; + indent_up(); + if (base_type->is_binary()) { + f_service_ << indent() << "g_byte_array_unref (return_value);" << endl; + } else { + f_service_ << indent() << "g_free (return_value);" << endl; + } + indent_down(); + } + } else if (return_type->is_container()) { + f_service_ << indent() << "if (return_value != NULL)" << endl; + indent_up(); + + if (return_type->is_list()) { + t_type* elem_type = ((t_list*)return_type)->get_elem_type(); + + f_service_ << indent(); + if (is_numeric(elem_type)) { + f_service_ << "g_array_unref"; + } else { + f_service_ << "g_ptr_array_unref"; + } + f_service_ << " (return_value);" << endl; + } else if (return_type->is_map() || return_type->is_set()) { + f_service_ << indent() << "g_hash_table_unref (return_value);" << endl; + } + + indent_down(); + } else if (return_type->is_struct()) { + f_service_ << indent() << "if (return_value != NULL)" << endl; + indent_up(); + f_service_ << indent() << "g_object_unref (return_value);" << endl; + indent_down(); + } + + f_service_ << endl; + } + f_service_ << indent() << "result =" << endl; + indent_up(); + f_service_ << indent() << "((thrift_protocol_write_message_begin (output_protocol," << endl; + args_indent = indent() + string(39, ' '); + f_service_ << args_indent << "\"" << service_function_name << "\"," << endl << args_indent + << "T_REPLY," << endl << args_indent << "sequence_id," << endl << args_indent + << "error) != -1) &&" << endl << indent() + << " (thrift_struct_write (THRIFT_STRUCT (result_struct)," << endl; + args_indent = indent() + string(23, ' '); + f_service_ << args_indent << "output_protocol," << endl << args_indent << "error) != -1));" + << endl; + indent_down(); + } + scope_down(f_service_); + f_service_ << indent() << "else" << endl; + scope_up(f_service_); + + // The handler reported failure; check to see if an application-defined + // exception was raised and if so, return it to the caller + f_service_ << indent(); + if (xceptions.size() > 0) { + for (xception_iter = xceptions.begin(); xception_iter != xceptions.end(); ++xception_iter) { + f_service_ << "if (" << initial_caps_to_underscores((*xception_iter)->get_name()) + << " != NULL)" << endl; + scope_up(f_service_); + f_service_ << indent() << "g_object_set (result_struct," << endl; + args_indent = indent() + string(14, ' '); + f_service_ << args_indent << "\"" << (*xception_iter)->get_name() << "\", " + << (*xception_iter)->get_name() << "," << endl << args_indent << "NULL);" << endl + << endl; + f_service_ << indent() << "result =" << endl; + indent_up(); + f_service_ << indent() << "((thrift_protocol_write_message_begin (output_protocol," << endl; + args_indent = indent() + string(39, ' '); + f_service_ << args_indent << "\"" << service_function_name << "\"," << endl << args_indent + << "T_REPLY," << endl << args_indent << "sequence_id," << endl << args_indent + << "error) != -1) &&" << endl << indent() + << " (thrift_struct_write (THRIFT_STRUCT (result_struct)," << endl; + args_indent = indent() + string(23, ' '); + f_service_ << args_indent << "output_protocol," << endl << args_indent << "error) != -1));" + << endl; + indent_down(); + scope_down(f_service_); + f_service_ << indent() << "else" << endl; + } + + scope_up(f_service_); + f_service_ << indent(); + } + + // If the handler reported failure but raised no application-defined + // exception, return a Thrift application exception with the information + // returned via GLib's own error-reporting mechanism + f_service_ << "if (*error == NULL)" << endl; + indent_up(); + f_service_ << indent() << "g_warning (\"" << service_name_ << "." + << (*function_iter)->get_name() << " implementation returned FALSE \"" << endl + << indent() << string(11, ' ') << "\"but did not set an error\");" << endl << endl; + indent_down(); + f_service_ << indent() << "xception =" << endl; + indent_up(); + f_service_ << indent() << "g_object_new (THRIFT_TYPE_APPLICATION_EXCEPTION," << endl; + args_indent = indent() + string(14, ' '); + f_service_ << args_indent << "\"type\", *error != NULL ? (*error)->code :" << endl + << args_indent << string(11, ' ') << "THRIFT_APPLICATION_EXCEPTION_ERROR_UNKNOWN," + << endl << args_indent << "\"message\", *error != NULL ? (*error)->message : NULL," + << endl << args_indent << "NULL);" << endl; + indent_down(); + f_service_ << indent() << "g_clear_error (error);" << endl << endl << indent() + << "result =" << endl; + indent_up(); + f_service_ << indent() << "((thrift_protocol_write_message_begin (output_protocol," << endl; + args_indent = indent() + string(39, ' '); + f_service_ << args_indent << "\"" << service_function_name << "\"," << endl << args_indent + << "T_EXCEPTION," << endl << args_indent << "sequence_id," << endl << args_indent + << "error) != -1) &&" << endl << indent() + << " (thrift_struct_write (THRIFT_STRUCT (xception)," << endl; + args_indent = indent() + string(23, ' '); + f_service_ << args_indent << "output_protocol," << endl << args_indent << "error) != -1));" + << endl; + indent_down(); + f_service_ << endl << indent() << "g_object_unref (xception);" << endl; + + if (xceptions.size() > 0) { + scope_down(f_service_); + } + scope_down(f_service_); + f_service_ << endl; + + // Dellocate or unref retrieved argument values as necessary + for (arg_iter = args.begin(); arg_iter != args.end(); ++arg_iter) { + string arg_name = (*arg_iter)->get_name(); + t_type* arg_type = get_true_type((*arg_iter)->get_type()); + + if (arg_type->is_base_type()) { + t_base_type* base_type = ((t_base_type*)arg_type); + + if (base_type->get_base() == t_base_type::TYPE_STRING) { + f_service_ << indent() << "if (" << arg_name << " != NULL)" << endl; + indent_up(); + if (base_type->is_binary()) { + f_service_ << indent() << "g_byte_array_unref (" << arg_name << ");" << endl; + } else { + f_service_ << indent() << "g_free (" << arg_name << ");" << endl; + } + indent_down(); + } + } else if (arg_type->is_container()) { + f_service_ << indent() << "if (" << arg_name << " != NULL)" << endl; + indent_up(); + + if (arg_type->is_list()) { + t_type* elem_type = ((t_list*)arg_type)->get_elem_type(); + + f_service_ << indent(); + if (is_numeric(elem_type)) { + f_service_ << "g_array_unref"; + } else { + f_service_ << "g_ptr_array_unref"; + } + f_service_ << " (" << arg_name << ");" << endl; + } else if (arg_type->is_map() || arg_type->is_set()) { + f_service_ << indent() << "g_hash_table_unref (" << arg_name << ");" << endl; + } + + indent_down(); + } else if (arg_type->is_struct()) { + f_service_ << indent() << "if (" << arg_name << " != NULL)" << endl; + indent_up(); + f_service_ << indent() << "g_object_unref (" << arg_name << ");" << endl; + indent_down(); + } + } + + if (!(*function_iter)->is_oneway()) { + f_service_ << indent() << "g_object_unref (result_struct);" << endl << endl << indent() + << "if (result == TRUE)" << endl; + indent_up(); + f_service_ << indent() << "result =" << endl; + indent_up(); + f_service_ << indent() << "((thrift_protocol_write_message_end " + << "(output_protocol, error) != -1) &&" << endl << indent() + << " (thrift_transport_write_end (transport, error) " + << "!= FALSE) &&" << endl << indent() + << " (thrift_transport_flush (transport, error) " + << "!= FALSE));" << endl; + indent_down(); + indent_down(); + } + scope_down(f_service_); + f_service_ << indent() << "else" << endl; + indent_up(); + f_service_ << indent() << "result = FALSE;" << endl; + indent_down(); + + f_service_ << endl << indent() << "g_object_unref (transport);" << endl << indent() + << "g_object_unref (args);" << endl << endl << indent() << "return result;" << endl; + scope_down(f_service_); + + f_service_ << endl; + } + + // Generate the processor's dispatch_call implementation + function_name = class_name_lc + "_dispatch_call"; + args_indent = indent() + string(function_name.length() + 2, ' '); + f_service_ << "static gboolean" << endl << function_name + << " (ThriftDispatchProcessor *dispatch_processor," << endl << args_indent + << "ThriftProtocol *input_protocol," << endl << args_indent + << "ThriftProtocol *output_protocol," << endl << args_indent << "gchar *method_name," + << endl << args_indent << "gint32 sequence_id," << endl << args_indent + << "GError **error)" << endl; + scope_up(f_service_); + f_service_ << indent() << class_name_lc << "_process_function_def *" + << "process_function_def;" << endl; + f_service_ << indent() << "gboolean dispatch_result = FALSE;" << endl << endl << indent() + << class_name << " *self = " << class_name_uc << " (dispatch_processor);" << endl; + f_service_ << indent() << parent_class_name << "Class " + "*parent_class =" << endl; + indent_up(); + f_service_ << indent() << "g_type_class_peek_parent (" << class_name_uc << "_GET_CLASS (self));" + << endl; + indent_down(); + f_service_ << endl + << indent() << "process_function_def = " + << "g_hash_table_lookup (self->process_map, method_name);" << endl + << indent() << "if (process_function_def != NULL)" << endl; + scope_up(f_service_); + args_indent = indent() + string(53, ' '); + f_service_ << indent() << "g_free (method_name);" << endl + << indent() << "dispatch_result = " + << "(*process_function_def->function) (self," << endl + << args_indent << "sequence_id," << endl + << args_indent << "input_protocol," << endl + << args_indent << "output_protocol," << endl + << args_indent << "error);" << endl; + scope_down(f_service_); + f_service_ << indent() << "else" << endl; + scope_up(f_service_); + + // Method name not recognized; chain up to our parent processor---note the + // top-most implementation of this method, in ThriftDispatchProcessor itself, + // will return an application exception to the caller if no class in the + // hierarchy recognizes the method name + f_service_ << indent() << "dispatch_result = parent_class->dispatch_call " + "(dispatch_processor," << endl; + args_indent = indent() + string(47, ' '); + f_service_ << args_indent << "input_protocol," << endl << args_indent << "output_protocol," + << endl << args_indent << "method_name," << endl << args_indent << "sequence_id," + << endl << args_indent << "error);" << endl; + scope_down(f_service_); + f_service_ << endl << indent() << "return dispatch_result;" << endl; + scope_down(f_service_); + f_service_ << endl; + + // Generate the processor's property setter + function_name = class_name_lc + "_set_property"; + args_indent = string(function_name.length() + 2, ' '); + f_service_ << "static void" << endl << function_name << " (GObject *object," << endl + << args_indent << "guint property_id," << endl << args_indent << "const GValue *value," + << endl << args_indent << "GParamSpec *pspec)" << endl; + scope_up(f_service_); + f_service_ << indent() << class_name << " *self = " << class_name_uc << " (object);" << endl + << endl << indent() << "switch (property_id)" << endl; + scope_up(f_service_); + f_service_ << indent() << "case PROP_" << class_name_uc << "_HANDLER:" << endl; + indent_up(); + f_service_ << indent() << "if (self->handler != NULL)" << endl; + indent_up(); + f_service_ << indent() << "g_object_unref (self->handler);" << endl; + indent_down(); + f_service_ << indent() << "self->handler = g_value_get_object (value);" << endl << indent() + << "g_object_ref (self->handler);" << endl; + if (extends_service) { + // Chain up to set the handler in every superclass as well + f_service_ << endl << indent() << "G_OBJECT_CLASS (" << class_name_lc << "_parent_class)->" + << endl; + indent_up(); + f_service_ << indent() << "set_property (object, property_id, value, pspec);" << endl; + indent_down(); + } + f_service_ << indent() << "break;" << endl; + indent_down(); + f_service_ << indent() << "default:" << endl; + indent_up(); + f_service_ << indent() << "G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);" + << endl << indent() << "break;" << endl; + indent_down(); + scope_down(f_service_); + scope_down(f_service_); + f_service_ << endl; + + // Generate processor's property getter + function_name = class_name_lc + "_get_property"; + args_indent = string(function_name.length() + 2, ' '); + f_service_ << "static void" << endl << function_name << " (GObject *object," << endl + << args_indent << "guint property_id," << endl << args_indent << "GValue *value," + << endl << args_indent << "GParamSpec *pspec)" << endl; + scope_up(f_service_); + f_service_ << indent() << class_name << " *self = " << class_name_uc << " (object);" << endl + << endl << indent() << "switch (property_id)" << endl; + scope_up(f_service_); + f_service_ << indent() << "case PROP_" << class_name_uc << "_HANDLER:" << endl; + indent_up(); + f_service_ << indent() << "g_value_set_object (value, self->handler);" << endl << indent() + << "break;" << endl; + indent_down(); + f_service_ << indent() << "default:" << endl; + indent_up(); + f_service_ << indent() << "G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);" + << endl << indent() << "break;" << endl; + indent_down(); + scope_down(f_service_); + scope_down(f_service_); + f_service_ << endl; + + // Generator the processor's dispose function + f_service_ << "static void" << endl << class_name_lc << "_dispose (GObject *gobject)" << endl; + scope_up(f_service_); + f_service_ << indent() << class_name << " *self = " << class_name_uc << " (gobject);" << endl + << endl << indent() << "if (self->handler != NULL)" << endl; + scope_up(f_service_); + f_service_ << indent() << "g_object_unref (self->handler);" << endl << indent() + << "self->handler = NULL;" << endl; + scope_down(f_service_); + f_service_ << endl << indent() << "G_OBJECT_CLASS (" << class_name_lc << "_parent_class)" + "->dispose (gobject);" + << endl; + scope_down(f_service_); + f_service_ << endl; + + // Generate processor finalize function + f_service_ << "static void" << endl << class_name_lc << "_finalize (GObject *gobject)" << endl; + scope_up(f_service_); + f_service_ << indent() << this->nspace << service_name_ << "Processor *self = " << this->nspace_uc + << service_name_uc << "_PROCESSOR (gobject);" << endl << endl << indent() + << "thrift_safe_hash_table_destroy (self->process_map);" << endl << endl << indent() + << "G_OBJECT_CLASS (" << class_name_lc << "_parent_class)" + "->finalize (gobject);" << endl; + scope_down(f_service_); + f_service_ << endl; + + // Generate processor instance initializer + f_service_ << "static void" << endl << class_name_lc << "_init (" << class_name << " *self)" + << endl; + scope_up(f_service_); + if (functions.size() > 0) { + f_service_ << indent() << "guint index;" << endl + << endl; + } + f_service_ << indent() << "self->handler = NULL;" << endl << indent() + << "self->process_map = " + "g_hash_table_new (g_str_hash, g_str_equal);" << endl; + if (functions.size() > 0) { + args_indent = string(21, ' '); + f_service_ << endl + << indent() << "for (index = 0; index < " + << functions.size() << "; index += 1)" << endl; + indent_up(); + f_service_ << indent() << "g_hash_table_insert (self->process_map," << endl + << indent() << args_indent + << class_name_lc << "_process_function_defs[index].name," << endl + << indent() << args_indent + << "&" << class_name_lc << "_process_function_defs[index]" << ");" + << endl; + indent_down(); + } + scope_down(f_service_); + f_service_ << endl; + + // Generate processor class initializer + f_service_ << "static void" << endl << class_name_lc << "_class_init (" << class_name + << "Class *cls)" << endl; + scope_up(f_service_); + f_service_ << indent() << "GObjectClass *gobject_class = G_OBJECT_CLASS (cls);" << endl + << indent() << "ThriftDispatchProcessorClass *dispatch_processor_class =" << endl; + indent_up(); + f_service_ << indent() << "THRIFT_DISPATCH_PROCESSOR_CLASS (cls);" << endl; + indent_down(); + f_service_ << indent() << "GParamSpec *param_spec;" << endl << endl << indent() + << "gobject_class->dispose = " << class_name_lc << "_dispose;" << endl << indent() + << "gobject_class->finalize = " << class_name_lc << "_finalize;" << endl << indent() + << "gobject_class->set_property = " << class_name_lc << "_set_property;" << endl + << indent() << "gobject_class->get_property = " << class_name_lc << "_get_property;" + << endl << endl << indent() + << "dispatch_processor_class->dispatch_call = " << class_name_lc << "_dispatch_call;" + << endl << indent() << "cls->dispatch_call = " << class_name_lc << "_dispatch_call;" + << endl << endl << indent() << "param_spec = g_param_spec_object (\"handler\"," + << endl; + args_indent = indent() + string(34, ' '); + f_service_ << args_indent << "\"Service handler implementation\"," << endl << args_indent + << "\"The service handler implementation \"" << endl << args_indent + << "\"to which method calls are dispatched.\"," << endl << args_indent + << this->nspace_uc + "TYPE_" + service_name_uc + "_HANDLER," << endl << args_indent + << "G_PARAM_READWRITE);" << endl; + f_service_ << indent() << "g_object_class_install_property (gobject_class," << endl; + args_indent = indent() + string(33, ' '); + f_service_ << args_indent << "PROP_" << class_name_uc << "_HANDLER," << endl << args_indent + << "param_spec);" << endl; + scope_down(f_service_); +} + +/** + * Generates C code that represents a Thrift service server. + */ +void t_c_glib_generator::generate_service_server(t_service* tservice) { + (void)tservice; + // Generate the service's handler class + generate_service_handler(tservice); + + // Generate the service's processor class + generate_service_processor(tservice); +} + +/** + * Generates C code to represent a THrift structure as a GObject. + */ +void t_c_glib_generator::generate_object(t_struct* tstruct) { + string name = tstruct->get_name(); + string name_u = initial_caps_to_underscores(name); + string name_uc = to_upper_case(name_u); + + string class_name = this->nspace + name; + string class_name_lc = this->nspace_lc + initial_caps_to_underscores(name); + string class_name_uc = to_upper_case(class_name_lc); + + string function_name; + string args_indent; + + // write the instance definition + f_types_ << "struct _" << this->nspace << name << endl << "{ " << endl + << " ThriftStruct parent; " << endl << endl << " /* public */" << endl; + + // for each field, add a member variable + vector<t_field*>::const_iterator m_iter; + const vector<t_field*>& members = tstruct->get_members(); + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* t = get_true_type((*m_iter)->get_type()); + f_types_ << " " << type_name(t) << " " << (*m_iter)->get_name() << ";" << endl; + if ((*m_iter)->get_req() != t_field::T_REQUIRED) { + f_types_ << " gboolean __isset_" << (*m_iter)->get_name() << ";" << endl; + } + } + + // close the structure definition and create a typedef + f_types_ << "};" << endl << "typedef struct _" << this->nspace << name << " " << this->nspace + << name << ";" << endl << endl; + + // write the class definition + f_types_ << "struct _" << this->nspace << name << "Class" << endl << "{" << endl + << " ThriftStructClass parent;" << endl << "};" << endl << "typedef struct _" + << this->nspace << name << "Class " << this->nspace << name << "Class;" << endl << endl; + + // write the standard GObject boilerplate + f_types_ << "GType " << this->nspace_lc << name_u << "_get_type (void);" << endl << "#define " + << this->nspace_uc << "TYPE_" << name_uc << " (" << this->nspace_lc << name_u + << "_get_type())" << endl << "#define " << this->nspace_uc << name_uc + << "(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), " << this->nspace_uc << "TYPE_" << name_uc + << ", " << this->nspace << name << "))" << endl << "#define " << this->nspace_uc + << name_uc << "_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), " << this->nspace_uc << "_TYPE_" + << name_uc << ", " << this->nspace << name << "Class))" << endl << "#define " + << this->nspace_uc << "IS_" << name_uc << "(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), " + << this->nspace_uc << "TYPE_" << name_uc << "))" << endl << "#define " << this->nspace_uc + << "IS_" << name_uc << "_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE ((c), " << this->nspace_uc + << "TYPE_" << name_uc << "))" << endl << "#define " << this->nspace_uc << name_uc + << "_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), " << this->nspace_uc << "TYPE_" + << name_uc << ", " << this->nspace << name << "Class))" << endl << endl; + + // start writing the object implementation .c file + + // generate properties enum + if (members.size() > 0) { + f_types_impl_ << "enum _" << class_name << "Properties" << endl << "{" << endl; + indent_up(); + f_types_impl_ << indent() << "PROP_" << class_name_uc << "_0"; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + string member_name_uc + = to_upper_case(to_lower_case(initial_caps_to_underscores((*m_iter)->get_name()))); + + f_types_impl_ << "," << endl << indent() << "PROP_" << class_name_uc << "_" << member_name_uc; + } + f_types_impl_ << endl; + indent_down(); + f_types_impl_ << "};" << endl << endl; + } + + // generate struct I/O methods + string this_get = this->nspace + name + " * this_object = " + this->nspace_uc + name_uc + + "(object);"; + generate_struct_reader(f_types_impl_, tstruct, "this_object->", this_get); + generate_struct_writer(f_types_impl_, tstruct, "this_object->", this_get); + + // generate property setter and getter + if (members.size() > 0) { + // generate property setter + function_name = class_name_lc + "_set_property"; + args_indent = string(function_name.length() + 2, ' '); + f_types_impl_ << "static void" << endl << function_name << " (GObject *object," << endl + << args_indent << "guint property_id," << endl << args_indent + << "const GValue *value," << endl << args_indent << "GParamSpec *pspec)" << endl; + scope_up(f_types_impl_); + f_types_impl_ << indent() << class_name << " *self = " << class_name_uc << " (object);" << endl + << endl << indent() << "switch (property_id)" << endl; + scope_up(f_types_impl_); + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_field* member = (*m_iter); + string member_name = member->get_name(); + string member_name_uc + = to_upper_case(to_lower_case(initial_caps_to_underscores(member_name))); + t_type* member_type = get_true_type(member->get_type()); + + string property_identifier = "PROP_" + class_name_uc + "_" + member_name_uc; + + f_types_impl_ << indent() << "case " << property_identifier + ":" << endl; + indent_up(); + + if (member_type->is_base_type()) { + t_base_type* base_type = ((t_base_type*)member_type); + string assign_function_name; + + if (base_type->get_base() == t_base_type::TYPE_STRING) { + string release_function_name; + + f_types_impl_ << indent() << "if (self->" << member_name << " != NULL)" << endl; + indent_up(); + + if (base_type->is_binary()) { + release_function_name = "g_byte_array_unref"; + assign_function_name = "g_value_dup_boxed"; + } else { + release_function_name = "g_free"; + assign_function_name = "g_value_dup_string"; + } + + f_types_impl_ << indent() << release_function_name << " (self->" << member_name << ");" + << endl; + indent_down(); + } else { + switch (base_type->get_base()) { + case t_base_type::TYPE_BOOL: + assign_function_name = "g_value_get_boolean"; + break; + + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + assign_function_name = "g_value_get_int"; + break; + + case t_base_type::TYPE_I64: + assign_function_name = "g_value_get_int64"; + break; + + case t_base_type::TYPE_DOUBLE: + assign_function_name = "g_value_get_double"; + break; + + default: + throw "compiler error: " + "unrecognized base type \"" + base_type->get_name() + "\" " + "for struct member \"" + + member_name + "\""; + break; + } + } + + f_types_impl_ << indent() << "self->" << member_name << " = " << assign_function_name + << " (value);" << endl; + } else if (member_type->is_enum()) { + f_types_impl_ << indent() << "self->" << member_name << " = g_value_get_int (value);" + << endl; + } else if (member_type->is_container()) { + string release_function_name; + string assign_function_name; + + if (member_type->is_list()) { + t_type* elem_type = ((t_list*)member_type)->get_elem_type(); + + // Lists of base types other than strings are represented as GArrays; + // all others as GPtrArrays + if (is_numeric(elem_type)) { + release_function_name = "g_array_unref"; + } else { + release_function_name = "g_ptr_array_unref"; + } + + assign_function_name = "g_value_dup_boxed"; + } else if (member_type->is_set() || member_type->is_map()) { + release_function_name = "g_hash_table_unref"; + assign_function_name = "g_value_dup_boxed"; + } + + f_types_impl_ << indent() << "if (self->" << member_name << " != NULL)" << endl; + indent_up(); + f_types_impl_ << indent() << release_function_name << " (self->" << member_name << ");" + << endl; + indent_down(); + f_types_impl_ << indent() << "self->" << member_name << " = " << assign_function_name + << " (value);" << endl; + } else if (member_type->is_struct() || member_type->is_xception()) { + f_types_impl_ << indent() << "if (self->" << member_name << " != NULL)" << endl; + indent_up(); + f_types_impl_ << indent() << "g_object_unref (self->" << member_name << ");" << endl; + indent_down(); + f_types_impl_ << indent() << "self->" << member_name << " = g_value_dup_object (value);" + << endl; + } + + if (member->get_req() != t_field::T_REQUIRED) { + f_types_impl_ << indent() << "self->__isset_" << member_name << " = TRUE;" << endl; + } + + f_types_impl_ << indent() << "break;" << endl << endl; + indent_down(); + } + f_types_impl_ << indent() << "default:" << endl; + indent_up(); + f_types_impl_ << indent() << "G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);" + << endl << indent() << "break;" << endl; + indent_down(); + scope_down(f_types_impl_); + scope_down(f_types_impl_); + f_types_impl_ << endl; + + // generate property getter + function_name = class_name_lc + "_get_property"; + args_indent = string(function_name.length() + 2, ' '); + f_types_impl_ << "static void" << endl << function_name << " (GObject *object," << endl + << args_indent << "guint property_id," << endl << args_indent << "GValue *value," + << endl << args_indent << "GParamSpec *pspec)" << endl; + scope_up(f_types_impl_); + f_types_impl_ << indent() << class_name << " *self = " << class_name_uc << " (object);" << endl + << endl << indent() << "switch (property_id)" << endl; + scope_up(f_types_impl_); + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_field* member = (*m_iter); + string member_name = (*m_iter)->get_name(); + string member_name_uc + = to_upper_case(to_lower_case(initial_caps_to_underscores(member_name))); + t_type* member_type = get_true_type(member->get_type()); + + string property_identifier = "PROP_" + class_name_uc + "_" + member_name_uc; + + string setter_function_name; + + if (member_type->is_base_type()) { + t_base_type* base_type = ((t_base_type*)member_type); + + switch (base_type->get_base()) { + case t_base_type::TYPE_BOOL: + setter_function_name = "g_value_set_boolean"; + break; + + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + setter_function_name = "g_value_set_int"; + break; + + case t_base_type::TYPE_I64: + setter_function_name = "g_value_set_int64"; + break; + + case t_base_type::TYPE_DOUBLE: + setter_function_name = "g_value_set_double"; + break; + + case t_base_type::TYPE_STRING: + if (base_type->is_binary()) { + setter_function_name = "g_value_set_boxed"; + } else { + setter_function_name = "g_value_set_string"; + } + break; + + default: + throw "compiler error: " + "unrecognized base type \"" + base_type->get_name() + "\" " + "for struct member \"" + + member_name + "\""; + break; + } + } else if (member_type->is_enum()) { + setter_function_name = "g_value_set_int"; + } else if (member_type->is_struct() || member_type->is_xception()) { + setter_function_name = "g_value_set_object"; + } else if (member_type->is_container()) { + setter_function_name = "g_value_set_boxed"; + } else { + throw "compiler error: " + "unrecognized type for struct member \"" + member_name + "\""; + } + + f_types_impl_ << indent() << "case " << property_identifier + ":" << endl; + indent_up(); + f_types_impl_ << indent() << setter_function_name << " (value, self->" << member_name << ");" + << endl << indent() << "break;" << endl << endl; + indent_down(); + } + f_types_impl_ << indent() << "default:" << endl; + indent_up(); + f_types_impl_ << indent() << "G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);" + << endl << indent() << "break;" << endl; + indent_down(); + scope_down(f_types_impl_); + scope_down(f_types_impl_); + f_types_impl_ << endl; + } + + // generate the instance init function + + f_types_impl_ << "static void " << endl << this->nspace_lc << name_u << "_instance_init (" + << this->nspace << name << " * object)" << endl << "{" << endl; + indent_up(); + + // generate default-value structures for container-type members + bool constant_declaration_output = false; + bool string_list_constant_output = false; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_field* member = *m_iter; + t_const_value* member_value = member->get_value(); + + if (member_value != NULL) { + string member_name = member->get_name(); + t_type* member_type = get_true_type(member->get_type()); + + if (member_type->is_list()) { + const vector<t_const_value*>& list = member_value->get_list(); + t_type* elem_type = ((t_list*)member_type)->get_elem_type(); + + // Generate an array with the list literal + indent(f_types_impl_) << "static " << type_name(elem_type, false, true) << " __default_" + << member_name << "[" << list.size() << "] = " << endl; + indent_up(); + f_types_impl_ << indent() << constant_literal(member_type, member_value) << ";" << endl; + indent_down(); + + constant_declaration_output = true; + + // If we are generating values for a pointer array (i.e. a list of + // strings), set a flag so we know to also declare an index variable to + // use in pre-populating the array + if (elem_type->is_string()) { + string_list_constant_output = true; + } + } + + // TODO: Handle container types other than list + } + } + if (constant_declaration_output) { + if (string_list_constant_output) { + indent(f_types_impl_) << "unsigned int list_index;" << endl; + } + + f_types_impl_ << endl; + } + + // satisfy compilers with -Wall turned on + indent(f_types_impl_) << "/* satisfy -Wall */" << endl << indent() + << "THRIFT_UNUSED_VAR (object);" << endl; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* member_type = (*m_iter)->get_type(); + t_type* t = get_true_type(member_type); + if (t->is_base_type()) { + string dval = " = "; + if (t->is_enum()) { + dval += "(" + type_name(t) + ")"; + } + t_const_value* cv = (*m_iter)->get_value(); + if (cv != NULL) { + dval += constant_value("", t, cv); + } else { + dval += t->is_string() ? "NULL" : "0"; + } + indent(f_types_impl_) << "object->" << (*m_iter)->get_name() << dval << ";" << endl; + } else if (t->is_struct()) { + string name = (*m_iter)->get_name(); + t_program* type_program = member_type->get_program(); + string type_nspace = type_program ? type_program->get_namespace("c_glib") : ""; + string type_nspace_prefix = + type_nspace.empty() ? "" : initial_caps_to_underscores(type_nspace) + "_"; + string type_name_uc = to_upper_case(initial_caps_to_underscores(member_type->get_name())); + indent(f_types_impl_) << "object->" << name << " = g_object_new (" + << to_upper_case(type_nspace_prefix) << "TYPE_" << type_name_uc + << ", NULL);" << endl; + } else if (t->is_xception()) { + string name = (*m_iter)->get_name(); + indent(f_types_impl_) << "object->" << name << " = NULL;" << endl; + } else if (t->is_container()) { + string name = (*m_iter)->get_name(); + string init_function; + t_type* etype = NULL; + + if (t->is_map()) { + t_type* key = ((t_map*)t)->get_key_type(); + t_type* value = ((t_map*)t)->get_val_type(); + init_function = generate_new_hash_from_type(key, value); + } else if (t->is_set()) { + etype = ((t_set*)t)->get_elem_type(); + init_function = generate_new_hash_from_type(etype, NULL); + } else if (t->is_list()) { + etype = ((t_list*)t)->get_elem_type(); + init_function = generate_new_array_from_type(etype); + } + + indent(f_types_impl_) << "object->" << name << " = " << init_function << endl; + + // Pre-populate the container with the specified default values, if any + if ((*m_iter)->get_value()) { + t_const_value* member_value = (*m_iter)->get_value(); + + if (t->is_list()) { + const vector<t_const_value*>& list = member_value->get_list(); + + if (is_numeric(etype)) { + indent(f_types_impl_) << + "g_array_append_vals (object->" << name << ", &__default_" << + name << ", " << list.size() << ");" << endl; + } + else { + indent(f_types_impl_) << + "for (list_index = 0; list_index < " << list.size() << "; " << + "list_index += 1)" << endl; + indent_up(); + indent(f_types_impl_) << + "g_ptr_array_add (object->" << name << "," << endl << + indent() << string(17, ' ') << "g_strdup (__default_" << + name << "[list_index]));" << endl; + indent_down(); + } + } + + // TODO: Handle container types other than list + } + } + + /* if not required, initialize the __isset variable */ + if ((*m_iter)->get_req() != t_field::T_REQUIRED) { + indent(f_types_impl_) << "object->__isset_" << (*m_iter)->get_name() << " = FALSE;" << endl; + } + } + + indent_down(); + f_types_impl_ << "}" << endl << endl; + + /* create the destructor */ + f_types_impl_ << "static void " << endl << this->nspace_lc << name_u + << "_finalize (GObject *object)" << endl << "{" << endl; + indent_up(); + + f_types_impl_ << indent() << this->nspace << name << " *tobject = " << this->nspace_uc << name_uc + << " (object);" << endl << endl; + + f_types_impl_ << indent() << "/* satisfy -Wall in case we don't use tobject */" << endl + << indent() << "THRIFT_UNUSED_VAR (tobject);" << endl; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* t = get_true_type((*m_iter)->get_type()); + if (t->is_container()) { + string name = (*m_iter)->get_name(); + if (t->is_map() || t->is_set()) { + f_types_impl_ << indent() << "if (tobject->" << name << " != NULL)" << endl; + f_types_impl_ << indent() << "{" << endl; + indent_up(); + f_types_impl_ << indent() << "g_hash_table_destroy (tobject->" << name << ");" << endl; + f_types_impl_ << indent() << "tobject->" << name << " = NULL;" << endl; + indent_down(); + f_types_impl_ << indent() << "}" << endl; + } else if (t->is_list()) { + t_type* etype = ((t_list*)t)->get_elem_type(); + string destructor_function = "g_ptr_array_unref"; + + if (etype->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)etype)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot determine array type"; + case t_base_type::TYPE_BOOL: + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + case t_base_type::TYPE_DOUBLE: + destructor_function = "g_array_unref"; + break; + case t_base_type::TYPE_STRING: + break; + default: + throw "compiler error: no array info for type"; + } + } else if (etype->is_enum()) { + destructor_function = "g_array_unref"; + } + + f_types_impl_ << indent() << "if (tobject->" << name << " != NULL)" << endl; + f_types_impl_ << indent() << "{" << endl; + indent_up(); + f_types_impl_ << indent() << destructor_function << " (tobject->" << name << ");" << endl; + f_types_impl_ << indent() << "tobject->" << name << " = NULL;" << endl; + indent_down(); + f_types_impl_ << indent() << "}" << endl; + } + } else if (t->is_struct() || t->is_xception()) { + string name = (*m_iter)->get_name(); + // TODO: g_clear_object needs glib >= 2.28 + // f_types_impl_ << indent() << "g_clear_object (&(tobject->" << name << "));" << endl; + // does g_object_unref the trick? + f_types_impl_ << indent() << "if (tobject->" << name << " != NULL)" << endl; + f_types_impl_ << indent() << "{" << endl; + indent_up(); + f_types_impl_ << indent() << "g_object_unref(tobject->" << name << ");" << endl; + f_types_impl_ << indent() << "tobject->" << name << " = NULL;" << endl; + indent_down(); + f_types_impl_ << indent() << "}" << endl; + } else if (t->is_string()) { + string name = (*m_iter)->get_name(); + f_types_impl_ << indent() << "if (tobject->" << name << " != NULL)" << endl; + f_types_impl_ << indent() << "{" << endl; + indent_up(); + f_types_impl_ << indent() << generate_free_func_from_type(t) << "(tobject->" << name << ");" + << endl; + f_types_impl_ << indent() << "tobject->" << name << " = NULL;" << endl; + indent_down(); + f_types_impl_ << indent() << "}" << endl; + } + } + + indent_down(); + f_types_impl_ << "}" << endl << endl; + + // generate the class init function + + f_types_impl_ << "static void" << endl << class_name_lc << "_class_init (" << class_name + << "Class * cls)" << endl; + scope_up(f_types_impl_); + + f_types_impl_ << indent() << "GObjectClass *gobject_class = G_OBJECT_CLASS (cls);" << endl + << indent() << "ThriftStructClass *struct_class = " + << "THRIFT_STRUCT_CLASS (cls);" << endl << endl << indent() + << "struct_class->read = " << class_name_lc << "_read;" << endl << indent() + << "struct_class->write = " << class_name_lc << "_write;" << endl << endl + << indent() << "gobject_class->finalize = " << class_name_lc << "_finalize;" + << endl; + if (members.size() > 0) { + f_types_impl_ << indent() << "gobject_class->get_property = " << class_name_lc + << "_get_property;" << endl << indent() + << "gobject_class->set_property = " << class_name_lc << "_set_property;" << endl; + + // install a property for each member + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_field* member = (*m_iter); + string member_name = member->get_name(); + string member_name_uc + = to_upper_case(to_lower_case(initial_caps_to_underscores(member_name))); + t_type* member_type = get_true_type(member->get_type()); + t_const_value* member_value = member->get_value(); + + string property_identifier = "PROP_" + class_name_uc + "_" + member_name_uc; + + f_types_impl_ << endl << indent() << "g_object_class_install_property" << endl; + indent_up(); + args_indent = indent() + ' '; + f_types_impl_ << indent() << "(gobject_class," << endl << args_indent << property_identifier + << "," << endl << args_indent; + + if (member_type->is_base_type()) { + t_base_type::t_base base_type = ((t_base_type*)member_type)->get_base(); + + if (base_type == t_base_type::TYPE_STRING) { + if (((t_base_type*)member_type)->is_binary()) { + args_indent += string(20, ' '); + f_types_impl_ << "g_param_spec_boxed (\"" << member_name << "\"," << endl << args_indent + << "NULL," << endl << args_indent << "NULL," << endl << args_indent + << "G_TYPE_BYTE_ARRAY," << endl << args_indent << "G_PARAM_READWRITE));" + << endl; + } else { + args_indent += string(21, ' '); + f_types_impl_ << "g_param_spec_string (\"" << member_name << "\"," << endl + << args_indent << "NULL," << endl << args_indent << "NULL," << endl + << args_indent + << ((member_value != NULL) ? "\"" + member_value->get_string() + "\"" + : "NULL") << "," << endl << args_indent + << "G_PARAM_READWRITE));" << endl; + } + } else if (base_type == t_base_type::TYPE_BOOL) { + args_indent += string(22, ' '); + f_types_impl_ << "g_param_spec_boolean (\"" << member_name << "\"," << endl << args_indent + << "NULL," << endl << args_indent << "NULL," << endl << args_indent + << (((member_value != NULL) && (member_value->get_integer() != 0)) + ? "TRUE" + : "FALSE") << "," << endl << args_indent << "G_PARAM_READWRITE));" + << endl; + } else if ((base_type == t_base_type::TYPE_I8) || (base_type == t_base_type::TYPE_I16) + || (base_type == t_base_type::TYPE_I32) || (base_type == t_base_type::TYPE_I64) + || (base_type == t_base_type::TYPE_DOUBLE)) { + string param_spec_function_name = "g_param_spec_int"; + string min_value; + string max_value; + ostringstream default_value; + + switch (base_type) { + case t_base_type::TYPE_I8: + min_value = "G_MININT8"; + max_value = "G_MAXINT8"; + break; + + case t_base_type::TYPE_I16: + min_value = "G_MININT16"; + max_value = "G_MAXINT16"; + break; + + case t_base_type::TYPE_I32: + min_value = "G_MININT32"; + max_value = "G_MAXINT32"; + break; + + case t_base_type::TYPE_I64: + param_spec_function_name = "g_param_spec_int64"; + min_value = "G_MININT64"; + max_value = "G_MAXINT64"; + break; + + case t_base_type::TYPE_DOUBLE: + param_spec_function_name = "g_param_spec_double"; + min_value = "-INFINITY"; + max_value = "INFINITY"; + break; + + default: + throw "compiler error: " + "unrecognized base type \"" + member_type->get_name() + "\" " + "for struct member \"" + + member_name + "\""; + break; + } + + if (member_value != NULL) { + default_value << (base_type == t_base_type::TYPE_DOUBLE ? member_value->get_double() + : member_value->get_integer()); + } else { + default_value << "0"; + } + + args_indent += string(param_spec_function_name.length() + 2, ' '); + f_types_impl_ << param_spec_function_name << " (\"" << member_name << "\"," << endl + << args_indent << "NULL," << endl << args_indent << "NULL," << endl + << args_indent << min_value << "," << endl << args_indent << max_value + << "," << endl << args_indent << default_value.str() << "," << endl + << args_indent << "G_PARAM_READWRITE));" << endl; + } + + indent_down(); + } else if (member_type->is_enum()) { + t_enum_value* enum_min_value = ((t_enum*)member_type)->get_min_value(); + t_enum_value* enum_max_value = ((t_enum*)member_type)->get_max_value(); + int min_value = (enum_min_value != NULL) ? enum_min_value->get_value() : 0; + int max_value = (enum_max_value != NULL) ? enum_max_value->get_value() : 0; + + args_indent += string(18, ' '); + f_types_impl_ << "g_param_spec_int (\"" << member_name << "\"," << endl << args_indent + << "NULL," << endl << args_indent << "NULL," << endl << args_indent + << min_value << "," << endl << args_indent << max_value << "," << endl + << args_indent << min_value << "," << endl << args_indent + << "G_PARAM_READWRITE));" << endl; + indent_down(); + } else if (member_type->is_struct() || member_type->is_xception()) { + t_program* type_program = member_type->get_program(); + string type_nspace = type_program ? type_program->get_namespace("c_glib") : ""; + string type_nspace_prefix = + type_nspace.empty() ? "" : initial_caps_to_underscores(type_nspace) + "_"; + + string param_type = to_upper_case(type_nspace_prefix) + "TYPE_" + + to_upper_case(initial_caps_to_underscores(member_type->get_name())); + + args_indent += string(20, ' '); + f_types_impl_ << "g_param_spec_object (\"" << member_name << "\"," << endl << args_indent + << "NULL," << endl << args_indent << "NULL," << endl << args_indent + << param_type << "," << endl << args_indent << "G_PARAM_READWRITE));" << endl; + indent_down(); + } else if (member_type->is_list()) { + t_type* elem_type = ((t_list*)member_type)->get_elem_type(); + string param_type; + + if (elem_type->is_base_type() && !elem_type->is_string()) { + param_type = "G_TYPE_ARRAY"; + } else { + param_type = "G_TYPE_PTR_ARRAY"; + } + + args_indent += string(20, ' '); + f_types_impl_ << "g_param_spec_boxed (\"" << member_name << "\"," << endl << args_indent + << "NULL," << endl << args_indent << "NULL," << endl << args_indent + << param_type << "," << endl << args_indent << "G_PARAM_READWRITE));" << endl; + indent_down(); + } else if (member_type->is_set() || member_type->is_map()) { + args_indent += string(20, ' '); + f_types_impl_ << "g_param_spec_boxed (\"" << member_name << "\"," << endl << args_indent + << "NULL," << endl << args_indent << "NULL," << endl << args_indent + << "G_TYPE_HASH_TABLE," << endl << args_indent << "G_PARAM_READWRITE));" + << endl; + indent_down(); + } + } + } + scope_down(f_types_impl_); + f_types_impl_ << endl; + + f_types_impl_ << "GType" << endl << this->nspace_lc << name_u << "_get_type (void)" << endl << "{" + << endl << " static GType type = 0;" << endl << endl << " if (type == 0) " << endl + << " {" << endl << " static const GTypeInfo type_info = " << endl << " {" + << endl << " sizeof (" << this->nspace << name << "Class)," << endl + << " NULL, /* base_init */" << endl << " NULL, /* base_finalize */" + << endl << " (GClassInitFunc) " << this->nspace_lc << name_u << "_class_init," + << endl << " NULL, /* class_finalize */" << endl + << " NULL, /* class_data */" << endl << " sizeof (" << this->nspace + << name << ")," << endl << " 0, /* n_preallocs */" << endl + << " (GInstanceInitFunc) " << this->nspace_lc << name_u << "_instance_init," + << endl << " NULL, /* value_table */" << endl << " };" << endl << endl + << " type = g_type_register_static (THRIFT_TYPE_STRUCT, " << endl + << " \"" << this->nspace << name << "Type\"," + << endl << " &type_info, 0);" << endl << " }" + << endl << endl << " return type;" << endl << "}" << endl << endl; +} + +/** + * Generates functions to write Thrift structures to a stream. + */ +void t_c_glib_generator::generate_struct_writer(ostream& out, + t_struct* tstruct, + string this_name, + string this_get, + bool is_function) { + string name = tstruct->get_name(); + string name_u = initial_caps_to_underscores(name); + string name_uc = to_upper_case(name_u); + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + int error_ret = 0; + + if (is_function) { + error_ret = -1; + indent(out) << "static gint32" << endl << this->nspace_lc << name_u + << "_write (ThriftStruct *object, ThriftProtocol *protocol, GError **error)" + << endl; + } + indent(out) << "{" << endl; + indent_up(); + + out << indent() << "gint32 ret;" << endl << indent() << "gint32 xfer = 0;" << endl << endl; + + indent(out) << this_get << endl; + // satisfy -Wall in the case of an empty struct + if (!this_get.empty()) { + indent(out) << "THRIFT_UNUSED_VAR (this_object);" << endl; + } + + out << indent() << "if ((ret = thrift_protocol_write_struct_begin (protocol, \"" << name + << "\", error)) < 0)" << endl << indent() << " return " << error_ret << ";" << endl + << indent() << "xfer += ret;" << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_OPTIONAL) { + indent(out) << "if (this_object->__isset_" << (*f_iter)->get_name() << " == TRUE) {" << endl; + indent_up(); + } + + out << indent() << "if ((ret = thrift_protocol_write_field_begin (protocol, " + << "\"" << (*f_iter)->get_name() << "\", " << type_to_enum((*f_iter)->get_type()) << ", " + << (*f_iter)->get_key() << ", error)) < 0)" << endl << indent() << " return " << error_ret + << ";" << endl << indent() << "xfer += ret;" << endl; + generate_serialize_field(out, *f_iter, this_name, "", error_ret); + out << indent() << "if ((ret = thrift_protocol_write_field_end (protocol, error)) < 0)" << endl + << indent() << " return " << error_ret << ";" << endl << indent() << "xfer += ret;" + << endl; + + if ((*f_iter)->get_req() == t_field::T_OPTIONAL) { + indent_down(); + indent(out) << "}" << endl; + } + } + + // write the struct map + out << indent() << "if ((ret = thrift_protocol_write_field_stop (protocol, error)) < 0)" << endl + << indent() << " return " << error_ret << ";" << endl << indent() << "xfer += ret;" << endl + << indent() << "if ((ret = thrift_protocol_write_struct_end (protocol, error)) < 0)" << endl + << indent() << " return " << error_ret << ";" << endl << indent() << "xfer += ret;" << endl + << endl; + + if (is_function) { + indent(out) << "return xfer;" << endl; + } + + indent_down(); + indent(out) << "}" << endl << endl; +} + +/** + * Generates code to read Thrift structures from a stream. + */ +void t_c_glib_generator::generate_struct_reader(ostream& out, + t_struct* tstruct, + string this_name, + string this_get, + bool is_function) { + string name = tstruct->get_name(); + string name_u = initial_caps_to_underscores(name); + string name_uc = to_upper_case(name_u); + int error_ret = 0; + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + if (is_function) { + error_ret = -1; + indent(out) << "/* reads a " << name_u << " object */" << endl << "static gint32" << endl + << this->nspace_lc << name_u + << "_read (ThriftStruct *object, ThriftProtocol *protocol, GError **error)" << endl; + } + + indent(out) << "{" << endl; + indent_up(); + + // declare stack temp variables + out << indent() << "gint32 ret;" << endl << indent() << "gint32 xfer = 0;" << endl << indent() + << "gchar *name = NULL;" << endl << indent() << "ThriftType ftype;" << endl << indent() + << "gint16 fid;" << endl << indent() << "guint32 len = 0;" << endl << indent() + << "gpointer data = NULL;" << endl << indent() << this_get << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_REQUIRED) { + indent(out) << "gboolean isset_" << (*f_iter)->get_name() << " = FALSE;" << endl; + } + } + + out << endl; + + // satisfy -Wall in case we don't use some variables + out << indent() << "/* satisfy -Wall in case these aren't used */" << endl << indent() + << "THRIFT_UNUSED_VAR (len);" << endl << indent() << "THRIFT_UNUSED_VAR (data);" << endl; + + if (!this_get.empty()) { + out << indent() << "THRIFT_UNUSED_VAR (this_object);" << endl; + } + out << endl; + + // read the beginning of the structure marker + out << indent() << "/* read the struct begin marker */" << endl << indent() + << "if ((ret = thrift_protocol_read_struct_begin (protocol, &name, error)) < 0)" << endl + << indent() << "{" << endl << indent() << " if (name) g_free (name);" << endl << indent() + << " return " << error_ret << ";" << endl << indent() << "}" << endl << indent() + << "xfer += ret;" << endl << indent() << "if (name) g_free (name);" << endl << indent() + << "name = NULL;" << endl << endl; + + // read the struct fields + out << indent() << "/* read the struct fields */" << endl << indent() << "while (1)" << endl; + scope_up(out); + + // read beginning field marker + out << indent() << "/* read the beginning of a field */" << endl << indent() + << "if ((ret = thrift_protocol_read_field_begin (protocol, &name, &ftype, &fid, error)) < 0)" + << endl << indent() << "{" << endl << indent() << " if (name) g_free (name);" << endl + << indent() << " return " << error_ret << ";" << endl << indent() << "}" << endl << indent() + << "xfer += ret;" << endl << indent() << "if (name) g_free (name);" << endl << indent() + << "name = NULL;" << endl << endl; + + // check for field STOP marker + out << indent() << "/* break if we get a STOP field */" << endl << indent() + << "if (ftype == T_STOP)" << endl << indent() << "{" << endl << indent() << " break;" << endl + << indent() << "}" << endl << endl; + + // switch depending on the field type + indent(out) << "switch (fid)" << endl; + + // start switch + scope_up(out); + + // generate deserialization code for known types + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + indent(out) << "case " << (*f_iter)->get_key() << ":" << endl; + indent_up(); + indent(out) << "if (ftype == " << type_to_enum((*f_iter)->get_type()) << ")" << endl; + indent(out) << "{" << endl; + + indent_up(); + // generate deserialize field + generate_deserialize_field(out, *f_iter, this_name, "", error_ret, false); + indent_down(); + + out << indent() << "} else {" << endl << indent() + << " if ((ret = thrift_protocol_skip (protocol, ftype, error)) < 0)" << endl << indent() + << " return " << error_ret << ";" << endl << indent() << " xfer += ret;" << endl + << indent() << "}" << endl << indent() << "break;" << endl; + indent_down(); + } + + // create the default case + out << indent() << "default:" << endl << indent() + << " if ((ret = thrift_protocol_skip (protocol, ftype, error)) < 0)" << endl << indent() + << " return " << error_ret << ";" << endl << indent() << " xfer += ret;" << endl + << indent() << " break;" << endl; + + // end switch + scope_down(out); + + // read field end marker + out << indent() << "if ((ret = thrift_protocol_read_field_end (protocol, error)) < 0)" << endl + << indent() << " return " << error_ret << ";" << endl << indent() << "xfer += ret;" << endl; + + // end while loop + scope_down(out); + out << endl; + + // read the end of the structure + out << indent() << "if ((ret = thrift_protocol_read_struct_end (protocol, error)) < 0)" << endl + << indent() << " return " << error_ret << ";" << endl << indent() << "xfer += ret;" << endl + << endl; + + // if a required field is missing, throw an error + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_REQUIRED) { + out << indent() << "if (!isset_" << (*f_iter)->get_name() << ")" << endl << indent() << "{" + << endl << indent() << " g_set_error (error, THRIFT_PROTOCOL_ERROR," << endl << indent() + << " THRIFT_PROTOCOL_ERROR_INVALID_DATA," << endl << indent() + << " \"missing field\");" << endl << indent() << " return -1;" << endl + << indent() << "}" << endl << endl; + } + } + + if (is_function) { + indent(out) << "return xfer;" << endl; + } + + // end the function/structure + indent_down(); + indent(out) << "}" << endl << endl; +} + +void t_c_glib_generator::generate_serialize_field(ostream& out, + t_field* tfield, + string prefix, + string suffix, + int error_ret) { + t_type* type = get_true_type(tfield->get_type()); + string name = prefix + tfield->get_name() + suffix; + + if (type->is_void()) { + throw "CANNOT GENERATE SERIALIZE CODE FOR void TYPE: " + name; + } + + if (type->is_struct() || type->is_xception()) { + generate_serialize_struct(out, (t_struct*)type, name, error_ret); + } else if (type->is_container()) { + generate_serialize_container(out, type, name, error_ret); + } else if (type->is_base_type() || type->is_enum()) { + indent(out) << "if ((ret = thrift_protocol_write_"; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_BOOL: + out << "bool (protocol, " << name; + break; + case t_base_type::TYPE_I8: + out << "byte (protocol, " << name; + break; + case t_base_type::TYPE_I16: + out << "i16 (protocol, " << name; + break; + case t_base_type::TYPE_I32: + out << "i32 (protocol, " << name; + break; + case t_base_type::TYPE_I64: + out << "i64 (protocol, " << name; + break; + case t_base_type::TYPE_DOUBLE: + out << "double (protocol, " << name; + break; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + out << "binary (protocol, " << name << " ? ((GByteArray *) " << name << ")->data : NULL, " + << name << " ? ((GByteArray *) " << name << ")->len : 0"; + } else { + out << "string (protocol, " << name; + } + break; + default: + throw "compiler error: no C writer for base type " + t_base_type::t_base_name(tbase) + name; + } + } else { + out << "i32 (protocol, (gint32) " << name; + } + out << ", error)) < 0)" << endl + << indent() << " return " << error_ret << ";" << endl + << indent() << "xfer += ret;" << endl << endl; + } else { + throw std::logic_error("DO NOT KNOW HOW TO SERIALIZE FIELD '" + name + "' TYPE '" + + type_name(type)); + } +} + +void t_c_glib_generator::generate_serialize_struct(ostream& out, + t_struct* tstruct, + string prefix, + int error_ret) { + (void)tstruct; + out << indent() << "if ((ret = thrift_struct_write (THRIFT_STRUCT (" << prefix + << "), protocol, error)) < 0)" << endl << indent() << " return " << error_ret << ";" << endl + << indent() << "xfer += ret;" << endl << endl; +} + +void t_c_glib_generator::generate_serialize_container(ostream& out, + t_type* ttype, + string prefix, + int error_ret) { + scope_up(out); + + if (ttype->is_map()) { + t_type* tkey = ((t_map*)ttype)->get_key_type(); + t_type* tval = ((t_map*)ttype)->get_val_type(); + string tkey_name = type_name(tkey); + string tval_name = type_name(tval); + string tkey_ptr; + string tval_ptr; + string keyname = tmp("key"); + string valname = tmp("val"); + + declore_local_variable_for_write(out, tkey, keyname); + declore_local_variable_for_write(out, tval, valname); + + /* If either the key or value type is a typedef, find its underlying type so + we can correctly determine how to generate a pointer to it */ + tkey = get_true_type(tkey); + tval = get_true_type(tval); + + tkey_ptr = tkey->is_string() || !tkey->is_base_type() ? "" : "*"; + tval_ptr = tval->is_string() || !tval->is_base_type() ? "" : "*"; + + /* + * Some ugliness here. To maximize backwards compatibility, we + * avoid using GHashTableIter and instead get a GList of all keys, + * then copy it into a array on the stack, and free it. + * This is because we may exit early before we get a chance to free the + * GList. + */ + out << indent() << "GList *key_list = NULL, *iter = NULL;" << endl + << indent() << tkey_name << tkey_ptr << "* keys;" << endl + << indent() << "int i = 0, key_count;" << endl + << endl + << indent() << "if ((ret = thrift_protocol_write_map_begin (protocol, " + << type_to_enum(tkey) << ", " << type_to_enum(tval) << ", " << prefix << " ? " + << "(gint32) g_hash_table_size ((GHashTable *) " << prefix << ") : 0" + << ", error)) < 0)" << endl; + indent_up(); + out << indent() << "return " << error_ret << ";" << endl; + indent_down(); + out << indent() << "xfer += ret;" << endl + << indent() << "if (" << prefix << ")" << endl + << indent() << " g_hash_table_foreach ((GHashTable *) " << prefix + << ", thrift_hash_table_get_keys, &key_list);" << endl + << indent() << "key_count = g_list_length (key_list);" << endl + << indent() << "keys = g_newa (" << tkey_name << tkey_ptr + << ", key_count);" << endl + << indent() << "for (iter = g_list_first (key_list); iter; " + "iter = iter->next)" << endl; + indent_up(); + out << indent() << "keys[i++] = (" << tkey_name << tkey_ptr + << ") iter->data;" << endl; + indent_down(); + out << indent() << "g_list_free (key_list);" << endl + << endl + << indent() << "for (i = 0; i < key_count; ++i)" << endl; + scope_up(out); + out << indent() << keyname << " = keys[i];" << endl + << indent() << valname << " = (" << tval_name << tval_ptr + << ") g_hash_table_lookup (((GHashTable *) " << prefix + << "), (gpointer) " << keyname << ");" << endl + << endl; + generate_serialize_map_element(out, + (t_map*)ttype, + tkey_ptr + " " + keyname, + tval_ptr + " " + valname, + error_ret); + scope_down(out); + out << indent() << "if ((ret = thrift_protocol_write_map_end (protocol, " + "error)) < 0)" << endl; + indent_up(); + out << indent() << "return " << error_ret << ";" << endl; + indent_down(); + out << indent() << "xfer += ret;" << endl; + } else if (ttype->is_set()) { + t_type* telem = ((t_set*)ttype)->get_elem_type(); + string telem_name = type_name(telem); + string telem_ptr = telem->is_string() || !telem->is_base_type() ? "" : "*"; + out << indent() << "GList *key_list = NULL, *iter = NULL;" << endl + << indent() << telem_name << telem_ptr << "* keys;" << endl + << indent() << "int i = 0, key_count;" << endl + << indent() << telem_name << telem_ptr << " elem;" << endl + << indent() << "gpointer value;" << endl + << indent() << "THRIFT_UNUSED_VAR (value);" << endl + << endl + << indent() << "if ((ret = thrift_protocol_write_set_begin (protocol, " + << type_to_enum(telem) << ", " << prefix << " ? " + << "(gint32) g_hash_table_size ((GHashTable *) " << prefix << ") : 0" + << ", error)) < 0)" << endl; + indent_up(); + out << indent() << "return " << error_ret << ";" << endl; + indent_down(); + out << indent() << "xfer += ret;" << endl + << indent() << "if (" << prefix << ")" << endl + << indent() << " g_hash_table_foreach ((GHashTable *) " << prefix + << ", thrift_hash_table_get_keys, &key_list);" << endl + << indent() << "key_count = g_list_length (key_list);" << endl + << indent() << "keys = g_newa (" << telem_name << telem_ptr + << ", key_count);" << endl + << indent() << "for (iter = g_list_first (key_list); iter; " + "iter = iter->next)" << endl; + indent_up(); + out << indent() << "keys[i++] = (" << telem_name << telem_ptr + << ") iter->data;" << endl; + indent_down(); + out << indent() << "g_list_free (key_list);" << endl + << endl + << indent() << "for (i = 0; i < key_count; ++i)" << endl; + scope_up(out); + out << indent() << "elem = keys[i];" << endl + << indent() << "value = (gpointer) g_hash_table_lookup " + "(((GHashTable *) " << prefix << "), (gpointer) elem);" << endl + << endl; + generate_serialize_set_element(out, + (t_set*)ttype, + telem_ptr + "elem", + error_ret); + scope_down(out); + out << indent() << "if ((ret = thrift_protocol_write_set_end (protocol, " + "error)) < 0)" << endl; + indent_up(); + out << indent() << "return " << error_ret << ";" << endl; + indent_down(); + out << indent() << "xfer += ret;" << endl; + } else if (ttype->is_list()) { + string length = "(" + prefix + " ? " + prefix + "->len : 0)"; + string i = tmp("i"); + out << indent() << "guint " << i << ";" << endl + << endl + << indent() << "if ((ret = thrift_protocol_write_list_begin (protocol, " + << type_to_enum(((t_list*)ttype)->get_elem_type()) << ", (gint32) " + << length << ", error)) < 0)" << endl; + indent_up(); + out << indent() << "return " << error_ret << ";" << endl; + indent_down(); + out << indent() << "xfer += ret;" << endl + << indent() << "for (" << i << " = 0; " << i << " < " << length << "; " + << i << "++)" << endl; + scope_up(out); + generate_serialize_list_element(out, (t_list*)ttype, prefix, i, error_ret); + scope_down(out); + out << indent() << "if ((ret = thrift_protocol_write_list_end (protocol, " + "error)) < 0)" << endl; + indent_up(); + out << indent() << "return " << error_ret << ";" << endl; + indent_down(); + out << indent() << "xfer += ret;" << endl; + } + + scope_down(out); +} + +void t_c_glib_generator::generate_serialize_map_element(ostream& out, + t_map* tmap, + string key, + string value, + int error_ret) { + t_field kfield(tmap->get_key_type(), key); + generate_serialize_field(out, &kfield, "", "", error_ret); + + t_field vfield(tmap->get_val_type(), value); + generate_serialize_field(out, &vfield, "", "", error_ret); +} + +void t_c_glib_generator::generate_serialize_set_element(ostream& out, + t_set* tset, + string element, + int error_ret) { + t_field efield(tset->get_elem_type(), element); + generate_serialize_field(out, &efield, "", "", error_ret); +} + +void t_c_glib_generator::generate_serialize_list_element(ostream& out, + t_list* tlist, + string list, + string index, + int error_ret) { + t_type* ttype = get_true_type(tlist->get_elem_type()); + + // cast to non-const + string cast = ""; + string name = "g_ptr_array_index ((GPtrArray *) " + list + ", " + index + ")"; + + if (ttype->is_void()) { + throw std::runtime_error("compiler error: list element type cannot be void"); + } else if (is_numeric(ttype)) { + name = "g_array_index (" + list + ", " + base_type_name(ttype) + ", " + index + ")"; + } else if (ttype->is_string()) { + cast = "(gchar*)"; + } else if (ttype->is_map() || ttype->is_set()) { + cast = "(GHashTable*)"; + } else if (ttype->is_list()) { + t_type* etype = ((t_list*)ttype)->get_elem_type(); + if (etype->is_void()) { + throw std::runtime_error("compiler error: list element type cannot be void"); + } + cast = is_numeric(etype) ? "(GArray*)" : "(GPtrArray*)"; + } + + t_field efield(ttype, "(" + cast + name + ")"); + generate_serialize_field(out, &efield, "", "", error_ret); +} + +/* deserializes a field of any type. */ +void t_c_glib_generator::generate_deserialize_field(ostream& out, + t_field* tfield, + string prefix, + string suffix, + int error_ret, + bool allocate) { + t_type* type = get_true_type(tfield->get_type()); + + if (type->is_void()) { + throw std::runtime_error("CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " + prefix + + tfield->get_name()); + } + + string name = prefix + tfield->get_name() + suffix; + + if (type->is_struct() || type->is_xception()) { + generate_deserialize_struct(out, (t_struct*)type, name, error_ret, allocate); + } else if (type->is_container()) { + generate_deserialize_container(out, type, name, error_ret); + } else if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + if (tbase == t_base_type::TYPE_STRING) { + indent(out) << "if (" << name << " != NULL)" << endl << indent() << "{" << endl; + indent_up(); + indent(out) << "g_free(" << name << ");" << endl << indent() << name << " = NULL;" << endl; + indent_down(); + indent(out) << "}" << endl << endl; + } + indent(out) << "if ((ret = thrift_protocol_read_"; + + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + out << "binary (protocol, &data, &len"; + } else { + out << "string (protocol, &" << name; + } + break; + case t_base_type::TYPE_BOOL: + out << "bool (protocol, &" << name; + break; + case t_base_type::TYPE_I8: + out << "byte (protocol, &" << name; + break; + case t_base_type::TYPE_I16: + out << "i16 (protocol, &" << name; + break; + case t_base_type::TYPE_I32: + out << "i32 (protocol, &" << name; + break; + case t_base_type::TYPE_I64: + out << "i64 (protocol, &" << name; + break; + case t_base_type::TYPE_DOUBLE: + out << "double (protocol, &" << name; + break; + default: + throw "compiler error: no C reader for base type " + t_base_type::t_base_name(tbase) + name; + } + out << ", error)) < 0)" << endl; + out << indent() << " return " << error_ret << ";" << endl << indent() << "xfer += ret;" + << endl; + + // load the byte array with the data + if (tbase == t_base_type::TYPE_STRING && type->is_binary()) { + indent(out) << name << " = g_byte_array_new();" << endl; + indent(out) << "g_byte_array_append (" << name << ", (guint8 *) data, (guint) len);" << endl; + indent(out) << "g_free (data);" << endl; + } + } else if (type->is_enum()) { + string t = tmp("ecast"); + out << indent() << "gint32 " << t << ";" << endl << indent() + << "if ((ret = thrift_protocol_read_i32 (protocol, &" << t << ", error)) < 0)" << endl + << indent() << " return " << error_ret << ";" << endl << indent() << "xfer += ret;" << endl + << indent() << name << " = (" << type_name(type) << ")" << t << ";" << endl; + } else { + throw std::logic_error("DO NOT KNOW HOW TO SERIALIZE FIELD '" + tfield->get_name() + "' TYPE '" + + type_name(type)); + } + + // if the type is not required and this is a thrift struct (no prefix), + // set the isset variable. if the type is required, then set the + // local variable indicating the value was set, so that we can do // validation later. + if (prefix != "" && tfield->get_req() != t_field::T_REQUIRED) { + indent(out) << prefix << "__isset_" << tfield->get_name() << suffix << " = TRUE;" << endl; + } else if (prefix != "" && tfield->get_req() == t_field::T_REQUIRED) { + indent(out) << "isset_" << tfield->get_name() << " = TRUE;" << endl; + } +} + +void t_c_glib_generator::generate_deserialize_struct(ostream& out, + t_struct* tstruct, + string prefix, + int error_ret, + bool allocate) { + string name_uc = to_upper_case(initial_caps_to_underscores(tstruct->get_name())); + if (tstruct->is_xception()) { + out << indent() << "/* This struct is an exception */" << endl; + allocate = true; + } + + if (allocate) { + out << indent() << "if ( " << prefix << " != NULL)" << endl << indent() << "{" << endl; + indent_up(); + out << indent() << "g_object_unref (" << prefix << ");" << endl; + indent_down(); + out << indent() << "}" << endl << indent() << prefix << " = g_object_new (" << this->nspace_uc + << "TYPE_" << name_uc << ", NULL);" << endl; + } + out << indent() << "if ((ret = thrift_struct_read (THRIFT_STRUCT (" << prefix + << "), protocol, error)) < 0)" << endl << indent() << "{" << endl; + indent_up(); + if (allocate) { + indent(out) << "g_object_unref (" << prefix << ");" << endl; + if (tstruct->is_xception()) { + indent(out) << prefix << " = NULL;" << endl; + } + } + out << indent() << "return " << error_ret << ";" << endl; + indent_down(); + out << indent() << "}" << endl << indent() << "xfer += ret;" << endl; +} + +void t_c_glib_generator::generate_deserialize_container(ostream& out, + t_type* ttype, + string prefix, + int error_ret) { + scope_up(out); + + if (ttype->is_map()) { + out << indent() << "guint32 size;" << endl + << indent() << "guint32 i;" << endl + << indent() << "ThriftType key_type;" << endl + << indent() << "ThriftType value_type;" << endl + << endl + << indent() << "/* read the map begin marker */" << endl + << indent() << "if ((ret = thrift_protocol_read_map_begin (protocol, " + "&key_type, &value_type, &size, error)) < 0)" << endl; + indent_up(); + out << indent() << "return " << error_ret << ";" << endl; + indent_down(); + out << indent() << "xfer += ret;" << endl + << endl; + + // iterate over map elements + out << indent() << "/* iterate through each of the map's fields */" << endl + << indent() << "for (i = 0; i < size; i++)" << endl; + scope_up(out); + generate_deserialize_map_element(out, (t_map*)ttype, prefix, error_ret); + scope_down(out); + out << endl; + + // read map end + out << indent() << "/* read the map end marker */" << endl + << indent() << "if ((ret = thrift_protocol_read_map_end (protocol, " + "error)) < 0)" << endl; + indent_up(); + out << indent() << "return " << error_ret << ";" << endl; + indent_down(); + out << indent() << "xfer += ret;" << endl; + } else if (ttype->is_set()) { + out << indent() << "guint32 size;" << endl + << indent() << "guint32 i;" << endl + << indent() << "ThriftType element_type;" << endl + << endl + << indent() << "if ((ret = thrift_protocol_read_set_begin (protocol, " + "&element_type, &size, error)) < 0)" << endl; + indent_up(); + out << indent() << "return " << error_ret << ";" << endl; + indent_down(); + out << indent() << "xfer += ret;" << endl + << endl; + + // iterate over the elements + out << indent() << "/* iterate through the set elements */" << endl + << indent() << "for (i = 0; i < size; ++i)" << endl; + scope_up(out); + generate_deserialize_set_element(out, (t_set*)ttype, prefix, error_ret); + scope_down(out); + + // read set end + out << indent() << "if ((ret = thrift_protocol_read_set_end (protocol, " + "error)) < 0)" << endl; + indent_up(); + out << indent() << "return " << error_ret << ";" << endl; + indent_down(); + out << indent() << "xfer += ret;" << endl + << endl; + } else if (ttype->is_list()) { + out << indent() << "guint32 size;" << endl + << indent() << "guint32 i;" << endl + << indent() << "ThriftType element_type;" << endl + << endl + << indent() << "if ((ret = thrift_protocol_read_list_begin (protocol, " + "&element_type,&size, error)) < 0)" << endl; + indent_up(); + out << indent() << "return " << error_ret << ";" << endl; + indent_down(); + out << indent() << "xfer += ret;" << endl + << endl; + + // iterate over the elements + out << indent() << "/* iterate through list elements */" << endl + << indent() << "for (i = 0; i < size; i++)" << endl; + scope_up(out); + generate_deserialize_list_element(out, + (t_list*)ttype, + prefix, + "i", + error_ret); + scope_down(out); + + // read list end + out << indent() << "if ((ret = thrift_protocol_read_list_end (protocol, " + "error)) < 0)" << endl; + indent_up(); + out << indent() << "return " << error_ret << ";" << endl; + indent_down(); + out << indent() << "xfer += ret;" << endl; + } + + scope_down(out); +} + +void t_c_glib_generator::declare_local_variable(ostream& out, t_type* ttype, string& name, bool for_hash_table) { + string tname = type_name(ttype); + + /* If the given type is a typedef, find its underlying type so we + can correctly determine how to generate a pointer to it */ + ttype = get_true_type(ttype); + string ptr = !is_numeric(ttype) ? "" : "*"; + + if (ttype->is_map()) { + t_map* tmap = (t_map*)ttype; + out << indent() << tname << ptr << " " << name << " = " + << generate_new_hash_from_type(tmap->get_key_type(), tmap->get_val_type()) << endl; + } else if (ttype->is_list()) { + t_list* tlist = (t_list*)ttype; + out << indent() << tname << ptr << " " << name << " = " + << generate_new_array_from_type(tlist->get_elem_type()) << endl; + } else if (for_hash_table && ttype->is_enum()) { + out << indent() << tname << " " << name << ";" << endl; + } else { + out << indent() << tname << ptr << " " << name + << (ptr != "" ? " = g_new (" + tname + ", 1)" : " = NULL") << ";" << endl; + } +} + +void t_c_glib_generator::declore_local_variable_for_write(ostream& out, + t_type* ttype, + string& name) { + string tname = type_name(ttype); + ttype = get_true_type(ttype); + string ptr = ttype->is_string() || !ttype->is_base_type() ? " " : "* "; + string init_val = ttype->is_enum() ? "" : " = NULL"; + out << indent() << tname << ptr << name << init_val << ";" << endl; +} + +void t_c_glib_generator::generate_deserialize_map_element(ostream& out, + t_map* tmap, + string prefix, + int error_ret) { + t_type* tkey = tmap->get_key_type(); + t_type* tval = tmap->get_val_type(); + string keyname = tmp("key"); + string valname = tmp("val"); + + declare_local_variable(out, tkey, keyname, true); + declare_local_variable(out, tval, valname, true); + + /* If either the key or value type is a typedef, find its underlying + type so we can correctly determine how to generate a pointer to + it */ + tkey = get_true_type(tkey); + tval = get_true_type(tval); + + string tkey_ptr = tkey->is_string() || !tkey->is_base_type() ? "" : "*"; + string tval_ptr = tval->is_string() || !tval->is_base_type() ? "" : "*"; + + // deserialize the fields of the map element + t_field fkey(tkey, tkey_ptr + keyname); + generate_deserialize_field(out, &fkey, "", "", error_ret); + t_field fval(tval, tval_ptr + valname); + generate_deserialize_field(out, &fval, "", "", error_ret); + + indent(out) << "if (" << prefix << " && " << keyname << ")" << endl; + indent_up(); + indent(out) << "g_hash_table_insert ((GHashTable *)" << prefix << ", (gpointer) " << keyname + << ", (gpointer) " << valname << ");" << endl; + indent_down(); +} + +void t_c_glib_generator::generate_deserialize_set_element(ostream& out, + t_set* tset, + string prefix, + int error_ret) { + t_type* telem = tset->get_elem_type(); + string elem = tmp("_elem"); + string telem_ptr = telem->is_string() || !telem->is_base_type() ? "" : "*"; + + declare_local_variable(out, telem, elem, true); + + t_field felem(telem, telem_ptr + elem); + generate_deserialize_field(out, &felem, "", "", error_ret); + + indent(out) << "if (" << prefix << " && " << elem << ")" << endl; + indent_up(); + indent(out) << "g_hash_table_insert ((GHashTable *) " << prefix << ", (gpointer) " << elem + << ", (gpointer) " << elem << ");" << endl; + indent_down(); +} + +void t_c_glib_generator::generate_deserialize_list_element(ostream& out, + t_list* tlist, + string prefix, + string index, + int error_ret) { + (void)index; + t_type* ttype = get_true_type(tlist->get_elem_type()); + string elem = tmp("_elem"); + string telem_ptr = !is_numeric(ttype) ? "" : "*"; + + declare_local_variable(out, ttype, elem, false); + + t_field felem(ttype, telem_ptr + elem); + generate_deserialize_field(out, &felem, "", "", error_ret); + + if (ttype->is_void()) { + throw std::runtime_error("compiler error: list element type cannot be void"); + } else if (is_numeric(ttype)) { + indent(out) << "g_array_append_vals (" << prefix << ", " << elem << ", 1);" << endl; + } else { + indent(out) << "g_ptr_array_add (" << prefix << ", " << elem << ");" << endl; + } +} + +string t_c_glib_generator::generate_free_func_from_type(t_type* ttype) { + if (ttype == NULL) + return "NULL"; + + if (ttype->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)ttype)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot determine hash type"; + break; + case t_base_type::TYPE_BOOL: + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + case t_base_type::TYPE_DOUBLE: + return "g_free"; + case t_base_type::TYPE_STRING: + if (((t_base_type*)ttype)->is_binary()) { + return "thrift_string_free"; + } + return "g_free"; + default: + throw "compiler error: no hash table info for type"; + } + } else if (ttype->is_enum()) { + return "NULL"; + } else if (ttype->is_map() || ttype->is_set()) { + return "(GDestroyNotify) thrift_safe_hash_table_destroy"; + } else if (ttype->is_struct()) { + return "g_object_unref"; + } else if (ttype->is_list()) { + t_type* etype = ((t_list*)ttype)->get_elem_type(); + if (etype->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)etype)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot determine array type"; + break; + case t_base_type::TYPE_BOOL: + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + case t_base_type::TYPE_DOUBLE: + return "(GDestroyNotify) g_array_unref"; + case t_base_type::TYPE_STRING: + return "(GDestroyNotify) g_ptr_array_unref"; + default: + throw "compiler error: no array info for type"; + } + } else if (etype->is_container() || etype->is_struct()) { + return "(GDestroyNotify) g_ptr_array_unref"; + ; + } else if (etype->is_enum()) { + return "(GDestroyNotify) g_array_unref"; + } + printf("Type not expected inside the array: %s\n", etype->get_name().c_str()); + throw "Type not expected inside array"; + } else if (ttype->is_typedef()) { + return generate_free_func_from_type(((t_typedef*)ttype)->get_type()); + } + printf("Type not expected: %s\n", ttype->get_name().c_str()); + throw "Type not expected"; +} + +string t_c_glib_generator::generate_hash_func_from_type(t_type* ttype) { + if (ttype == NULL) + return "NULL"; + + if (ttype->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)ttype)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot determine hash type"; + break; + case t_base_type::TYPE_BOOL: + return "thrift_boolean_hash"; + case t_base_type::TYPE_I8: + return "thrift_int8_hash"; + case t_base_type::TYPE_I16: + return "thrift_int16_hash"; + case t_base_type::TYPE_I32: + return "g_int_hash"; + case t_base_type::TYPE_I64: + return "g_int64_hash"; + case t_base_type::TYPE_DOUBLE: + return "g_double_hash"; + case t_base_type::TYPE_STRING: + return "g_str_hash"; + default: + throw "compiler error: no hash table info for type"; + } + } else if (ttype->is_enum()) { + return "g_direct_hash"; + } else if (ttype->is_container() || ttype->is_struct()) { + return "g_direct_hash"; + } else if (ttype->is_typedef()) { + return generate_hash_func_from_type(((t_typedef*)ttype)->get_type()); + } + printf("Type not expected: %s\n", ttype->get_name().c_str()); + throw "Type not expected"; +} + +string t_c_glib_generator::generate_cmp_func_from_type(t_type* ttype) { + if (ttype == NULL) + return "NULL"; + + if (ttype->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)ttype)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot determine hash type"; + break; + case t_base_type::TYPE_BOOL: + return "thrift_boolean_equal"; + case t_base_type::TYPE_I8: + return "thrift_int8_equal"; + case t_base_type::TYPE_I16: + return "thrift_int16_equal"; + case t_base_type::TYPE_I32: + return "g_int_equal"; + case t_base_type::TYPE_I64: + return "g_int64_equal"; + case t_base_type::TYPE_DOUBLE: + return "g_double_equal"; + case t_base_type::TYPE_STRING: + return "g_str_equal"; + default: + throw "compiler error: no hash table info for type"; + } + } else if (ttype->is_enum()) { + return "g_direct_equal"; + } else if (ttype->is_container() || ttype->is_struct()) { + return "g_direct_equal"; + } else if (ttype->is_typedef()) { + return generate_cmp_func_from_type(((t_typedef*)ttype)->get_type()); + } + printf("Type not expected: %s\n", ttype->get_name().c_str()); + throw "Type not expected"; +} + +string t_c_glib_generator::generate_new_hash_from_type(t_type* key, t_type* value) { + string hash_func = generate_hash_func_from_type(key); + string cmp_func = generate_cmp_func_from_type(key); + string key_free_func = generate_free_func_from_type(key); + string value_free_func = generate_free_func_from_type(value); + + return "g_hash_table_new_full (" + hash_func + ", " + cmp_func + ", " + key_free_func + ", " + + value_free_func + ");"; +} + +string t_c_glib_generator::generate_new_array_from_type(t_type* ttype) { + if (ttype->is_void()) { + throw std::runtime_error("compiler error: cannot determine array type"); + } else if (is_numeric(ttype)) { + return "g_array_new (0, 1, sizeof (" + base_type_name(ttype) + "));"; + } else { + string free_func = generate_free_func_from_type(ttype); + return "g_ptr_array_new_with_free_func (" + free_func + ");"; + } +} + +/*************************************** + * UTILITY FUNCTIONS * + ***************************************/ + +/** + * Upper case a string. + */ +string to_upper_case(string name) { + string s(name); + std::transform(s.begin(), s.end(), s.begin(), ::toupper); + return s; +} + +/** + * Lower case a string. + */ +string to_lower_case(string name) { + string s(name); + std::transform(s.begin(), s.end(), s.begin(), ::tolower); + return s; +} + +/** + * Makes a string friendly to C code standards by lowercasing and adding + * underscores, with the exception of the first character. For example: + * + * Input: "ZomgCamelCase" + * Output: "zomg_camel_case" + */ +string initial_caps_to_underscores(string name) { + string ret; + const char* tmp = name.c_str(); + int pos = 0; + + /* the first character isn't underscored if uppercase, just lowercased */ + ret += tolower(tmp[pos]); + pos++; + for (unsigned int i = pos; i < name.length(); i++) { + char lc = tolower(tmp[i]); + if (lc != tmp[i]) { + ret += '_'; + } + ret += lc; + } + + return ret; +} + +/** + * Performs the reverse operation of initial_caps_to_underscores: The first + * character of the string is made uppercase, along with each character that + * follows an underscore (which is removed). Useful for converting Thrift + * service-method names into GObject-style class names. + * + * Input: "zomg_camel_case" + * Output: "ZomgCamelCase" + */ +string underscores_to_initial_caps(string name) { + string ret; + const char* tmp = name.c_str(); + bool uppercase_next = true; + + for (unsigned int i = 0; i < name.length(); i++) { + char c = tmp[i]; + if (c == '_') { + uppercase_next = true; + } else { + if (uppercase_next) { + ret += toupper(c); + uppercase_next = false; + } else { + ret += c; + } + } + } + + return ret; +} + +/* register this generator with the main program */ +THRIFT_REGISTER_GENERATOR(c_glib, "C, using GLib", "") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_cl_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_cl_generator.cc new file mode 100644 index 000000000..ad7c0ef17 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_cl_generator.cc @@ -0,0 +1,558 @@ +/* + * Copyright (c) 2008- Patrick Collison <patrick@collison.ie> + * Copyright (c) 2006- Facebook + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <string> +#include <fstream> +#include <iostream> +#include <vector> + +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sstream> +#include <string> +#include <algorithm> + +#include "thrift/platform.h" +#include "t_oop_generator.h" + +using namespace std; + + +/** + * Common Lisp code generator. + * + * @author Patrick Collison <patrick@collison.ie> + */ +class t_cl_generator : public t_oop_generator { + public: + t_cl_generator( + t_program* program, + const std::map<std::string, std::string>& parsed_options, + const std::string& option_string) + : t_oop_generator(program) + { + no_asd = false; + system_prefix = "thrift-gen-"; + + std::map<std::string, std::string>::const_iterator iter; + + for(iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + if(iter->first.compare("no_asd") == 0) { + no_asd = true; + } else if (iter->first.compare("sys_pref") == 0) { + system_prefix = iter->second; + } else { + throw "unknown option cl:" + iter->first; + } + } + + out_dir_base_ = "gen-cl"; + copy_options_ = option_string; + } + + void init_generator() override; + void close_generator() override; + + void generate_typedef (t_typedef* ttypedef) override; + void generate_enum (t_enum* tenum) override; + void generate_const (t_const* tconst) override; + void generate_struct (t_struct* tstruct) override; + void generate_xception (t_struct* txception) override; + void generate_service (t_service* tservice) override; + void generate_cl_struct (std::ostream& out, t_struct* tstruct, bool is_exception); + void generate_cl_struct_internal (std::ostream& out, t_struct* tstruct, bool is_exception); + void generate_exception_sig(std::ostream& out, t_function* f); + std::string render_const_value(t_type* type, t_const_value* value); + + std::string cl_autogen_comment(); + void asdf_def(std::ostream &out); + void package_def(std::ostream &out); + void package_in(std::ostream &out); + std::string generated_package(); + std::string prefix(std::string name); + std::string package_of(t_program* program); + std::string package(); + std::string render_includes(); + + std::string type_name(t_type* ttype); + std::string typespec (t_type *t); + std::string function_signature(t_function* tfunction); + std::string argument_list(t_struct* tstruct); + + std::string cl_docstring(std::string raw); + + private: + + int temporary_var; + /** + * Isolate the variable definitions, as they can require structure definitions + */ + ofstream_with_content_based_conditional_update f_asd_; + ofstream_with_content_based_conditional_update f_types_; + ofstream_with_content_based_conditional_update f_vars_; + + std::string copy_options_; + + bool no_asd; + std::string system_prefix; +}; + + +void t_cl_generator::init_generator() { + MKDIR(get_out_dir().c_str()); + string program_dir = get_out_dir() + "/" + program_name_; + MKDIR(program_dir.c_str()); + + temporary_var = 0; + + string f_types_name = program_dir + "/" + program_name_ + "-types.lisp"; + string f_vars_name = program_dir + "/" + program_name_ + "-vars.lisp"; + + f_types_.open(f_types_name); + f_types_ << cl_autogen_comment() << endl; + f_vars_.open(f_vars_name); + f_vars_ << cl_autogen_comment() << endl; + + package_def(f_types_); + package_in(f_types_); + package_in(f_vars_); + + if (!no_asd) { + string f_asd_name = program_dir + "/" + system_prefix + program_name_ + ".asd"; + f_asd_.open(f_asd_name); + f_asd_ << cl_autogen_comment() << endl; + asdf_def(f_asd_); + } +} + +/** + * Renders all the imports necessary for including another Thrift program + */ +string t_cl_generator::render_includes() { + const vector<t_program*>& includes = program_->get_includes(); + string result = ""; + result += ":depends-on (:thrift"; + for (auto include : includes) { + result += " :" + system_prefix + underscore(include->get_name()); + } + result += ")\n"; + return result; +} + +string t_cl_generator::package_of(t_program* program) { + string prefix = program->get_namespace("cl"); + return prefix.empty() ? "thrift-generated" : prefix; +} + +string t_cl_generator::package() { + return package_of(program_); +} + +string t_cl_generator::prefix(string symbol) { + return "\"" + symbol + "\""; +} + +string t_cl_generator::cl_autogen_comment() { + return + std::string(";;; ") + "Autogenerated by Thrift\n" + + ";;; DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n" + + ";;; options string: " + copy_options_ + "\n"; +} + +string t_cl_generator::cl_docstring(string raw) { + replace(raw.begin(), raw.end(), '"', '\''); + return raw; +} + + +void t_cl_generator::close_generator() { + f_asd_.close(); + f_types_.close(); + f_vars_.close(); +} + +string t_cl_generator::generated_package() { + return program_->get_namespace("cpp"); +} + +void t_cl_generator::asdf_def(std::ostream &out) { + out << "(asdf:defsystem #:" << system_prefix << program_name_ << endl; + indent_up(); + out << indent() << render_includes() + << indent() << ":serial t" << endl + << indent() << ":components (" + << "(:file \"" << program_name_ << "-types\") " + << "(:file \"" << program_name_ << "-vars\")))" << endl; + indent_down(); +} + +/*** + * Generate a package definition. Add use references equivalent to the idl file's include statements. + */ +void t_cl_generator::package_def(std::ostream &out) { + const vector<t_program*>& includes = program_->get_includes(); + + out << "(thrift:def-package :" << package(); + if ( includes.size() > 0 ) { + out << " :use ("; + for (auto include : includes) { + out << " :" << include->get_name(); + } + out << ")"; + } + out << ")" << endl << endl; +} + +void t_cl_generator::package_in(std::ostream &out) { + out << "(cl:in-package :" << package() << ")" << endl << endl; +} + +/** + * Generates a typedef. This is not done in Common Lisp, types are all implicit. + * + * @param ttypedef The type definition + */ +void t_cl_generator::generate_typedef(t_typedef* ttypedef) { + (void)ttypedef; +} + +void t_cl_generator::generate_enum(t_enum* tenum) { + f_types_ << "(thrift:def-enum " << prefix(tenum->get_name()) << endl; + + vector<t_enum_value*> constants = tenum->get_constants(); + vector<t_enum_value*>::iterator c_iter; + int value = -1; + + indent_up(); + f_types_ << indent() << "("; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + value = (*c_iter)->get_value(); + + if(c_iter != constants.begin()) f_types_ << endl << indent() << " "; + + f_types_ << "(\"" << (*c_iter)->get_name() << "\" . " << value << ")"; + } + indent_down(); + f_types_ << "))" << endl << endl; +} + +/** + * Generate a constant value + */ +void t_cl_generator::generate_const(t_const* tconst) { + t_type* type = tconst->get_type(); + string name = tconst->get_name(); + t_const_value* value = tconst->get_value(); + + f_vars_ << "(thrift:def-constant " << prefix(name) << " " << render_const_value(type, value) << ")" + << endl << endl; +} + +/** + * Prints the value of a constant with the given type. Note that type checking + * is NOT performed in this function as it is always run beforehand using the + * validate_types method in main.cc + */ +string t_cl_generator::render_const_value(t_type* type, t_const_value* value) { + type = get_true_type(type); + std::ostringstream out; + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + out << "\"" << value->get_string() << "\""; + break; + case t_base_type::TYPE_BOOL: + out << (value->get_integer() > 0 ? "t" : "nil"); + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + out << value->get_integer(); + break; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) { + out << value->get_integer(); + } else { + out << value->get_double(); + } + break; + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + indent(out) << value->get_integer(); + } else if (type->is_struct() || type->is_xception()) { + out << (type->is_struct() ? "(make-instance '" : "(make-exception '") << + lowercase(type->get_name()) << " " << endl; + indent_up(); + + const vector<t_field*>& fields = ((t_struct*)type)->get_members(); + vector<t_field*>::const_iterator f_iter; + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + t_type* field_type = NULL; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field_type = (*f_iter)->get_type(); + } + } + if (field_type == NULL) { + throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string(); + } + + out << indent() << ":" << v_iter->first->get_string() << " " << + render_const_value(field_type, v_iter->second) << endl; + } + out << indent() << ")"; + + indent_down(); + } else if (type->is_map()) { + // emit an hash form with both keys and values to be evaluated + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + out << "(thrift:map "; + indent_up(); + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + out << endl << indent() + << "(cl:cons " << render_const_value(ktype, v_iter->first) << " " + << render_const_value(vtype, v_iter->second) << ")"; + } + indent_down(); + out << indent() << ")"; + } else if (type->is_list() || type->is_set()) { + t_type* etype; + if (type->is_list()) { + etype = ((t_list*)type)->get_elem_type(); + } else { + etype = ((t_set*)type)->get_elem_type(); + } + if (type->is_set()) { + out << "(thrift:set" << endl; + } else { + out << "(thrift:list" << endl; + } + indent_up(); + indent_up(); + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + out << indent() << render_const_value(etype, *v_iter) << endl; + } + out << indent() << ")"; + indent_down(); + indent_down(); + } else { + throw "CANNOT GENERATE CONSTANT FOR TYPE: " + type->get_name(); + } + return out.str(); +} + +void t_cl_generator::generate_struct(t_struct* tstruct) { + generate_cl_struct(f_types_, tstruct, false); +} + +void t_cl_generator::generate_xception(t_struct* txception) { + generate_cl_struct(f_types_, txception, true); +} + +void t_cl_generator::generate_cl_struct_internal(std::ostream& out, t_struct* tstruct, bool is_exception) { + (void)is_exception; + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + out << "("; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_const_value* value = (*m_iter)->get_value(); + t_type* type = (*m_iter)->get_type(); + + if (m_iter != members.begin()) { + out << endl << indent() << " "; + } + out << "(" << prefix((*m_iter)->get_name()) << " " << + ( (NULL != value) ? render_const_value(type, value) : "nil" ) << + " :id " << (*m_iter)->get_key(); + if ( type->is_base_type() && "string" == typespec(type) ) + if ( ((t_base_type*)type)->is_binary() ) + out << " :type binary"; + else + out << " :type string"; + else + out << " :type " << typespec(type); + if ( (*m_iter)->get_req() == t_field::T_OPTIONAL ) { + out << " :optional t"; + } + if ( (*m_iter)->has_doc()) { + out << " :documentation \"" << cl_docstring((*m_iter)->get_doc()) << "\""; + } + out <<")"; + } + + out << ")"; +} + +void t_cl_generator::generate_cl_struct(std::ostream& out, t_struct* tstruct, bool is_exception = false) { + std::string name = type_name(tstruct); + out << (is_exception ? "(thrift:def-exception " : "(thrift:def-struct ") << + prefix(name) << endl; + indent_up(); + if ( tstruct->has_doc() ) { + out << indent() ; + out << "\"" << cl_docstring(tstruct->get_doc()) << "\"" << endl; + } + out << indent() ; + generate_cl_struct_internal(out, tstruct, is_exception); + indent_down(); + out << ")" << endl << endl; +} + +void t_cl_generator::generate_exception_sig(std::ostream& out, t_function* f) { + generate_cl_struct_internal(out, f->get_xceptions(), true); +} + +void t_cl_generator::generate_service(t_service* tservice) { + string extends_client; + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + if (tservice->get_extends() != NULL) { + extends_client = type_name(tservice->get_extends()); + } + + extends_client = extends_client.empty() ? "nil" : prefix(extends_client); + + f_types_ << "(thrift:def-service " << prefix(service_name_) << " " + << extends_client; + + indent_up(); + + if ( tservice->has_doc()) { + f_types_ << endl << indent() + << "(:documentation \"" << cl_docstring(tservice->get_doc()) << "\")"; + } + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_function* function = *f_iter; + string fname = function->get_name(); + string signature = function_signature(function); + t_struct* exceptions = function->get_xceptions(); + const vector<t_field*>& xmembers = exceptions->get_members(); + + f_types_ << endl << indent() << "(:method " << prefix(fname); + f_types_ << " (" << signature << " " << typespec((*f_iter)->get_returntype()) << ")"; + if (xmembers.size() > 0) { + f_types_ << endl << indent() << " :exceptions " ; + generate_exception_sig(f_types_, function); + } + if ( (*f_iter)->is_oneway() ) { + f_types_ << endl << indent() << " :oneway t"; + } + if ( (*f_iter)->has_doc() ) { + f_types_ << endl << indent() << " :documentation \"" + << cl_docstring((*f_iter)->get_doc()) << "\""; + } + f_types_ << ")"; + } + + f_types_ << ")" << endl << endl; + + indent_down(); +} + +string t_cl_generator::typespec(t_type *t) { + t = get_true_type(t); + + if (t -> is_binary()){ + return "binary"; + } else if (t->is_base_type()) { + return type_name(t); + } else if (t->is_map()) { + t_map *m = (t_map*) t; + return "(thrift:map " + typespec(m->get_key_type()) + " " + + typespec(m->get_val_type()) + ")"; + } else if (t->is_struct() || t->is_xception()) { + return "(struct " + prefix(type_name(t)) + ")"; + } else if (t->is_list()) { + return "(thrift:list " + typespec(((t_list*) t)->get_elem_type()) + ")"; + } else if (t->is_set()) { + return "(thrift:set " + typespec(((t_set*) t)->get_elem_type()) + ")"; + } else if (t->is_enum()) { + return "(enum \"" + ((t_enum*) t)->get_name() + "\")"; + } else { + throw "Sorry, I don't know how to generate this: " + type_name(t); + } +} + +string t_cl_generator::function_signature(t_function* tfunction) { + return argument_list(tfunction->get_arglist()); +} + +string t_cl_generator::argument_list(t_struct* tstruct) { + stringstream res; + res << "("; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + res << " "; + } + res << "(" + prefix((*f_iter)->get_name()) << " " << + typespec((*f_iter)->get_type()) << " " << + (*f_iter)->get_key() << ")"; + + + } + res << ")"; + return res.str(); +} + +string t_cl_generator::type_name(t_type* ttype) { + string prefix = ""; + t_program* program = ttype->get_program(); + + if (program != NULL && program != program_) + prefix = package_of(program) == package() ? "" : package_of(program) + ":"; + + string name = ttype->get_name(); + + if (ttype->is_struct() || ttype->is_xception()) + name = lowercase(ttype->get_name()); + + return prefix + name; +} + +THRIFT_REGISTER_GENERATOR( + cl, + "Common Lisp", + " no_asd: Do not define ASDF systems for each generated Thrift program.\n" + " sys_pref= The prefix to give ASDF system names. Default: thrift-gen-\n") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_cpp_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_cpp_generator.cc new file mode 100644 index 000000000..d66b6e6e1 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_cpp_generator.cc @@ -0,0 +1,4554 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +#include <cassert> + +#include <fstream> +#include <iomanip> +#include <iostream> +#include <limits> +#include <sstream> +#include <string> +#include <vector> + +#include <sys/stat.h> + +#include "thrift/platform.h" +#include "thrift/generate/t_oop_generator.h" + +using std::map; +using std::ofstream; +using std::ostream; +using std::string; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes + +/** + * C++ code generator. This is legitimacy incarnate. + * + */ +class t_cpp_generator : public t_oop_generator { +public: + t_cpp_generator(t_program* program, + const std::map<std::string, std::string>& parsed_options, + const std::string& option_string) + : t_oop_generator(program) { + (void)option_string; + std::map<std::string, std::string>::const_iterator iter; + + + gen_pure_enums_ = false; + use_include_prefix_ = false; + gen_cob_style_ = false; + gen_no_client_completion_ = false; + gen_no_default_operators_ = false; + gen_templates_ = false; + gen_templates_only_ = false; + gen_moveable_ = false; + gen_no_ostream_operators_ = false; + gen_no_skeleton_ = false; + + for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + if( iter->first.compare("pure_enums") == 0) { + gen_pure_enums_ = true; + } else if( iter->first.compare("include_prefix") == 0) { + use_include_prefix_ = true; + } else if( iter->first.compare("cob_style") == 0) { + gen_cob_style_ = true; + } else if( iter->first.compare("no_client_completion") == 0) { + gen_no_client_completion_ = true; + } else if( iter->first.compare("no_default_operators") == 0) { + gen_no_default_operators_ = true; + } else if( iter->first.compare("templates") == 0) { + gen_templates_ = true; + gen_templates_only_ = (iter->second == "only"); + } else if( iter->first.compare("moveable_types") == 0) { + gen_moveable_ = true; + } else if ( iter->first.compare("no_ostream_operators") == 0) { + gen_no_ostream_operators_ = true; + } else if ( iter->first.compare("no_skeleton") == 0) { + gen_no_skeleton_ = true; + } else { + throw "unknown option cpp:" + iter->first; + } + } + + out_dir_base_ = "gen-cpp"; + } + + /** + * Init and close methods + */ + + void init_generator() override; + void close_generator() override; + + void generate_consts(std::vector<t_const*> consts) override; + + /** + * Program-level generation functions + */ + + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_enum_ostream_operator_decl(std::ostream& out, t_enum* tenum); + void generate_enum_ostream_operator(std::ostream& out, t_enum* tenum); + void generate_enum_to_string_helper_function_decl(std::ostream& out, t_enum* tenum); + void generate_enum_to_string_helper_function(std::ostream& out, t_enum* tenum); + void generate_forward_declaration(t_struct* tstruct) override; + void generate_struct(t_struct* tstruct) override { generate_cpp_struct(tstruct, false); } + void generate_xception(t_struct* txception) override { generate_cpp_struct(txception, true); } + void generate_cpp_struct(t_struct* tstruct, bool is_exception); + + void generate_service(t_service* tservice) override; + + void print_const_value(std::ostream& out, std::string name, t_type* type, t_const_value* value); + std::string render_const_value(std::ostream& out, + std::string name, + t_type* type, + t_const_value* value); + + void generate_struct_declaration(std::ostream& out, + t_struct* tstruct, + bool is_exception = false, + bool pointers = false, + bool read = true, + bool write = true, + bool swap = false, + bool is_user_struct = false); + void generate_struct_definition(std::ostream& out, + std::ostream& force_cpp_out, + t_struct* tstruct, + bool setters = true, + bool is_user_struct = false); + void generate_copy_constructor(std::ostream& out, t_struct* tstruct, bool is_exception); + void generate_move_constructor(std::ostream& out, t_struct* tstruct, bool is_exception); + void generate_constructor_helper(std::ostream& out, + t_struct* tstruct, + bool is_excpetion, + bool is_move); + void generate_assignment_operator(std::ostream& out, t_struct* tstruct); + void generate_move_assignment_operator(std::ostream& out, t_struct* tstruct); + void generate_assignment_helper(std::ostream& out, t_struct* tstruct, bool is_move); + void generate_struct_reader(std::ostream& out, t_struct* tstruct, bool pointers = false); + void generate_struct_writer(std::ostream& out, t_struct* tstruct, bool pointers = false); + void generate_struct_result_writer(std::ostream& out, t_struct* tstruct, bool pointers = false); + void generate_struct_swap(std::ostream& out, t_struct* tstruct); + void generate_struct_print_method(std::ostream& out, t_struct* tstruct); + void generate_exception_what_method(std::ostream& out, t_struct* tstruct); + + /** + * Service-level generation functions + */ + + void generate_service_interface(t_service* tservice, string style); + void generate_service_interface_factory(t_service* tservice, string style); + void generate_service_null(t_service* tservice, string style); + void generate_service_multiface(t_service* tservice); + void generate_service_helpers(t_service* tservice); + void generate_service_client(t_service* tservice, string style); + void generate_service_processor(t_service* tservice, string style); + void generate_service_skeleton(t_service* tservice); + void generate_process_function(t_service* tservice, + t_function* tfunction, + string style, + bool specialized = false); + void generate_function_helpers(t_service* tservice, t_function* tfunction); + void generate_service_async_skeleton(t_service* tservice); + + /** + * Serialization constructs + */ + + void generate_deserialize_field(std::ostream& out, + t_field* tfield, + std::string prefix = "", + std::string suffix = ""); + + void generate_deserialize_struct(std::ostream& out, + t_struct* tstruct, + std::string prefix = "", + bool pointer = false); + + void generate_deserialize_container(std::ostream& out, t_type* ttype, std::string prefix = ""); + + void generate_deserialize_set_element(std::ostream& out, t_set* tset, std::string prefix = ""); + + void generate_deserialize_map_element(std::ostream& out, t_map* tmap, std::string prefix = ""); + + void generate_deserialize_list_element(std::ostream& out, + t_list* tlist, + std::string prefix, + bool push_back, + std::string index); + + void generate_serialize_field(std::ostream& out, + t_field* tfield, + std::string prefix = "", + std::string suffix = ""); + + void generate_serialize_struct(std::ostream& out, + t_struct* tstruct, + std::string prefix = "", + bool pointer = false); + + void generate_serialize_container(std::ostream& out, t_type* ttype, std::string prefix = ""); + + void generate_serialize_map_element(std::ostream& out, t_map* tmap, std::string iter); + + void generate_serialize_set_element(std::ostream& out, t_set* tmap, std::string iter); + + void generate_serialize_list_element(std::ostream& out, t_list* tlist, std::string iter); + + void generate_function_call(ostream& out, + t_function* tfunction, + string target, + string iface, + string arg_prefix); + /* + * Helper rendering functions + */ + + std::string namespace_prefix(std::string ns); + std::string namespace_open(std::string ns); + std::string namespace_close(std::string ns); + std::string type_name(t_type* ttype, bool in_typedef = false, bool arg = false); + std::string base_type_name(t_base_type::t_base tbase); + std::string declare_field(t_field* tfield, + bool init = false, + bool pointer = false, + bool constant = false, + bool reference = false); + std::string function_signature(t_function* tfunction, + std::string style, + std::string prefix = "", + bool name_params = true); + std::string cob_function_signature(t_function* tfunction, + std::string prefix = "", + bool name_params = true); + std::string argument_list(t_struct* tstruct, bool name_params = true, bool start_comma = false); + std::string type_to_enum(t_type* ttype); + + void generate_enum_constant_list(std::ostream& f, + const vector<t_enum_value*>& constants, + const char* prefix, + const char* suffix, + bool include_values); + + void generate_struct_ostream_operator_decl(std::ostream& f, t_struct* tstruct); + void generate_struct_ostream_operator(std::ostream& f, t_struct* tstruct); + void generate_struct_print_method_decl(std::ostream& f, t_struct* tstruct); + void generate_exception_what_method_decl(std::ostream& f, + t_struct* tstruct, + bool external = false); + + bool is_reference(t_field* tfield) { return tfield->get_reference(); } + + bool is_complex_type(t_type* ttype) { + ttype = get_true_type(ttype); + + return ttype->is_container() || ttype->is_struct() || ttype->is_xception() + || (ttype->is_base_type() + && (((t_base_type*)ttype)->get_base() == t_base_type::TYPE_STRING)); + } + + void set_use_include_prefix(bool use_include_prefix) { use_include_prefix_ = use_include_prefix; } + + /** + * The compiler option "no_thrift_ostream_impl" can be used to prevent + * the compiler from emitting implementations for operator <<. In this + * case the consuming application must provide any needed to build. + * + * To disable this on a per structure bases, one can alternatively set + * the annotation "cpp.customostream" to prevent thrift from emitting an + * operator << (std::ostream&). + * + * See AnnotationTest for validation of this annotation feature. + */ + bool has_custom_ostream(t_type* ttype) const { + return (gen_no_ostream_operators_) || + (ttype->annotations_.find("cpp.customostream") != ttype->annotations_.end()); + } + +private: + /** + * Returns the include prefix to use for a file generated by program, or the + * empty string if no include prefix should be used. + */ + std::string get_include_prefix(const t_program& program) const; + + /** + * True if we should generate pure enums for Thrift enums, instead of wrapper classes. + */ + bool gen_pure_enums_; + + /** + * True if we should generate templatized reader/writer methods. + */ + bool gen_templates_; + + /** + * True iff we should generate process function pointers for only templatized + * reader/writer methods. + */ + bool gen_templates_only_; + + /** + * True if we should generate move constructors & assignment operators. + */ + bool gen_moveable_; + + /** + * True if we should generate ostream definitions + */ + bool gen_no_ostream_operators_; + + /** + * True iff we should use a path prefix in our #include statements for other + * thrift-generated header files. + */ + bool use_include_prefix_; + + /** + * True if we should generate "Continuation OBject"-style classes as well. + */ + bool gen_cob_style_; + + /** + * True if we should omit calls to completion__() in CobClient class. + */ + bool gen_no_client_completion_; + + /** + * True if we should omit generating the default opeartors ==, != and <. + */ + bool gen_no_default_operators_; + + /** + * True if we should generate skeleton. + */ + bool gen_no_skeleton_; + + /** + * Strings for namespace, computed once up front then used directly + */ + + std::string ns_open_; + std::string ns_close_; + + /** + * File streams, stored here to avoid passing them as parameters to every + * function. + */ + + ofstream_with_content_based_conditional_update f_types_; + ofstream_with_content_based_conditional_update f_types_impl_; + ofstream_with_content_based_conditional_update f_types_tcc_; + ofstream_with_content_based_conditional_update f_header_; + ofstream_with_content_based_conditional_update f_service_; + ofstream_with_content_based_conditional_update f_service_tcc_; + + // The ProcessorGenerator is used to generate parts of the code, + // so it needs access to many of our protected members and methods. + // + // TODO: The code really should be cleaned up so that helper methods for + // writing to the output files are separate from the generator classes + // themselves. + friend class ProcessorGenerator; +}; + +/** + * Prepares for file generation by opening up the necessary file output + * streams. + */ +void t_cpp_generator::init_generator() { + // Make output directory + MKDIR(get_out_dir().c_str()); + + // Make output file + string f_types_name = get_out_dir() + program_name_ + "_types.h"; + f_types_.open(f_types_name); + + string f_types_impl_name = get_out_dir() + program_name_ + "_types.cpp"; + f_types_impl_.open(f_types_impl_name.c_str()); + + if (gen_templates_) { + // If we don't open the stream, it appears to just discard data, + // which is fine. + string f_types_tcc_name = get_out_dir() + program_name_ + "_types.tcc"; + f_types_tcc_.open(f_types_tcc_name.c_str()); + } + + // Print header + f_types_ << autogen_comment(); + f_types_impl_ << autogen_comment(); + f_types_tcc_ << autogen_comment(); + + // Start ifndef + f_types_ << "#ifndef " << program_name_ << "_TYPES_H" << endl << "#define " << program_name_ + << "_TYPES_H" << endl << endl; + f_types_tcc_ << "#ifndef " << program_name_ << "_TYPES_TCC" << endl << "#define " << program_name_ + << "_TYPES_TCC" << endl << endl; + + // Include base types + f_types_ << "#include <iosfwd>" << endl + << endl + << "#include <thrift/Thrift.h>" << endl + << "#include <thrift/TApplicationException.h>" << endl + << "#include <thrift/TBase.h>" << endl + << "#include <thrift/protocol/TProtocol.h>" << endl + << "#include <thrift/transport/TTransport.h>" << endl + << endl; + // Include C++xx compatibility header + f_types_ << "#include <functional>" << endl; + f_types_ << "#include <memory>" << endl; + + // Include other Thrift includes + const vector<t_program*>& includes = program_->get_includes(); + for (auto include : includes) { + f_types_ << "#include \"" << get_include_prefix(*include) << include->get_name() + << "_types.h\"" << endl; + + // XXX(simpkins): If gen_templates_ is enabled, we currently assume all + // included files were also generated with templates enabled. + f_types_tcc_ << "#include \"" << get_include_prefix(*include) << include->get_name() + << "_types.tcc\"" << endl; + } + f_types_ << endl; + + // Include custom headers + const vector<string>& cpp_includes = program_->get_cpp_includes(); + for (const auto & cpp_include : cpp_includes) { + if (cpp_include[0] == '<') { + f_types_ << "#include " << cpp_include << endl; + } else { + f_types_ << "#include \"" << cpp_include << "\"" << endl; + } + } + f_types_ << endl; + + // Include the types file + f_types_impl_ << "#include \"" << get_include_prefix(*get_program()) << program_name_ + << "_types.h\"" << endl << endl; + f_types_tcc_ << "#include \"" << get_include_prefix(*get_program()) << program_name_ + << "_types.h\"" << endl << endl; + + // The swap() code needs <algorithm> for std::swap() + f_types_impl_ << "#include <algorithm>" << endl; + // for operator<< + f_types_impl_ << "#include <ostream>" << endl << endl; + f_types_impl_ << "#include <thrift/TToString.h>" << endl << endl; + + // Open namespace + ns_open_ = namespace_open(program_->get_namespace("cpp")); + ns_close_ = namespace_close(program_->get_namespace("cpp")); + + f_types_ << ns_open_ << endl << endl; + + f_types_impl_ << ns_open_ << endl << endl; + + f_types_tcc_ << ns_open_ << endl << endl; +} + +/** + * Closes the output files. + */ +void t_cpp_generator::close_generator() { + // Close namespace + f_types_ << ns_close_ << endl << endl; + f_types_impl_ << ns_close_ << endl; + f_types_tcc_ << ns_close_ << endl << endl; + + // Include the types.tcc file from the types header file, + // so clients don't have to explicitly include the tcc file. + // TODO(simpkins): Make this a separate option. + if (gen_templates_) { + f_types_ << "#include \"" << get_include_prefix(*get_program()) << program_name_ + << "_types.tcc\"" << endl << endl; + } + + // Close ifndef + f_types_ << "#endif" << endl; + f_types_tcc_ << "#endif" << endl; + + // Close output file + f_types_.close(); + f_types_impl_.close(); + f_types_tcc_.close(); +} + +/** + * Generates a typedef. This is just a simple 1-liner in C++ + * + * @param ttypedef The type definition + */ +void t_cpp_generator::generate_typedef(t_typedef* ttypedef) { + f_types_ << indent() << "typedef " << type_name(ttypedef->get_type(), true) << " " + << ttypedef->get_symbolic() << ";" << endl << endl; +} + +void t_cpp_generator::generate_enum_constant_list(std::ostream& f, + const vector<t_enum_value*>& constants, + const char* prefix, + const char* suffix, + bool include_values) { + f << " {" << endl; + indent_up(); + + vector<t_enum_value*>::const_iterator c_iter; + bool first = true; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + if (first) { + first = false; + } else { + f << "," << endl; + } + indent(f) << prefix << (*c_iter)->get_name() << suffix; + if (include_values) { + f << " = " << (*c_iter)->get_value(); + } + } + + f << endl; + indent_down(); + indent(f) << "};" << endl; +} + +/** + * Generates code for an enumerated type. In C++, this is essentially the same + * as the thrift definition itself, using the enum keyword in C++. + * + * @param tenum The enumeration + */ +void t_cpp_generator::generate_enum(t_enum* tenum) { + vector<t_enum_value*> constants = tenum->get_constants(); + + std::string enum_name = tenum->get_name(); + if (!gen_pure_enums_) { + enum_name = "type"; + f_types_ << indent() << "struct " << tenum->get_name() << " {" << endl; + indent_up(); + } + f_types_ << indent() << "enum " << enum_name; + + generate_enum_constant_list(f_types_, constants, "", "", true); + + if (!gen_pure_enums_) { + indent_down(); + f_types_ << "};" << endl; + } + + f_types_ << endl; + + /** + Generate a character array of enum names for debugging purposes. + */ + std::string prefix = ""; + if (!gen_pure_enums_) { + prefix = tenum->get_name() + "::"; + } + + f_types_impl_ << indent() << "int _k" << tenum->get_name() << "Values[] ="; + generate_enum_constant_list(f_types_impl_, constants, prefix.c_str(), "", false); + + f_types_impl_ << indent() << "const char* _k" << tenum->get_name() << "Names[] ="; + generate_enum_constant_list(f_types_impl_, constants, "\"", "\"", false); + + f_types_ << indent() << "extern const std::map<int, const char*> _" << tenum->get_name() + << "_VALUES_TO_NAMES;" << endl << endl; + + f_types_impl_ << indent() << "const std::map<int, const char*> _" << tenum->get_name() + << "_VALUES_TO_NAMES(::apache::thrift::TEnumIterator(" << constants.size() << ", _k" + << tenum->get_name() << "Values" + << ", _k" << tenum->get_name() << "Names), " + << "::apache::thrift::TEnumIterator(-1, NULL, NULL));" << endl << endl; + + generate_enum_ostream_operator_decl(f_types_, tenum); + generate_enum_ostream_operator(f_types_impl_, tenum); + + generate_enum_to_string_helper_function_decl(f_types_, tenum); + generate_enum_to_string_helper_function(f_types_impl_, tenum); +} + +void t_cpp_generator::generate_enum_ostream_operator_decl(std::ostream& out, t_enum* tenum) { + + out << "std::ostream& operator<<(std::ostream& out, const "; + if (gen_pure_enums_) { + out << tenum->get_name(); + } else { + out << tenum->get_name() << "::type&"; + } + out << " val);" << endl; + out << endl; +} + +void t_cpp_generator::generate_enum_ostream_operator(std::ostream& out, t_enum* tenum) { + + // If we've been told the consuming application will provide an ostream + // operator definition then we only make a declaration: + + if (!has_custom_ostream(tenum)) { + out << "std::ostream& operator<<(std::ostream& out, const "; + if (gen_pure_enums_) { + out << tenum->get_name(); + } else { + out << tenum->get_name() << "::type&"; + } + out << " val) "; + scope_up(out); + + out << indent() << "std::map<int, const char*>::const_iterator it = _" + << tenum->get_name() << "_VALUES_TO_NAMES.find(val);" << endl; + out << indent() << "if (it != _" << tenum->get_name() << "_VALUES_TO_NAMES.end()) {" << endl; + indent_up(); + out << indent() << "out << it->second;" << endl; + indent_down(); + out << indent() << "} else {" << endl; + indent_up(); + out << indent() << "out << static_cast<int>(val);" << endl; + indent_down(); + out << indent() << "}" << endl; + + out << indent() << "return out;" << endl; + scope_down(out); + out << endl; + } +} + +void t_cpp_generator::generate_enum_to_string_helper_function_decl(std::ostream& out, t_enum* tenum) { + out << "std::string to_string(const "; + if (gen_pure_enums_) { + out << tenum->get_name(); + } else { + out << tenum->get_name() << "::type&"; + } + out << " val);" << endl; + out << endl; +} + +void t_cpp_generator::generate_enum_to_string_helper_function(std::ostream& out, t_enum* tenum) { + if (!has_custom_ostream(tenum)) { + out << "std::string to_string(const "; + if (gen_pure_enums_) { + out << tenum->get_name(); + } else { + out << tenum->get_name() << "::type&"; + } + out << " val) " ; + scope_up(out); + + out << indent() << "std::map<int, const char*>::const_iterator it = _" + << tenum->get_name() << "_VALUES_TO_NAMES.find(val);" << endl; + out << indent() << "if (it != _" << tenum->get_name() << "_VALUES_TO_NAMES.end()) {" << endl; + indent_up(); + out << indent() << "return std::string(it->second);" << endl; + indent_down(); + out << indent() << "} else {" << endl; + indent_up(); + out << indent() << "return std::to_string(static_cast<int>(val));" << endl; + indent_down(); + out << indent() << "}" << endl; + + scope_down(out); + out << endl; + } +} + +/** + * Generates a class that holds all the constants. + */ +void t_cpp_generator::generate_consts(std::vector<t_const*> consts) { + string f_consts_name = get_out_dir() + program_name_ + "_constants.h"; + ofstream_with_content_based_conditional_update f_consts; + f_consts.open(f_consts_name); + + string f_consts_impl_name = get_out_dir() + program_name_ + "_constants.cpp"; + ofstream_with_content_based_conditional_update f_consts_impl; + f_consts_impl.open(f_consts_impl_name); + + // Print header + f_consts << autogen_comment(); + f_consts_impl << autogen_comment(); + + // Start ifndef + f_consts << "#ifndef " << program_name_ << "_CONSTANTS_H" << endl << "#define " << program_name_ + << "_CONSTANTS_H" << endl << endl << "#include \"" << get_include_prefix(*get_program()) + << program_name_ << "_types.h\"" << endl << endl << ns_open_ << endl << endl; + + f_consts_impl << "#include \"" << get_include_prefix(*get_program()) << program_name_ + << "_constants.h\"" << endl << endl << ns_open_ << endl << endl; + + f_consts << "class " << program_name_ << "Constants {" << endl << " public:" << endl << " " + << program_name_ << "Constants();" << endl << endl; + indent_up(); + vector<t_const*>::iterator c_iter; + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + string name = (*c_iter)->get_name(); + t_type* type = (*c_iter)->get_type(); + f_consts << indent() << type_name(type) << " " << name << ";" << endl; + } + indent_down(); + f_consts << "};" << endl; + + f_consts_impl << "const " << program_name_ << "Constants g_" << program_name_ << "_constants;" + << endl << endl << program_name_ << "Constants::" << program_name_ + << "Constants() {" << endl; + indent_up(); + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + print_const_value(f_consts_impl, + (*c_iter)->get_name(), + (*c_iter)->get_type(), + (*c_iter)->get_value()); + } + indent_down(); + indent(f_consts_impl) << "}" << endl; + + f_consts << endl << "extern const " << program_name_ << "Constants g_" << program_name_ + << "_constants;" << endl << endl << ns_close_ << endl << endl << "#endif" << endl; + f_consts.close(); + + f_consts_impl << endl << ns_close_ << endl << endl; + f_consts_impl.close(); +} + +/** + * Prints the value of a constant with the given type. Note that type checking + * is NOT performed in this function as it is always run beforehand using the + * validate_types method in main.cc + */ +void t_cpp_generator::print_const_value(ostream& out, + string name, + t_type* type, + t_const_value* value) { + type = get_true_type(type); + if (type->is_base_type()) { + string v2 = render_const_value(out, name, type, value); + indent(out) << name << " = " << v2 << ";" << endl << endl; + } else if (type->is_enum()) { + indent(out) << name << " = (" << type_name(type) << ")" << value->get_integer() << ";" << endl + << endl; + } else if (type->is_struct() || type->is_xception()) { + const vector<t_field*>& fields = ((t_struct*)type)->get_members(); + vector<t_field*>::const_iterator f_iter; + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + bool is_nonrequired_field = false; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + t_type* field_type = NULL; + is_nonrequired_field = false; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field_type = (*f_iter)->get_type(); + is_nonrequired_field = (*f_iter)->get_req() != t_field::T_REQUIRED; + } + } + if (field_type == NULL) { + throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string(); + } + string val = render_const_value(out, name, field_type, v_iter->second); + indent(out) << name << "." << v_iter->first->get_string() << " = " << val << ";" << endl; + if (is_nonrequired_field) { + indent(out) << name << ".__isset." << v_iter->first->get_string() << " = true;" << endl; + } + } + out << endl; + } else if (type->is_map()) { + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string key = render_const_value(out, name, ktype, v_iter->first); + string val = render_const_value(out, name, vtype, v_iter->second); + indent(out) << name << ".insert(std::make_pair(" << key << ", " << val << "));" << endl; + } + out << endl; + } else if (type->is_list()) { + t_type* etype = ((t_list*)type)->get_elem_type(); + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string val = render_const_value(out, name, etype, *v_iter); + indent(out) << name << ".push_back(" << val << ");" << endl; + } + out << endl; + } else if (type->is_set()) { + t_type* etype = ((t_set*)type)->get_elem_type(); + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string val = render_const_value(out, name, etype, *v_iter); + indent(out) << name << ".insert(" << val << ");" << endl; + } + out << endl; + } else { + throw "INVALID TYPE IN print_const_value: " + type->get_name(); + } +} + +/** + * + */ +string t_cpp_generator::render_const_value(ostream& out, + string name, + t_type* type, + t_const_value* value) { + (void)name; + std::ostringstream render; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + render << '"' << get_escaped_string(value) << '"'; + break; + case t_base_type::TYPE_BOOL: + render << ((value->get_integer() > 0) ? "true" : "false"); + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + render << value->get_integer(); + break; + case t_base_type::TYPE_I64: + render << value->get_integer() << "LL"; + break; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) { + render << "static_cast<double>(" << value->get_integer() << ")"; + } else { + render << emit_double_as_string(value->get_double()); + } + break; + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + render << "(" << type_name(type) << ")" << value->get_integer(); + } else { + string t = tmp("tmp"); + indent(out) << type_name(type) << " " << t << ";" << endl; + print_const_value(out, t, type, value); + render << t; + } + + return render.str(); +} + +void t_cpp_generator::generate_forward_declaration(t_struct* tstruct) { + // Forward declare struct def + f_types_ << indent() << "class " << tstruct->get_name() << ";" << endl << endl; +} + +/** + * Generates a struct definition for a thrift data type. This is a class + * with data members and a read/write() function, plus a mirroring isset + * inner class. + * + * @param tstruct The struct definition + */ +void t_cpp_generator::generate_cpp_struct(t_struct* tstruct, bool is_exception) { + generate_struct_declaration(f_types_, tstruct, is_exception, false, true, true, true, true); + generate_struct_definition(f_types_impl_, f_types_impl_, tstruct, true, true); + + std::ostream& out = (gen_templates_ ? f_types_tcc_ : f_types_impl_); + generate_struct_reader(out, tstruct); + generate_struct_writer(out, tstruct); + generate_struct_swap(f_types_impl_, tstruct); + generate_copy_constructor(f_types_impl_, tstruct, is_exception); + if (gen_moveable_) { + generate_move_constructor(f_types_impl_, tstruct, is_exception); + } + generate_assignment_operator(f_types_impl_, tstruct); + if (gen_moveable_) { + generate_move_assignment_operator(f_types_impl_, tstruct); + } + + if (!has_custom_ostream(tstruct)) { + generate_struct_print_method(f_types_impl_, tstruct); + } + + if (is_exception) { + generate_exception_what_method(f_types_impl_, tstruct); + } +} + +void t_cpp_generator::generate_copy_constructor(ostream& out, + t_struct* tstruct, + bool is_exception) { + generate_constructor_helper(out, tstruct, is_exception, /*is_move=*/false); +} + +void t_cpp_generator::generate_move_constructor(ostream& out, + t_struct* tstruct, + bool is_exception) { + generate_constructor_helper(out, tstruct, is_exception, /*is_move=*/true); +} + +namespace { +// Helper to convert a variable to rvalue, if move is enabled +std::string maybeMove(std::string const& other, bool move) { + if (move) { + return "std::move(" + other + ")"; + } + return other; +} +} + +void t_cpp_generator::generate_constructor_helper(ostream& out, + t_struct* tstruct, + bool is_exception, + bool is_move) { + + std::string tmp_name = tmp("other"); + + indent(out) << tstruct->get_name() << "::" << tstruct->get_name(); + + if (is_move) { + out << "( " << tstruct->get_name() << "&& "; + } else { + out << "(const " << tstruct->get_name() << "& "; + } + out << tmp_name << ") "; + if (is_exception) + out << ": TException() "; + out << "{" << endl; + indent_up(); + + const vector<t_field*>& members = tstruct->get_members(); + + // eliminate compiler unused warning + if (members.empty()) + indent(out) << "(void) " << tmp_name << ";" << endl; + + vector<t_field*>::const_iterator f_iter; + bool has_nonrequired_fields = false; + for (f_iter = members.begin(); f_iter != members.end(); ++f_iter) { + if ((*f_iter)->get_req() != t_field::T_REQUIRED) + has_nonrequired_fields = true; + indent(out) << (*f_iter)->get_name() << " = " + << maybeMove(tmp_name + "." + (*f_iter)->get_name(), is_move) << ";" << endl; + } + + if (has_nonrequired_fields) { + indent(out) << "__isset = " << maybeMove(tmp_name + ".__isset", is_move) << ";" << endl; + } + + indent_down(); + indent(out) << "}" << endl; +} + +void t_cpp_generator::generate_assignment_operator(ostream& out, t_struct* tstruct) { + generate_assignment_helper(out, tstruct, /*is_move=*/false); +} + +void t_cpp_generator::generate_move_assignment_operator(ostream& out, t_struct* tstruct) { + generate_assignment_helper(out, tstruct, /*is_move=*/true); +} + +void t_cpp_generator::generate_assignment_helper(ostream& out, t_struct* tstruct, bool is_move) { + std::string tmp_name = tmp("other"); + + indent(out) << tstruct->get_name() << "& " << tstruct->get_name() << "::operator=("; + + if (is_move) { + out << tstruct->get_name() << "&& "; + } else { + out << "const " << tstruct->get_name() << "& "; + } + out << tmp_name << ") {" << endl; + + indent_up(); + + const vector<t_field*>& members = tstruct->get_members(); + + // eliminate compiler unused warning + if (members.empty()) + indent(out) << "(void) " << tmp_name << ";" << endl; + + vector<t_field*>::const_iterator f_iter; + bool has_nonrequired_fields = false; + for (f_iter = members.begin(); f_iter != members.end(); ++f_iter) { + if ((*f_iter)->get_req() != t_field::T_REQUIRED) + has_nonrequired_fields = true; + indent(out) << (*f_iter)->get_name() << " = " + << maybeMove(tmp_name + "." + (*f_iter)->get_name(), is_move) << ";" << endl; + } + if (has_nonrequired_fields) { + indent(out) << "__isset = " << maybeMove(tmp_name + ".__isset", is_move) << ";" << endl; + } + + indent(out) << "return *this;" << endl; + indent_down(); + indent(out) << "}" << endl; +} + +/** + * Writes the struct declaration into the header file + * + * @param out Output stream + * @param tstruct The struct + */ +void t_cpp_generator::generate_struct_declaration(ostream& out, + t_struct* tstruct, + bool is_exception, + bool pointers, + bool read, + bool write, + bool swap, + bool is_user_struct) { + string extends = ""; + if (is_exception) { + extends = " : public ::apache::thrift::TException"; + } else { + if (is_user_struct && !gen_templates_) { + extends = " : public virtual ::apache::thrift::TBase"; + } + } + + // Get members + vector<t_field*>::const_iterator m_iter; + const vector<t_field*>& members = tstruct->get_members(); + + // Write the isset structure declaration outside the class. This makes + // the generated code amenable to processing by SWIG. + // We only declare the struct if it gets used in the class. + + // Isset struct has boolean fields, but only for non-required fields. + bool has_nonrequired_fields = false; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if ((*m_iter)->get_req() != t_field::T_REQUIRED) + has_nonrequired_fields = true; + } + + if (has_nonrequired_fields && (!pointers || read)) { + + out << indent() << "typedef struct _" << tstruct->get_name() << "__isset {" << endl; + indent_up(); + + indent(out) << "_" << tstruct->get_name() << "__isset() "; + bool first = true; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if ((*m_iter)->get_req() == t_field::T_REQUIRED) { + continue; + } + string isSet = ((*m_iter)->get_value() != NULL) ? "true" : "false"; + if (first) { + first = false; + out << ": " << (*m_iter)->get_name() << "(" << isSet << ")"; + } else { + out << ", " << (*m_iter)->get_name() << "(" << isSet << ")"; + } + } + out << " {}" << endl; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if ((*m_iter)->get_req() != t_field::T_REQUIRED) { + indent(out) << "bool " << (*m_iter)->get_name() << " :1;" << endl; + } + } + + indent_down(); + indent(out) << "} _" << tstruct->get_name() << "__isset;" << endl; + } + + out << endl; + + // Open struct def + out << indent() << "class " << tstruct->get_name() << extends << " {" << endl << indent() + << " public:" << endl << endl; + indent_up(); + + if (!pointers) { + // Copy constructor + indent(out) << tstruct->get_name() << "(const " << tstruct->get_name() << "&);" << endl; + + // Move constructor + if (gen_moveable_) { + indent(out) << tstruct->get_name() << "(" << tstruct->get_name() << "&&);" << endl; + } + + // Assignment Operator + indent(out) << tstruct->get_name() << "& operator=(const " << tstruct->get_name() << "&);" + << endl; + + // Move assignment operator + if (gen_moveable_) { + indent(out) << tstruct->get_name() << "& operator=(" << tstruct->get_name() << "&&);" << endl; + } + + // Default constructor + indent(out) << tstruct->get_name() << "()"; + + bool init_ctor = false; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* t = get_true_type((*m_iter)->get_type()); + if (t->is_base_type() || t->is_enum() || is_reference(*m_iter)) { + string dval; + if (t->is_enum()) { + dval += "(" + type_name(t) + ")"; + } + dval += (t->is_string() || is_reference(*m_iter)) ? "" : "0"; + t_const_value* cv = (*m_iter)->get_value(); + if (cv != NULL) { + dval = render_const_value(out, (*m_iter)->get_name(), t, cv); + } + if (!init_ctor) { + init_ctor = true; + out << " : "; + out << (*m_iter)->get_name() << "(" << dval << ")"; + } else { + out << ", " << (*m_iter)->get_name() << "(" << dval << ")"; + } + } + } + out << " {" << endl; + indent_up(); + // TODO(dreiss): When everything else in Thrift is perfect, + // do more of these in the initializer list. + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* t = get_true_type((*m_iter)->get_type()); + + if (!t->is_base_type()) { + t_const_value* cv = (*m_iter)->get_value(); + if (cv != NULL) { + print_const_value(out, (*m_iter)->get_name(), t, cv); + } + } + } + scope_down(out); + } + + if (tstruct->annotations_.find("final") == tstruct->annotations_.end()) { + out << endl << indent() << "virtual ~" << tstruct->get_name() << "() noexcept;" << endl; + } + + // Declare all fields + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + indent(out) << declare_field(*m_iter, + false, + (pointers && !(*m_iter)->get_type()->is_xception()), + !read) << endl; + } + + // Add the __isset data member if we need it, using the definition from above + if (has_nonrequired_fields && (!pointers || read)) { + out << endl << indent() << "_" << tstruct->get_name() << "__isset __isset;" << endl; + } + + // Create a setter function for each field + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if (pointers) { + continue; + } + if (is_reference((*m_iter))) { + out << endl << indent() << "void __set_" << (*m_iter)->get_name() << "(::std::shared_ptr<" + << type_name((*m_iter)->get_type(), false, false) << ">"; + out << " val);" << endl; + } else { + out << endl << indent() << "void __set_" << (*m_iter)->get_name() << "(" + << type_name((*m_iter)->get_type(), false, true); + out << " val);" << endl; + } + } + out << endl; + + if (!pointers) { + // Should we generate default operators? + if (!gen_no_default_operators_) { + // Generate an equality testing operator. Make it inline since the compiler + // will do a better job than we would when deciding whether to inline it. + out << indent() << "bool operator == (const " << tstruct->get_name() << " & " + << (members.size() > 0 ? "rhs" : "/* rhs */") << ") const" << endl; + scope_up(out); + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + // Most existing Thrift code does not use isset or optional/required, + // so we treat "default" fields as required. + if ((*m_iter)->get_req() != t_field::T_OPTIONAL) { + out << indent() << "if (!(" << (*m_iter)->get_name() << " == rhs." + << (*m_iter)->get_name() << "))" << endl << indent() << " return false;" << endl; + } else { + out << indent() << "if (__isset." << (*m_iter)->get_name() << " != rhs.__isset." + << (*m_iter)->get_name() << ")" << endl << indent() << " return false;" << endl + << indent() << "else if (__isset." << (*m_iter)->get_name() << " && !(" + << (*m_iter)->get_name() << " == rhs." << (*m_iter)->get_name() << "))" << endl + << indent() << " return false;" << endl; + } + } + indent(out) << "return true;" << endl; + scope_down(out); + out << indent() << "bool operator != (const " << tstruct->get_name() << " &rhs) const {" + << endl << indent() << " return !(*this == rhs);" << endl << indent() << "}" << endl + << endl; + + // Generate the declaration of a less-than operator. This must be + // implemented by the application developer if they wish to use it. (They + // will get a link error if they try to use it without an implementation.) + out << indent() << "bool operator < (const " << tstruct->get_name() << " & ) const;" << endl + << endl; + } + } + + if (read) { + if (gen_templates_) { + out << indent() << "template <class Protocol_>" << endl << indent() + << "uint32_t read(Protocol_* iprot);" << endl; + } else { + out << indent() << "uint32_t read(" + << "::apache::thrift::protocol::TProtocol* iprot);" << endl; + } + } + if (write) { + if (gen_templates_) { + out << indent() << "template <class Protocol_>" << endl << indent() + << "uint32_t write(Protocol_* oprot) const;" << endl; + } else { + out << indent() << "uint32_t write(" + << "::apache::thrift::protocol::TProtocol* oprot) const;" << endl; + } + } + out << endl; + + if (is_user_struct && !has_custom_ostream(tstruct)) { + out << indent() << "virtual "; + generate_struct_print_method_decl(out, NULL); + out << ";" << endl; + } + + // std::exception::what() + if (is_exception) { + out << indent() << "mutable std::string thriftTExceptionMessageHolder_;" << endl; + out << indent(); + generate_exception_what_method_decl(out, tstruct, false); + out << ";" << endl; + } + + indent_down(); + indent(out) << "};" << endl << endl; + + if (swap) { + // Generate a namespace-scope swap() function + out << indent() << "void swap(" << tstruct->get_name() << " &a, " << tstruct->get_name() + << " &b);" << endl << endl; + } + + if (is_user_struct) { + generate_struct_ostream_operator_decl(out, tstruct); + } +} + +void t_cpp_generator::generate_struct_definition(ostream& out, + ostream& force_cpp_out, + t_struct* tstruct, + bool setters, + bool is_user_struct) { + // Get members + vector<t_field*>::const_iterator m_iter; + const vector<t_field*>& members = tstruct->get_members(); + + // Destructor + if (tstruct->annotations_.find("final") == tstruct->annotations_.end()) { + force_cpp_out << endl << indent() << tstruct->get_name() << "::~" << tstruct->get_name() + << "() noexcept {" << endl; + indent_up(); + + indent_down(); + force_cpp_out << indent() << "}" << endl << endl; + } + + // Create a setter function for each field + if (setters) { + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if (is_reference((*m_iter))) { + out << endl << indent() << "void " << tstruct->get_name() << "::__set_" + << (*m_iter)->get_name() << "(::std::shared_ptr<" + << type_name((*m_iter)->get_type(), false, false) << ">"; + out << " val) {" << endl; + } else { + out << endl << indent() << "void " << tstruct->get_name() << "::__set_" + << (*m_iter)->get_name() << "(" << type_name((*m_iter)->get_type(), false, true); + out << " val) {" << endl; + } + indent_up(); + out << indent() << "this->" << (*m_iter)->get_name() << " = val;" << endl; + indent_down(); + + // assume all fields are required except optional fields. + // for optional fields change __isset.name to true + bool is_optional = (*m_iter)->get_req() == t_field::T_OPTIONAL; + if (is_optional) { + out << indent() << indent() << "__isset." << (*m_iter)->get_name() << " = true;" << endl; + } + out << indent() << "}" << endl; + } + } + if (is_user_struct) { + generate_struct_ostream_operator(out, tstruct); + } + out << endl; +} + +/** + * Makes a helper function to gen a struct reader. + * + * @param out Stream to write to + * @param tstruct The struct + */ +void t_cpp_generator::generate_struct_reader(ostream& out, t_struct* tstruct, bool pointers) { + if (gen_templates_) { + out << indent() << "template <class Protocol_>" << endl << indent() << "uint32_t " + << tstruct->get_name() << "::read(Protocol_* iprot) {" << endl; + } else { + indent(out) << "uint32_t " << tstruct->get_name() + << "::read(::apache::thrift::protocol::TProtocol* iprot) {" << endl; + } + indent_up(); + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + // Declare stack tmp variables + out << endl + << indent() << "::apache::thrift::protocol::TInputRecursionTracker tracker(*iprot);" << endl + << indent() << "uint32_t xfer = 0;" << endl + << indent() << "std::string fname;" << endl + << indent() << "::apache::thrift::protocol::TType ftype;" << endl + << indent() << "int16_t fid;" << endl + << endl + << indent() << "xfer += iprot->readStructBegin(fname);" << endl + << endl + << indent() << "using ::apache::thrift::protocol::TProtocolException;" << endl + << endl; + + // Required variables aren't in __isset, so we need tmp vars to check them. + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_REQUIRED) + indent(out) << "bool isset_" << (*f_iter)->get_name() << " = false;" << endl; + } + out << endl; + + // Loop over reading in fields + indent(out) << "while (true)" << endl; + scope_up(out); + + // Read beginning field marker + indent(out) << "xfer += iprot->readFieldBegin(fname, ftype, fid);" << endl; + + // Check for field STOP marker + out << indent() << "if (ftype == ::apache::thrift::protocol::T_STOP) {" << endl << indent() + << " break;" << endl << indent() << "}" << endl; + + if (fields.empty()) { + out << indent() << "xfer += iprot->skip(ftype);" << endl; + } else { + // Switch statement on the field we are reading + indent(out) << "switch (fid)" << endl; + + scope_up(out); + + // Generate deserialization code for known cases + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + indent(out) << "case " << (*f_iter)->get_key() << ":" << endl; + indent_up(); + indent(out) << "if (ftype == " << type_to_enum((*f_iter)->get_type()) << ") {" << endl; + indent_up(); + + const char* isset_prefix = ((*f_iter)->get_req() != t_field::T_REQUIRED) ? "this->__isset." + : "isset_"; + +#if 0 + // This code throws an exception if the same field is encountered twice. + // We've decided to leave it out for performance reasons. + // TODO(dreiss): Generate this code and "if" it out to make it easier + // for people recompiling thrift to include it. + out << + indent() << "if (" << isset_prefix << (*f_iter)->get_name() << ")" << endl << + indent() << " throw TProtocolException(TProtocolException::INVALID_DATA);" << endl; +#endif + + if (pointers && !(*f_iter)->get_type()->is_xception()) { + generate_deserialize_field(out, *f_iter, "(*(this->", "))"); + } else { + generate_deserialize_field(out, *f_iter, "this->"); + } + out << indent() << isset_prefix << (*f_iter)->get_name() << " = true;" << endl; + indent_down(); + out << indent() << "} else {" << endl << indent() << " xfer += iprot->skip(ftype);" << endl + << + // TODO(dreiss): Make this an option when thrift structs + // have a common base class. + // indent() << " throw TProtocolException(TProtocolException::INVALID_DATA);" << endl << + indent() << "}" << endl << indent() << "break;" << endl; + indent_down(); + } + + // In the default case we skip the field + out << indent() << "default:" << endl << indent() << " xfer += iprot->skip(ftype);" << endl + << indent() << " break;" << endl; + + scope_down(out); + } //!fields.empty() + // Read field end marker + indent(out) << "xfer += iprot->readFieldEnd();" << endl; + + scope_down(out); + + out << endl << indent() << "xfer += iprot->readStructEnd();" << endl; + + // Throw if any required fields are missing. + // We do this after reading the struct end so that + // there might possibly be a chance of continuing. + out << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_REQUIRED) + out << indent() << "if (!isset_" << (*f_iter)->get_name() << ')' << endl << indent() + << " throw TProtocolException(TProtocolException::INVALID_DATA);" << endl; + } + + indent(out) << "return xfer;" << endl; + + indent_down(); + indent(out) << "}" << endl << endl; +} + +/** + * Generates the write function. + * + * @param out Stream to write to + * @param tstruct The struct + */ +void t_cpp_generator::generate_struct_writer(ostream& out, t_struct* tstruct, bool pointers) { + string name = tstruct->get_name(); + const vector<t_field*>& fields = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator f_iter; + + if (gen_templates_) { + out << indent() << "template <class Protocol_>" << endl << indent() << "uint32_t " + << tstruct->get_name() << "::write(Protocol_* oprot) const {" << endl; + } else { + indent(out) << "uint32_t " << tstruct->get_name() + << "::write(::apache::thrift::protocol::TProtocol* oprot) const {" << endl; + } + indent_up(); + + out << indent() << "uint32_t xfer = 0;" << endl; + + indent(out) << "::apache::thrift::protocol::TOutputRecursionTracker tracker(*oprot);" << endl; + indent(out) << "xfer += oprot->writeStructBegin(\"" << name << "\");" << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + bool check_if_set = (*f_iter)->get_req() == t_field::T_OPTIONAL + || (*f_iter)->get_type()->is_xception(); + if (check_if_set) { + out << endl << indent() << "if (this->__isset." << (*f_iter)->get_name() << ") {" << endl; + indent_up(); + } else { + out << endl; + } + + // Write field header + out << indent() << "xfer += oprot->writeFieldBegin(" + << "\"" << (*f_iter)->get_name() << "\", " << type_to_enum((*f_iter)->get_type()) << ", " + << (*f_iter)->get_key() << ");" << endl; + // Write field contents + if (pointers && !(*f_iter)->get_type()->is_xception()) { + generate_serialize_field(out, *f_iter, "(*(this->", "))"); + } else { + generate_serialize_field(out, *f_iter, "this->"); + } + // Write field closer + indent(out) << "xfer += oprot->writeFieldEnd();" << endl; + if (check_if_set) { + indent_down(); + indent(out) << '}'; + } + } + + out << endl; + + // Write the struct map + out << indent() << "xfer += oprot->writeFieldStop();" << endl << indent() + << "xfer += oprot->writeStructEnd();" << endl << indent() + << "return xfer;" << endl; + + indent_down(); + indent(out) << "}" << endl << endl; +} + +/** + * Struct writer for result of a function, which can have only one of its + * fields set and does a conditional if else look up into the __isset field + * of the struct. + * + * @param out Output stream + * @param tstruct The result struct + */ +void t_cpp_generator::generate_struct_result_writer(ostream& out, + t_struct* tstruct, + bool pointers) { + string name = tstruct->get_name(); + const vector<t_field*>& fields = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator f_iter; + + if (gen_templates_) { + out << indent() << "template <class Protocol_>" << endl << indent() << "uint32_t " + << tstruct->get_name() << "::write(Protocol_* oprot) const {" << endl; + } else { + indent(out) << "uint32_t " << tstruct->get_name() + << "::write(::apache::thrift::protocol::TProtocol* oprot) const {" << endl; + } + indent_up(); + + out << endl << indent() << "uint32_t xfer = 0;" << endl << endl; + + indent(out) << "xfer += oprot->writeStructBegin(\"" << name << "\");" << endl; + + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + out << endl << indent() << "if "; + } else { + out << " else if "; + } + + out << "(this->__isset." << (*f_iter)->get_name() << ") {" << endl; + + indent_up(); + + // Write field header + out << indent() << "xfer += oprot->writeFieldBegin(" + << "\"" << (*f_iter)->get_name() << "\", " << type_to_enum((*f_iter)->get_type()) << ", " + << (*f_iter)->get_key() << ");" << endl; + // Write field contents + if (pointers) { + generate_serialize_field(out, *f_iter, "(*(this->", "))"); + } else { + generate_serialize_field(out, *f_iter, "this->"); + } + // Write field closer + indent(out) << "xfer += oprot->writeFieldEnd();" << endl; + + indent_down(); + indent(out) << "}"; + } + + // Write the struct map + out << endl << indent() << "xfer += oprot->writeFieldStop();" << endl << indent() + << "xfer += oprot->writeStructEnd();" << endl << indent() << "return xfer;" << endl; + + indent_down(); + indent(out) << "}" << endl << endl; +} + +/** + * Generates the swap function. + * + * @param out Stream to write to + * @param tstruct The struct + */ +void t_cpp_generator::generate_struct_swap(ostream& out, t_struct* tstruct) { + out << indent() << "void swap(" << tstruct->get_name() << " &a, " << tstruct->get_name() + << " &b) {" << endl; + indent_up(); + + // Let argument-dependent name lookup find the correct swap() function to + // use based on the argument types. If none is found in the arguments' + // namespaces, fall back to ::std::swap(). + out << indent() << "using ::std::swap;" << endl; + + bool has_nonrequired_fields = false; + const vector<t_field*>& fields = tstruct->get_members(); + for (auto tfield : fields) { + if (tfield->get_req() != t_field::T_REQUIRED) { + has_nonrequired_fields = true; + } + + out << indent() << "swap(a." << tfield->get_name() << ", b." << tfield->get_name() << ");" + << endl; + } + + if (has_nonrequired_fields) { + out << indent() << "swap(a.__isset, b.__isset);" << endl; + } + + // handle empty structs + if (fields.size() == 0) { + out << indent() << "(void) a;" << endl; + out << indent() << "(void) b;" << endl; + } + + scope_down(out); + out << endl; +} + +void t_cpp_generator::generate_struct_ostream_operator_decl(std::ostream& out, t_struct* tstruct) { + out << "std::ostream& operator<<(std::ostream& out, const " + << tstruct->get_name() + << "& obj);" << endl; + out << endl; +} + +void t_cpp_generator::generate_struct_ostream_operator(std::ostream& out, t_struct* tstruct) { + if (!has_custom_ostream(tstruct)) { + // thrift defines this behavior + out << "std::ostream& operator<<(std::ostream& out, const " + << tstruct->get_name() + << "& obj)" << endl; + scope_up(out); + out << indent() << "obj.printTo(out);" << endl + << indent() << "return out;" << endl; + scope_down(out); + out << endl; + } +} + +void t_cpp_generator::generate_struct_print_method_decl(std::ostream& out, t_struct* tstruct) { + out << "void "; + if (tstruct) { + out << tstruct->get_name() << "::"; + } + out << "printTo(std::ostream& out) const"; +} + +void t_cpp_generator::generate_exception_what_method_decl(std::ostream& out, + t_struct* tstruct, + bool external) { + out << "const char* "; + if (external) { + out << tstruct->get_name() << "::"; + } + out << "what() const noexcept"; +} + +namespace struct_ostream_operator_generator { +void generate_required_field_value(std::ostream& out, const t_field* field) { + out << " << to_string(" << field->get_name() << ")"; +} + +void generate_optional_field_value(std::ostream& out, const t_field* field) { + out << "; (__isset." << field->get_name() << " ? (out"; + generate_required_field_value(out, field); + out << ") : (out << \"<null>\"))"; +} + +void generate_field_value(std::ostream& out, const t_field* field) { + if (field->get_req() == t_field::T_OPTIONAL) + generate_optional_field_value(out, field); + else + generate_required_field_value(out, field); +} + +void generate_field_name(std::ostream& out, const t_field* field) { + out << "\"" << field->get_name() << "=\""; +} + +void generate_field(std::ostream& out, const t_field* field) { + generate_field_name(out, field); + generate_field_value(out, field); +} + +void generate_fields(std::ostream& out, + const vector<t_field*>& fields, + const std::string& indent) { + const vector<t_field*>::const_iterator beg = fields.begin(); + const vector<t_field*>::const_iterator end = fields.end(); + + for (vector<t_field*>::const_iterator it = beg; it != end; ++it) { + out << indent << "out << "; + + if (it != beg) { + out << "\", \" << "; + } + + generate_field(out, *it); + out << ";" << endl; + } +} +} + +/** + * Generates operator<< + */ +void t_cpp_generator::generate_struct_print_method(std::ostream& out, t_struct* tstruct) { + out << indent(); + generate_struct_print_method_decl(out, tstruct); + out << " {" << endl; + + indent_up(); + + out << indent() << "using ::apache::thrift::to_string;" << endl; + out << indent() << "out << \"" << tstruct->get_name() << "(\";" << endl; + struct_ostream_operator_generator::generate_fields(out, tstruct->get_members(), indent()); + out << indent() << "out << \")\";" << endl; + + indent_down(); + out << "}" << endl << endl; +} + +/** + * Generates what() method for exceptions + */ +void t_cpp_generator::generate_exception_what_method(std::ostream& out, t_struct* tstruct) { + out << indent(); + generate_exception_what_method_decl(out, tstruct, true); + out << " {" << endl; + + indent_up(); + out << indent() << "try {" << endl; + + indent_up(); + out << indent() << "std::stringstream ss;" << endl; + out << indent() << "ss << \"TException - service has thrown: \" << *this;" << endl; + out << indent() << "this->thriftTExceptionMessageHolder_ = ss.str();" << endl; + out << indent() << "return this->thriftTExceptionMessageHolder_.c_str();" << endl; + indent_down(); + + out << indent() << "} catch (const std::exception&) {" << endl; + + indent_up(); + out << indent() << "return \"TException - service has thrown: " << tstruct->get_name() << "\";" + << endl; + indent_down(); + + out << indent() << "}" << endl; + + indent_down(); + out << "}" << endl << endl; +} + +/** + * Generates a thrift service. In C++, this comprises an entirely separate + * header and source file. The header file defines the methods and includes + * the data types defined in the main header file, and the implementation + * file contains implementations of the basic printer and default interfaces. + * + * @param tservice The service definition + */ +void t_cpp_generator::generate_service(t_service* tservice) { + string svcname = tservice->get_name(); + + // Make output files + string f_header_name = get_out_dir() + svcname + ".h"; + f_header_.open(f_header_name.c_str()); + + // Print header file includes + f_header_ << autogen_comment(); + f_header_ << "#ifndef " << svcname << "_H" << endl << "#define " << svcname << "_H" << endl + << endl; + if (gen_cob_style_) { + f_header_ << "#include <thrift/transport/TBufferTransports.h>" << endl // TMemoryBuffer + << "#include <functional>" << endl + << "namespace apache { namespace thrift { namespace async {" << endl + << "class TAsyncChannel;" << endl << "}}}" << endl; + } + f_header_ << "#include <thrift/TDispatchProcessor.h>" << endl; + if (gen_cob_style_) { + f_header_ << "#include <thrift/async/TAsyncDispatchProcessor.h>" << endl; + } + f_header_ << "#include <thrift/async/TConcurrentClientSyncInfo.h>" << endl; + f_header_ << "#include <memory>" << endl; + f_header_ << "#include \"" << get_include_prefix(*get_program()) << program_name_ << "_types.h\"" + << endl; + + t_service* extends_service = tservice->get_extends(); + if (extends_service != NULL) { + f_header_ << "#include \"" << get_include_prefix(*(extends_service->get_program())) + << extends_service->get_name() << ".h\"" << endl; + } + + f_header_ << endl << ns_open_ << endl << endl; + + f_header_ << "#ifdef _MSC_VER\n" + " #pragma warning( push )\n" + " #pragma warning (disable : 4250 ) //inheriting methods via dominance \n" + "#endif\n\n"; + + // Service implementation file includes + string f_service_name = get_out_dir() + svcname + ".cpp"; + f_service_.open(f_service_name.c_str()); + f_service_ << autogen_comment(); + f_service_ << "#include \"" << get_include_prefix(*get_program()) << svcname << ".h\"" << endl; + if (gen_cob_style_) { + f_service_ << "#include \"thrift/async/TAsyncChannel.h\"" << endl; + } + if (gen_templates_) { + f_service_ << "#include \"" << get_include_prefix(*get_program()) << svcname << ".tcc\"" + << endl; + + string f_service_tcc_name = get_out_dir() + svcname + ".tcc"; + f_service_tcc_.open(f_service_tcc_name.c_str()); + f_service_tcc_ << autogen_comment(); + f_service_tcc_ << "#include \"" << get_include_prefix(*get_program()) << svcname << ".h\"" + << endl; + + f_service_tcc_ << "#ifndef " << svcname << "_TCC" << endl << "#define " << svcname << "_TCC" + << endl << endl; + + if (gen_cob_style_) { + f_service_tcc_ << "#include \"thrift/async/TAsyncChannel.h\"" << endl; + } + } + + f_service_ << endl << ns_open_ << endl << endl; + f_service_tcc_ << endl << ns_open_ << endl << endl; + + // Generate all the components + generate_service_interface(tservice, ""); + generate_service_interface_factory(tservice, ""); + generate_service_null(tservice, ""); + generate_service_helpers(tservice); + generate_service_client(tservice, ""); + generate_service_processor(tservice, ""); + generate_service_multiface(tservice); + generate_service_client(tservice, "Concurrent"); + + // Generate skeleton + if (!gen_no_skeleton_) { + generate_service_skeleton(tservice); + } + + // Generate all the cob components + if (gen_cob_style_) { + generate_service_interface(tservice, "CobCl"); + generate_service_interface(tservice, "CobSv"); + generate_service_interface_factory(tservice, "CobSv"); + generate_service_null(tservice, "CobSv"); + generate_service_client(tservice, "Cob"); + generate_service_processor(tservice, "Cob"); + + if (!gen_no_skeleton_) { + generate_service_async_skeleton(tservice); + } + + } + + f_header_ << "#ifdef _MSC_VER\n" + " #pragma warning( pop )\n" + "#endif\n\n"; + + // Close the namespace + f_service_ << ns_close_ << endl << endl; + f_service_tcc_ << ns_close_ << endl << endl; + f_header_ << ns_close_ << endl << endl; + + // TODO(simpkins): Make this a separate option + if (gen_templates_) { + f_header_ << "#include \"" << get_include_prefix(*get_program()) << svcname << ".tcc\"" << endl + << "#include \"" << get_include_prefix(*get_program()) << program_name_ + << "_types.tcc\"" << endl << endl; + } + + f_header_ << "#endif" << endl; + f_service_tcc_ << "#endif" << endl; + + // Close the files + f_service_tcc_.close(); + f_service_.close(); + f_header_.close(); +} + +/** + * Generates helper functions for a service. Basically, this generates types + * for all the arguments and results to functions. + * + * @param tservice The service to generate a header definition for + */ +void t_cpp_generator::generate_service_helpers(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + std::ostream& out = (gen_templates_ ? f_service_tcc_ : f_service_); + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* ts = (*f_iter)->get_arglist(); + string name_orig = ts->get_name(); + + // TODO(dreiss): Why is this stuff not in generate_function_helpers? + ts->set_name(tservice->get_name() + "_" + (*f_iter)->get_name() + "_args"); + generate_struct_declaration(f_header_, ts, false); + generate_struct_definition(out, f_service_, ts, false); + generate_struct_reader(out, ts); + generate_struct_writer(out, ts); + ts->set_name(tservice->get_name() + "_" + (*f_iter)->get_name() + "_pargs"); + generate_struct_declaration(f_header_, ts, false, true, false, true); + generate_struct_definition(out, f_service_, ts, false); + generate_struct_writer(out, ts, true); + ts->set_name(name_orig); + + generate_function_helpers(tservice, *f_iter); + } +} + +/** + * Generates a service interface definition. + * + * @param tservice The service to generate a header definition for + */ +void t_cpp_generator::generate_service_interface(t_service* tservice, string style) { + + string service_if_name = service_name_ + style + "If"; + if (style == "CobCl") { + // Forward declare the client. + string client_name = service_name_ + "CobClient"; + if (gen_templates_) { + client_name += "T"; + service_if_name += "T"; + indent(f_header_) << "template <class Protocol_>" << endl; + } + indent(f_header_) << "class " << client_name << ";" << endl << endl; + } + + string extends = ""; + if (tservice->get_extends() != NULL) { + extends = " : virtual public " + type_name(tservice->get_extends()) + style + "If"; + if (style == "CobCl" && gen_templates_) { + // TODO(simpkins): If gen_templates_ is enabled, we currently assume all + // parent services were also generated with templates enabled. + extends += "T<Protocol_>"; + } + } + + if (style == "CobCl" && gen_templates_) { + f_header_ << "template <class Protocol_>" << endl; + } + f_header_ << "class " << service_if_name << extends << " {" << endl << " public:" << endl; + indent_up(); + f_header_ << indent() << "virtual ~" << service_if_name << "() {}" << endl; + + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + if ((*f_iter)->has_doc()) + f_header_ << endl; + generate_java_doc(f_header_, *f_iter); + f_header_ << indent() << "virtual " << function_signature(*f_iter, style) << " = 0;" << endl; + } + indent_down(); + f_header_ << "};" << endl << endl; + + if (style == "CobCl" && gen_templates_) { + // generate a backwards-compatible typedef for clients that do not + // know about the new template-style code + f_header_ << "typedef " << service_if_name << "< ::apache::thrift::protocol::TProtocol> " + << service_name_ << style << "If;" << endl << endl; + } +} + +/** + * Generates a service interface factory. + * + * @param tservice The service to generate an interface factory for. + */ +void t_cpp_generator::generate_service_interface_factory(t_service* tservice, string style) { + string service_if_name = service_name_ + style + "If"; + + // Figure out the name of the upper-most parent class. + // Getting everything to work out properly with inheritance is annoying. + // Here's what we're doing for now: + // + // - All handlers implement getHandler(), but subclasses use covariant return + // types to return their specific service interface class type. We have to + // use raw pointers because of this; shared_ptr<> can't be used for + // covariant return types. + // + // - Since we're not using shared_ptr<>, we also provide a releaseHandler() + // function that must be called to release a pointer to a handler obtained + // via getHandler(). + // + // releaseHandler() always accepts a pointer to the upper-most parent class + // type. This is necessary since the parent versions of releaseHandler() + // may accept any of the parent types, not just the most specific subclass + // type. Implementations can use dynamic_cast to cast the pointer to the + // subclass type if desired. + t_service* base_service = tservice; + while (base_service->get_extends() != NULL) { + base_service = base_service->get_extends(); + } + string base_if_name = type_name(base_service) + style + "If"; + + // Generate the abstract factory class + string factory_name = service_if_name + "Factory"; + string extends; + if (tservice->get_extends() != NULL) { + extends = " : virtual public " + type_name(tservice->get_extends()) + style + "IfFactory"; + } + + f_header_ << "class " << factory_name << extends << " {" << endl << " public:" << endl; + indent_up(); + f_header_ << indent() << "typedef " << service_if_name << " Handler;" << endl << endl << indent() + << "virtual ~" << factory_name << "() {}" << endl << endl << indent() << "virtual " + << service_if_name << "* getHandler(" + << "const ::apache::thrift::TConnectionInfo& connInfo) = 0;" << endl << indent() + << "virtual void releaseHandler(" << base_if_name << "* /* handler */) = 0;" << endl; + + indent_down(); + f_header_ << "};" << endl << endl; + + // Generate the singleton factory class + string singleton_factory_name = service_if_name + "SingletonFactory"; + f_header_ << "class " << singleton_factory_name << " : virtual public " << factory_name << " {" + << endl << " public:" << endl; + indent_up(); + f_header_ << indent() << singleton_factory_name << "(const ::std::shared_ptr<" << service_if_name + << ">& iface) : iface_(iface) {}" << endl << indent() << "virtual ~" + << singleton_factory_name << "() {}" << endl << endl << indent() << "virtual " + << service_if_name << "* getHandler(" + << "const ::apache::thrift::TConnectionInfo&) {" << endl << indent() + << " return iface_.get();" << endl << indent() << "}" << endl << indent() + << "virtual void releaseHandler(" << base_if_name << "* /* handler */) {}" << endl; + + f_header_ << endl << " protected:" << endl << indent() << "::std::shared_ptr<" << service_if_name + << "> iface_;" << endl; + + indent_down(); + f_header_ << "};" << endl << endl; +} + +/** + * Generates a null implementation of the service. + * + * @param tservice The service to generate a header definition for + */ +void t_cpp_generator::generate_service_null(t_service* tservice, string style) { + string extends = ""; + if (tservice->get_extends() != NULL) { + extends = " , virtual public " + type_name(tservice->get_extends()) + style + "Null"; + } + f_header_ << "class " << service_name_ << style << "Null : virtual public " << service_name_ + << style << "If" << extends << " {" << endl << " public:" << endl; + indent_up(); + f_header_ << indent() << "virtual ~" << service_name_ << style << "Null() {}" << endl; + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + f_header_ << indent() << function_signature(*f_iter, style, "", false) << " {" << endl; + indent_up(); + + t_type* returntype = (*f_iter)->get_returntype(); + t_field returnfield(returntype, "_return"); + + if (style == "") { + if (returntype->is_void() || is_complex_type(returntype)) { + f_header_ << indent() << "return;" << endl; + } else { + f_header_ << indent() << declare_field(&returnfield, true) << endl << indent() + << "return _return;" << endl; + } + } else if (style == "CobSv") { + if (returntype->is_void()) { + f_header_ << indent() << "return cob();" << endl; + } else { + t_field returnfield(returntype, "_return"); + f_header_ << indent() << declare_field(&returnfield, true) << endl << indent() + << "return cob(_return);" << endl; + } + + } else { + throw "UNKNOWN STYLE"; + } + + indent_down(); + f_header_ << indent() << "}" << endl; + } + indent_down(); + f_header_ << "};" << endl << endl; +} + +void t_cpp_generator::generate_function_call(ostream& out, + t_function* tfunction, + string target, + string iface, + string arg_prefix) { + bool first = true; + t_type* ret_type = get_true_type(tfunction->get_returntype()); + out << indent(); + if (!tfunction->is_oneway() && !ret_type->is_void()) { + if (is_complex_type(ret_type)) { + first = false; + out << iface << "->" << tfunction->get_name() << "(" << target; + } else { + out << target << " = " << iface << "->" << tfunction->get_name() << "("; + } + } else { + out << iface << "->" << tfunction->get_name() << "("; + } + const std::vector<t_field*>& fields = tfunction->get_arglist()->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + out << ", "; + } + out << arg_prefix << (*f_iter)->get_name(); + } + out << ");" << endl; +} + +void t_cpp_generator::generate_service_async_skeleton(t_service* tservice) { + string svcname = tservice->get_name(); + + // Service implementation file includes + string f_skeleton_name = get_out_dir() + svcname + "_async_server.skeleton.cpp"; + + string ns = namespace_prefix(tservice->get_program()->get_namespace("cpp")); + + ofstream_with_content_based_conditional_update f_skeleton; + f_skeleton.open(f_skeleton_name.c_str()); + f_skeleton << "// This autogenerated skeleton file illustrates one way to adapt a synchronous" + << endl << "// interface into an asynchronous interface. You should copy it to another" + << endl + << "// filename to avoid overwriting it and rewrite as asynchronous any functions" + << endl << "// that would otherwise introduce unwanted latency." << endl << endl + << "#include \"" << get_include_prefix(*get_program()) << svcname << ".h\"" << endl + << "#include <thrift/protocol/TBinaryProtocol.h>" << endl << endl + << "using namespace ::apache::thrift;" << endl + << "using namespace ::apache::thrift::protocol;" << endl + << "using namespace ::apache::thrift::transport;" << endl + << "using namespace ::apache::thrift::async;" << endl << endl; + + // the following code would not compile: + // using namespace ; + // using namespace ::; + if ((!ns.empty()) && (ns.compare(" ::") != 0)) { + f_skeleton << "using namespace " << string(ns, 0, ns.size() - 2) << ";" << endl << endl; + } + + f_skeleton << "class " << svcname << "AsyncHandler : " + << "public " << svcname << "CobSvIf {" << endl << " public:" << endl; + indent_up(); + f_skeleton << indent() << svcname << "AsyncHandler() {" << endl << indent() + << " syncHandler_ = std::auto_ptr<" << svcname << "Handler>(new " << svcname + << "Handler);" << endl << indent() << " // Your initialization goes here" << endl + << indent() << "}" << endl; + f_skeleton << indent() << "virtual ~" << service_name_ << "AsyncHandler();" << endl; + + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + f_skeleton << endl << indent() << function_signature(*f_iter, "CobSv", "", true) << " {" + << endl; + indent_up(); + + t_type* returntype = (*f_iter)->get_returntype(); + t_field returnfield(returntype, "_return"); + + string target = returntype->is_void() ? "" : "_return"; + if (!returntype->is_void()) { + f_skeleton << indent() << declare_field(&returnfield, true) << endl; + } + generate_function_call(f_skeleton, *f_iter, target, "syncHandler_", ""); + f_skeleton << indent() << "return cob(" << target << ");" << endl; + + scope_down(f_skeleton); + } + f_skeleton << endl << " protected:" << endl << indent() << "std::auto_ptr<" << svcname + << "Handler> syncHandler_;" << endl; + indent_down(); + f_skeleton << "};" << endl << endl; +} + +/** + * Generates a multiface, which is a single server that just takes a set + * of objects implementing the interface and calls them all, returning the + * value of the last one to be called. + * + * @param tservice The service to generate a multiserver for. + */ +void t_cpp_generator::generate_service_multiface(t_service* tservice) { + // Generate the dispatch methods + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + string extends = ""; + string extends_multiface = ""; + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + extends_multiface = ", public " + extends + "Multiface"; + } + + string list_type = string("std::vector<std::shared_ptr<") + service_name_ + "If> >"; + + // Generate the header portion + f_header_ << "class " << service_name_ << "Multiface : " + << "virtual public " << service_name_ << "If" << extends_multiface << " {" << endl + << " public:" << endl; + indent_up(); + f_header_ << indent() << service_name_ << "Multiface(" << list_type + << "& ifaces) : ifaces_(ifaces) {" << endl; + if (!extends.empty()) { + f_header_ << indent() + << " std::vector<std::shared_ptr<" + service_name_ + "If> >::iterator iter;" + << endl << indent() << " for (iter = ifaces.begin(); iter != ifaces.end(); ++iter) {" + << endl << indent() << " " << extends << "Multiface::add(*iter);" << endl + << indent() << " }" << endl; + } + f_header_ << indent() << "}" << endl << indent() << "virtual ~" << service_name_ + << "Multiface() {}" << endl; + indent_down(); + + // Protected data members + f_header_ << " protected:" << endl; + indent_up(); + f_header_ << indent() << list_type << " ifaces_;" << endl << indent() << service_name_ + << "Multiface() {}" << endl << indent() << "void add(::std::shared_ptr<" + << service_name_ << "If> iface) {" << endl; + if (!extends.empty()) { + f_header_ << indent() << " " << extends << "Multiface::add(iface);" << endl; + } + f_header_ << indent() << " ifaces_.push_back(iface);" << endl << indent() << "}" << endl; + indent_down(); + + f_header_ << indent() << " public:" << endl; + indent_up(); + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* arglist = (*f_iter)->get_arglist(); + const vector<t_field*>& args = arglist->get_members(); + vector<t_field*>::const_iterator a_iter; + + string call = string("ifaces_[i]->") + (*f_iter)->get_name() + "("; + bool first = true; + if (is_complex_type((*f_iter)->get_returntype())) { + call += "_return"; + first = false; + } + for (a_iter = args.begin(); a_iter != args.end(); ++a_iter) { + if (first) { + first = false; + } else { + call += ", "; + } + call += (*a_iter)->get_name(); + } + call += ")"; + + f_header_ << indent() << function_signature(*f_iter, "") << " {" << endl; + indent_up(); + f_header_ << indent() << "size_t sz = ifaces_.size();" << endl << indent() << "size_t i = 0;" + << endl << indent() << "for (; i < (sz - 1); ++i) {" << endl; + indent_up(); + f_header_ << indent() << call << ";" << endl; + indent_down(); + f_header_ << indent() << "}" << endl; + + if (!(*f_iter)->get_returntype()->is_void()) { + if (is_complex_type((*f_iter)->get_returntype())) { + f_header_ << indent() << call << ";" << endl << indent() << "return;" << endl; + } else { + f_header_ << indent() << "return " << call << ";" << endl; + } + } else { + f_header_ << indent() << call << ";" << endl; + } + + indent_down(); + f_header_ << indent() << "}" << endl << endl; + } + + indent_down(); + f_header_ << indent() << "};" << endl << endl; +} + +/** + * Generates a service client definition. + * + * @param tservice The service to generate a server for. + */ +void t_cpp_generator::generate_service_client(t_service* tservice, string style) { + string ifstyle; + if (style == "Cob") { + ifstyle = "CobCl"; + } + + std::ostream& out = (gen_templates_ ? f_service_tcc_ : f_service_); + string template_header, template_suffix, short_suffix, protocol_type, _this; + string const prot_factory_type = "::apache::thrift::protocol::TProtocolFactory"; + if (gen_templates_) { + template_header = "template <class Protocol_>\n"; + short_suffix = "T"; + template_suffix = "T<Protocol_>"; + protocol_type = "Protocol_"; + _this = "this->"; + } else { + protocol_type = "::apache::thrift::protocol::TProtocol"; + } + string prot_ptr = "std::shared_ptr< " + protocol_type + ">"; + string client_suffix = "Client" + template_suffix; + string if_suffix = "If"; + if (style == "Cob") { + if_suffix += template_suffix; + } + + string extends = ""; + string extends_client = ""; + if (tservice->get_extends() != NULL) { + // TODO(simpkins): If gen_templates_ is enabled, we currently assume all + // parent services were also generated with templates enabled. + extends = type_name(tservice->get_extends()); + extends_client = ", public " + extends + style + client_suffix; + } + + // Generate the header portion + if (style == "Concurrent") { + f_header_ << "// The \'concurrent\' client is a thread safe client that correctly handles\n" + "// out of order responses. It is slower than the regular client, so should\n" + "// only be used when you need to share a connection among multiple threads\n"; + } + f_header_ << template_header << "class " << service_name_ << style << "Client" << short_suffix + << " : " + << "virtual public " << service_name_ << ifstyle << if_suffix << extends_client << " {" + << endl << " public:" << endl; + + indent_up(); + if (style != "Cob") { + f_header_ << indent() << service_name_ << style << "Client" << short_suffix << "(" << prot_ptr + << " prot"; + if (style == "Concurrent") { + f_header_ << ", std::shared_ptr<::apache::thrift::async::TConcurrentClientSyncInfo> sync"; + } + f_header_ << ") "; + + if (extends.empty()) { + if (style == "Concurrent") { + f_header_ << ": sync_(sync)" << endl; + } + f_header_ << "{" << endl; + f_header_ << indent() << " setProtocol" << short_suffix << "(prot);" << endl << indent() + << "}" << endl; + } else { + f_header_ << ":" << endl; + f_header_ << indent() << " " << extends << style << client_suffix << "(prot, prot"; + if (style == "Concurrent") { + f_header_ << ", sync"; + } + f_header_ << ") {}" << endl; + } + + f_header_ << indent() << service_name_ << style << "Client" << short_suffix << "(" << prot_ptr + << " iprot, " << prot_ptr << " oprot"; + if (style == "Concurrent") { + f_header_ << ", std::shared_ptr<::apache::thrift::async::TConcurrentClientSyncInfo> sync"; + } + f_header_ << ") "; + + if (extends.empty()) { + if (style == "Concurrent") { + f_header_ << ": sync_(sync)" << endl; + } + f_header_ << "{" << endl; + f_header_ << indent() << " setProtocol" << short_suffix << "(iprot,oprot);" << endl + << indent() << "}" << endl; + } else { + f_header_ << ":" << indent() << " " << extends << style << client_suffix + << "(iprot, oprot"; + if (style == "Concurrent") { + f_header_ << ", sync"; + } + f_header_ << ") {}" << endl; + } + + // create the setProtocol methods + if (extends.empty()) { + f_header_ << " private:" << endl; + // 1: one parameter + f_header_ << indent() << "void setProtocol" << short_suffix << "(" << prot_ptr << " prot) {" + << endl; + f_header_ << indent() << "setProtocol" << short_suffix << "(prot,prot);" << endl; + f_header_ << indent() << "}" << endl; + // 2: two parameter + f_header_ << indent() << "void setProtocol" << short_suffix << "(" << prot_ptr << " iprot, " + << prot_ptr << " oprot) {" << endl; + + f_header_ << indent() << " piprot_=iprot;" << endl << indent() << " poprot_=oprot;" << endl + << indent() << " iprot_ = iprot.get();" << endl << indent() + << " oprot_ = oprot.get();" << endl; + + f_header_ << indent() << "}" << endl; + f_header_ << " public:" << endl; + } + + // Generate getters for the protocols. + // Note that these are not currently templated for simplicity. + // TODO(simpkins): should they be templated? + f_header_ << indent() + << "std::shared_ptr< ::apache::thrift::protocol::TProtocol> getInputProtocol() {" + << endl << indent() << " return " << _this << "piprot_;" << endl << indent() << "}" + << endl; + + f_header_ << indent() + << "std::shared_ptr< ::apache::thrift::protocol::TProtocol> getOutputProtocol() {" + << endl << indent() << " return " << _this << "poprot_;" << endl << indent() << "}" + << endl; + + } else /* if (style == "Cob") */ { + f_header_ << indent() << service_name_ << style << "Client" << short_suffix << "(" + << "std::shared_ptr< ::apache::thrift::async::TAsyncChannel> channel, " + << "::apache::thrift::protocol::TProtocolFactory* protocolFactory) :" << endl; + if (extends.empty()) { + f_header_ << indent() << " channel_(channel)," << endl << indent() + << " itrans_(new ::apache::thrift::transport::TMemoryBuffer())," << endl + << indent() << " otrans_(new ::apache::thrift::transport::TMemoryBuffer())," + << endl; + if (gen_templates_) { + // TProtocolFactory classes return generic TProtocol pointers. + // We have to dynamic cast to the Protocol_ type we are expecting. + f_header_ << indent() << " piprot_(::std::dynamic_pointer_cast<Protocol_>(" + << "protocolFactory->getProtocol(itrans_)))," << endl << indent() + << " poprot_(::std::dynamic_pointer_cast<Protocol_>(" + << "protocolFactory->getProtocol(otrans_))) {" << endl; + // Throw a TException if either dynamic cast failed. + f_header_ << indent() << " if (!piprot_ || !poprot_) {" << endl << indent() + << " throw ::apache::thrift::TException(\"" + << "TProtocolFactory returned unexpected protocol type in " << service_name_ + << style << "Client" << short_suffix << " constructor\");" << endl << indent() + << " }" << endl; + } else { + f_header_ << indent() << " piprot_(protocolFactory->getProtocol(itrans_))," << endl + << indent() << " poprot_(protocolFactory->getProtocol(otrans_)) {" << endl; + } + f_header_ << indent() << " iprot_ = piprot_.get();" << endl << indent() + << " oprot_ = poprot_.get();" << endl << indent() << "}" << endl; + } else { + f_header_ << indent() << " " << extends << style << client_suffix + << "(channel, protocolFactory) {}" << endl; + } + } + + if (style == "Cob") { + f_header_ << indent() + << "::std::shared_ptr< ::apache::thrift::async::TAsyncChannel> getChannel() {" << endl + << indent() << " return " << _this << "channel_;" << endl << indent() << "}" << endl; + if (!gen_no_client_completion_) { + f_header_ << indent() << "virtual void completed__(bool /* success */) {}" << endl; + } + } + + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + indent(f_header_) << function_signature(*f_iter, ifstyle) << ";" << endl; + // TODO(dreiss): Use private inheritance to avoid generating thise in cob-style. + if (style == "Concurrent" && !(*f_iter)->is_oneway()) { + // concurrent clients need to move the seqid from the send function to the + // recv function. Oneway methods don't have a recv function, so we don't need to + // move the seqid for them. Attempting to do so would result in a seqid leak. + t_function send_function(g_type_i32, /*returning seqid*/ + string("send_") + (*f_iter)->get_name(), + (*f_iter)->get_arglist()); + indent(f_header_) << function_signature(&send_function, "") << ";" << endl; + } else { + t_function send_function(g_type_void, + string("send_") + (*f_iter)->get_name(), + (*f_iter)->get_arglist()); + indent(f_header_) << function_signature(&send_function, "") << ";" << endl; + } + if (!(*f_iter)->is_oneway()) { + if (style == "Concurrent") { + t_field seqIdArg(g_type_i32, "seqid"); + t_struct seqIdArgStruct(program_); + seqIdArgStruct.append(&seqIdArg); + t_function recv_function((*f_iter)->get_returntype(), + string("recv_") + (*f_iter)->get_name(), + &seqIdArgStruct); + indent(f_header_) << function_signature(&recv_function, "") << ";" << endl; + } else { + t_struct noargs(program_); + t_function recv_function((*f_iter)->get_returntype(), + string("recv_") + (*f_iter)->get_name(), + &noargs); + indent(f_header_) << function_signature(&recv_function, "") << ";" << endl; + } + } + } + indent_down(); + + if (extends.empty()) { + f_header_ << " protected:" << endl; + indent_up(); + + if (style == "Cob") { + f_header_ << indent() + << "::std::shared_ptr< ::apache::thrift::async::TAsyncChannel> channel_;" << endl + << indent() + << "::std::shared_ptr< ::apache::thrift::transport::TMemoryBuffer> itrans_;" << endl + << indent() + << "::std::shared_ptr< ::apache::thrift::transport::TMemoryBuffer> otrans_;" + << endl; + } + f_header_ << + indent() << prot_ptr << " piprot_;" << endl << + indent() << prot_ptr << " poprot_;" << endl << + indent() << protocol_type << "* iprot_;" << endl << + indent() << protocol_type << "* oprot_;" << endl; + + if (style == "Concurrent") { + f_header_ << + indent() << "std::shared_ptr<::apache::thrift::async::TConcurrentClientSyncInfo> sync_;"<<endl; + } + indent_down(); + } + + f_header_ << "};" << endl << endl; + + if (gen_templates_) { + // Output a backwards compatibility typedef using + // TProtocol as the template parameter. + f_header_ << "typedef " << service_name_ << style + << "ClientT< ::apache::thrift::protocol::TProtocol> " << service_name_ << style + << "Client;" << endl << endl; + } + + string scope = service_name_ + style + client_suffix + "::"; + + // Generate client method implementations + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + string seqIdCapture; + string seqIdUse; + string seqIdCommaUse; + if (style == "Concurrent" && !(*f_iter)->is_oneway()) { + seqIdCapture = "int32_t seqid = "; + seqIdUse = "seqid"; + seqIdCommaUse = ", seqid"; + } + + string funname = (*f_iter)->get_name(); + + // Open function + if (gen_templates_) { + indent(out) << template_header; + } + indent(out) << function_signature(*f_iter, ifstyle, scope) << endl; + scope_up(out); + indent(out) << seqIdCapture << "send_" << funname << "("; + + // Get the struct of function call params + t_struct* arg_struct = (*f_iter)->get_arglist(); + + // Declare the function arguments + const vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator fld_iter; + bool first = true; + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + if (first) { + first = false; + } else { + out << ", "; + } + out << (*fld_iter)->get_name(); + } + out << ");" << endl; + + if (style != "Cob") { + if (!(*f_iter)->is_oneway()) { + out << indent(); + if (!(*f_iter)->get_returntype()->is_void()) { + if (is_complex_type((*f_iter)->get_returntype())) { + out << "recv_" << funname << "(_return" << seqIdCommaUse << ");" << endl; + } else { + out << "return recv_" << funname << "(" << seqIdUse << ");" << endl; + } + } else { + out << "recv_" << funname << "(" << seqIdUse << ");" << endl; + } + } + } else { + if (!(*f_iter)->is_oneway()) { + out << indent() << _this << "channel_->sendAndRecvMessage(" + << "::std::bind(cob, this), " << _this << "otrans_.get(), " << _this << "itrans_.get());" + << endl; + } else { + out << indent() << _this << "channel_->sendMessage(" + << "::std::bind(cob, this), " << _this << "otrans_.get());" << endl; + } + } + scope_down(out); + out << endl; + + // if (style != "Cob") // TODO(dreiss): Libify the client and don't generate this for cob-style + if (true) { + t_type* send_func_return_type = g_type_void; + if (style == "Concurrent" && !(*f_iter)->is_oneway()) { + send_func_return_type = g_type_i32; + } + // Function for sending + t_function send_function(send_func_return_type, + string("send_") + (*f_iter)->get_name(), + (*f_iter)->get_arglist()); + + // Open the send function + if (gen_templates_) { + indent(out) << template_header; + } + indent(out) << function_signature(&send_function, "", scope) << endl; + scope_up(out); + + // Function arguments and results + string argsname = tservice->get_name() + "_" + (*f_iter)->get_name() + "_pargs"; + string resultname = tservice->get_name() + "_" + (*f_iter)->get_name() + "_presult"; + + string cseqidVal = "0"; + if (style == "Concurrent") { + if (!(*f_iter)->is_oneway()) { + cseqidVal = "this->sync_->generateSeqId()"; + } + } + // Serialize the request + out << + indent() << "int32_t cseqid = " << cseqidVal << ";" << endl; + if(style == "Concurrent") { + out << + indent() << "::apache::thrift::async::TConcurrentSendSentry sentry(this->sync_.get());" << endl; + } + if (style == "Cob") { + out << + indent() << _this << "otrans_->resetBuffer();" << endl; + } + out << + indent() << _this << "oprot_->writeMessageBegin(\"" << + (*f_iter)->get_name() << + "\", ::apache::thrift::protocol::" << ((*f_iter)->is_oneway() ? "T_ONEWAY" : "T_CALL") << + ", cseqid);" << endl << endl << + indent() << argsname << " args;" << endl; + + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + out << indent() << "args." << (*fld_iter)->get_name() << " = &" << (*fld_iter)->get_name() + << ";" << endl; + } + + out << indent() << "args.write(" << _this << "oprot_);" << endl << endl << indent() << _this + << "oprot_->writeMessageEnd();" << endl << indent() << _this + << "oprot_->getTransport()->writeEnd();" << endl << indent() << _this + << "oprot_->getTransport()->flush();" << endl; + + if (style == "Concurrent") { + out << endl << indent() << "sentry.commit();" << endl; + + if (!(*f_iter)->is_oneway()) { + out << indent() << "return cseqid;" << endl; + } + } + scope_down(out); + out << endl; + + // Generate recv function only if not an oneway function + if (!(*f_iter)->is_oneway()) { + t_struct noargs(program_); + + t_field seqIdArg(g_type_i32, "seqid"); + t_struct seqIdArgStruct(program_); + seqIdArgStruct.append(&seqIdArg); + + t_struct* recv_function_args = &noargs; + if (style == "Concurrent") { + recv_function_args = &seqIdArgStruct; + } + + t_function recv_function((*f_iter)->get_returntype(), + string("recv_") + (*f_iter)->get_name(), + recv_function_args); + // Open the recv function + if (gen_templates_) { + indent(out) << template_header; + } + indent(out) << function_signature(&recv_function, "", scope) << endl; + scope_up(out); + + out << endl << + indent() << "int32_t rseqid = 0;" << endl << + indent() << "std::string fname;" << endl << + indent() << "::apache::thrift::protocol::TMessageType mtype;" << endl; + if(style == "Concurrent") { + out << + endl << + indent() << "// the read mutex gets dropped and reacquired as part of waitForWork()" << endl << + indent() << "// The destructor of this sentry wakes up other clients" << endl << + indent() << "::apache::thrift::async::TConcurrentRecvSentry sentry(this->sync_.get(), seqid);" << endl; + } + if (style == "Cob" && !gen_no_client_completion_) { + out << indent() << "bool completed = false;" << endl << endl << indent() << "try {"; + indent_up(); + } + out << endl; + if (style == "Concurrent") { + out << + indent() << "while(true) {" << endl << + indent() << " if(!this->sync_->getPending(fname, mtype, rseqid)) {" << endl; + indent_up(); + indent_up(); + } + out << + indent() << _this << "iprot_->readMessageBegin(fname, mtype, rseqid);" << endl; + if (style == "Concurrent") { + scope_down(out); + out << indent() << "if(seqid == rseqid) {" << endl; + indent_up(); + } + out << + indent() << "if (mtype == ::apache::thrift::protocol::T_EXCEPTION) {" << endl << + indent() << " ::apache::thrift::TApplicationException x;" << endl << + indent() << " x.read(" << _this << "iprot_);" << endl << + indent() << " " << _this << "iprot_->readMessageEnd();" << endl << + indent() << " " << _this << "iprot_->getTransport()->readEnd();" << endl; + if (style == "Cob" && !gen_no_client_completion_) { + out << indent() << " completed = true;" << endl << indent() << " completed__(true);" + << endl; + } + if (style == "Concurrent") { + out << indent() << " sentry.commit();" << endl; + } + out << + indent() << " throw x;" << endl << + indent() << "}" << endl << + indent() << "if (mtype != ::apache::thrift::protocol::T_REPLY) {" << endl << + indent() << " " << _this << "iprot_->skip(" << "::apache::thrift::protocol::T_STRUCT);" << endl << + indent() << " " << _this << "iprot_->readMessageEnd();" << endl << + indent() << " " << _this << "iprot_->getTransport()->readEnd();" << endl; + if (style == "Cob" && !gen_no_client_completion_) { + out << indent() << " completed = true;" << endl << indent() << " completed__(false);" + << endl; + } + out << + indent() << "}" << endl << + indent() << "if (fname.compare(\"" << (*f_iter)->get_name() << "\") != 0) {" << endl << + indent() << " " << _this << "iprot_->skip(" << "::apache::thrift::protocol::T_STRUCT);" << endl << + indent() << " " << _this << "iprot_->readMessageEnd();" << endl << + indent() << " " << _this << "iprot_->getTransport()->readEnd();" << endl; + if (style == "Cob" && !gen_no_client_completion_) { + out << indent() << " completed = true;" << endl << indent() << " completed__(false);" + << endl; + } + if (style == "Concurrent") { + out << endl << + indent() << " // in a bad state, don't commit" << endl << + indent() << " using ::apache::thrift::protocol::TProtocolException;" << endl << + indent() << " throw TProtocolException(TProtocolException::INVALID_DATA);" << endl; + } + out << indent() << "}" << endl; + + if (!(*f_iter)->get_returntype()->is_void() + && !is_complex_type((*f_iter)->get_returntype())) { + t_field returnfield((*f_iter)->get_returntype(), "_return"); + out << indent() << declare_field(&returnfield) << endl; + } + + out << indent() << resultname << " result;" << endl; + + if (!(*f_iter)->get_returntype()->is_void()) { + out << indent() << "result.success = &_return;" << endl; + } + + out << indent() << "result.read(" << _this << "iprot_);" << endl << indent() << _this + << "iprot_->readMessageEnd();" << endl << indent() << _this + << "iprot_->getTransport()->readEnd();" << endl << endl; + + // Careful, only look for _result if not a void function + if (!(*f_iter)->get_returntype()->is_void()) { + if (is_complex_type((*f_iter)->get_returntype())) { + out << + indent() << "if (result.__isset.success) {" << endl; + out << + indent() << " // _return pointer has now been filled" << endl; + if (style == "Cob" && !gen_no_client_completion_) { + out << indent() << " completed = true;" << endl << indent() << " completed__(true);" + << endl; + } + if (style == "Concurrent") { + out << indent() << " sentry.commit();" << endl; + } + out << + indent() << " return;" << endl << + indent() << "}" << endl; + } else { + out << indent() << "if (result.__isset.success) {" << endl; + if (style == "Cob" && !gen_no_client_completion_) { + out << indent() << " completed = true;" << endl << indent() << " completed__(true);" + << endl; + } + if (style == "Concurrent") { + out << indent() << " sentry.commit();" << endl; + } + out << indent() << " return _return;" << endl << indent() << "}" << endl; + } + } + + t_struct* xs = (*f_iter)->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + out << indent() << "if (result.__isset." << (*x_iter)->get_name() << ") {" << endl; + if (style == "Cob" && !gen_no_client_completion_) { + out << indent() << " completed = true;" << endl << indent() << " completed__(true);" + << endl; + } + if (style == "Concurrent") { + out << indent() << " sentry.commit();" << endl; + } + out << indent() << " throw result." << (*x_iter)->get_name() << ";" << endl << indent() + << "}" << endl; + } + + // We only get here if we are a void function + if ((*f_iter)->get_returntype()->is_void()) { + if (style == "Cob" && !gen_no_client_completion_) { + out << indent() << "completed = true;" << endl << indent() << "completed__(true);" + << endl; + } + if (style == "Concurrent") { + out << indent() << "sentry.commit();" << endl; + } + indent(out) << "return;" << endl; + } else { + if (style == "Cob" && !gen_no_client_completion_) { + out << indent() << "completed = true;" << endl << indent() << "completed__(true);" + << endl; + } + if (style == "Concurrent") { + out << indent() << "// in a bad state, don't commit" << endl; + } + out << indent() << "throw " + "::apache::thrift::TApplicationException(::apache::thrift::" + "TApplicationException::MISSING_RESULT, \"" << (*f_iter)->get_name() + << " failed: unknown result\");" << endl; + } + if (style == "Concurrent") { + indent_down(); + indent_down(); + out << + indent() << " }" << endl << + indent() << " // seqid != rseqid" << endl << + indent() << " this->sync_->updatePending(fname, mtype, rseqid);" << endl << + endl << + indent() << " // this will temporarily unlock the readMutex, and let other clients get work done" << endl << + indent() << " this->sync_->waitForWork(seqid);" << endl << + indent() << "} // end while(true)" << endl; + } + if (style == "Cob" && !gen_no_client_completion_) { + indent_down(); + out << indent() << "} catch (...) {" << endl << indent() << " if (!completed) {" << endl + << indent() << " completed__(false);" << endl << indent() << " }" << endl + << indent() << " throw;" << endl << indent() << "}" << endl; + } + // Close function + scope_down(out); + out << endl; + } + } + } +} + +class ProcessorGenerator { +public: + ProcessorGenerator(t_cpp_generator* generator, t_service* service, const string& style); + + void run() { + generate_class_definition(); + + // Generate the dispatchCall() function + generate_dispatch_call(false); + if (generator_->gen_templates_) { + generate_dispatch_call(true); + } + + // Generate all of the process subfunctions + generate_process_functions(); + + generate_factory(); + } + + void generate_class_definition(); + void generate_dispatch_call(bool template_protocol); + void generate_process_functions(); + void generate_factory(); + +protected: + std::string type_name(t_type* ttype, bool in_typedef = false, bool arg = false) { + return generator_->type_name(ttype, in_typedef, arg); + } + + std::string indent() { return generator_->indent(); } + std::ostream& indent(std::ostream& os) { return generator_->indent(os); } + + void indent_up() { generator_->indent_up(); } + void indent_down() { generator_->indent_down(); } + + t_cpp_generator* generator_; + t_service* service_; + std::ostream& f_header_; + std::ostream& f_out_; + string service_name_; + string style_; + string pstyle_; + string class_name_; + string if_name_; + string factory_class_name_; + string finish_cob_; + string finish_cob_decl_; + string ret_type_; + string call_context_; + string cob_arg_; + string call_context_arg_; + string call_context_decl_; + string template_header_; + string template_suffix_; + string typename_str_; + string class_suffix_; + string extends_; +}; + +ProcessorGenerator::ProcessorGenerator(t_cpp_generator* generator, + t_service* service, + const string& style) + : generator_(generator), + service_(service), + f_header_(generator->f_header_), + f_out_(generator->gen_templates_ ? generator->f_service_tcc_ : generator->f_service_), + service_name_(generator->service_name_), + style_(style) { + if (style_ == "Cob") { + pstyle_ = "Async"; + class_name_ = service_name_ + pstyle_ + "Processor"; + if_name_ = service_name_ + "CobSvIf"; + + finish_cob_ = "::std::function<void(bool ok)> cob, "; + finish_cob_decl_ = "::std::function<void(bool ok)>, "; + cob_arg_ = "cob, "; + ret_type_ = "void "; + } else { + class_name_ = service_name_ + "Processor"; + if_name_ = service_name_ + "If"; + + ret_type_ = "bool "; + // TODO(edhall) callContext should eventually be added to TAsyncProcessor + call_context_ = ", void* callContext"; + call_context_arg_ = ", callContext"; + call_context_decl_ = ", void*"; + } + + factory_class_name_ = class_name_ + "Factory"; + + if (generator->gen_templates_) { + template_header_ = "template <class Protocol_>\n"; + template_suffix_ = "<Protocol_>"; + typename_str_ = "typename "; + class_name_ += "T"; + factory_class_name_ += "T"; + } + + if (service_->get_extends() != NULL) { + extends_ = type_name(service_->get_extends()) + pstyle_ + "Processor"; + if (generator_->gen_templates_) { + // TODO(simpkins): If gen_templates_ is enabled, we currently assume all + // parent services were also generated with templates enabled. + extends_ += "T<Protocol_>"; + } + } +} + +void ProcessorGenerator::generate_class_definition() { + // Generate the dispatch methods + vector<t_function*> functions = service_->get_functions(); + vector<t_function*>::iterator f_iter; + + string parent_class; + if (service_->get_extends() != NULL) { + parent_class = extends_; + } else { + if (style_ == "Cob") { + parent_class = "::apache::thrift::async::TAsyncDispatchProcessor"; + } else { + parent_class = "::apache::thrift::TDispatchProcessor"; + } + + if (generator_->gen_templates_) { + parent_class += "T<Protocol_>"; + } + } + + // Generate the header portion + f_header_ << template_header_ << "class " << class_name_ << " : public " << parent_class << " {" + << endl; + + // Protected data members + f_header_ << " protected:" << endl; + indent_up(); + f_header_ << indent() << "::std::shared_ptr<" << if_name_ << "> iface_;" << endl; + f_header_ << indent() << "virtual " << ret_type_ << "dispatchCall(" << finish_cob_ + << "::apache::thrift::protocol::TProtocol* iprot, " + << "::apache::thrift::protocol::TProtocol* oprot, " + << "const std::string& fname, int32_t seqid" << call_context_ << ");" << endl; + if (generator_->gen_templates_) { + f_header_ << indent() << "virtual " << ret_type_ << "dispatchCallTemplated(" << finish_cob_ + << "Protocol_* iprot, Protocol_* oprot, " + << "const std::string& fname, int32_t seqid" << call_context_ << ");" << endl; + } + indent_down(); + + // Process function declarations + f_header_ << " private:" << endl; + indent_up(); + + // Declare processMap_ + f_header_ << indent() << "typedef void (" << class_name_ << "::*" + << "ProcessFunction)(" << finish_cob_decl_ << "int32_t, " + << "::apache::thrift::protocol::TProtocol*, " + << "::apache::thrift::protocol::TProtocol*" << call_context_decl_ << ");" << endl; + if (generator_->gen_templates_) { + f_header_ << indent() << "typedef void (" << class_name_ << "::*" + << "SpecializedProcessFunction)(" << finish_cob_decl_ << "int32_t, " + << "Protocol_*, Protocol_*" << call_context_decl_ << ");" << endl << indent() + << "struct ProcessFunctions {" << endl << indent() << " ProcessFunction generic;" + << endl << indent() << " SpecializedProcessFunction specialized;" << endl << indent() + << " ProcessFunctions(ProcessFunction g, " + << "SpecializedProcessFunction s) :" << endl << indent() << " generic(g)," << endl + << indent() << " specialized(s) {}" << endl << indent() + << " ProcessFunctions() : generic(NULL), specialized(NULL) " + << "{}" << endl << indent() << "};" << endl << indent() + << "typedef std::map<std::string, ProcessFunctions> " + << "ProcessMap;" << endl; + } else { + f_header_ << indent() << "typedef std::map<std::string, ProcessFunction> " + << "ProcessMap;" << endl; + } + f_header_ << indent() << "ProcessMap processMap_;" << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + indent(f_header_) << "void process_" << (*f_iter)->get_name() << "(" << finish_cob_ + << "int32_t seqid, ::apache::thrift::protocol::TProtocol* iprot, " + "::apache::thrift::protocol::TProtocol* oprot" << call_context_ << ");" + << endl; + if (generator_->gen_templates_) { + indent(f_header_) << "void process_" << (*f_iter)->get_name() << "(" << finish_cob_ + << "int32_t seqid, Protocol_* iprot, Protocol_* oprot" << call_context_ + << ");" << endl; + } + if (style_ == "Cob") { + // XXX Factor this out, even if it is a pain. + string ret_arg = ((*f_iter)->get_returntype()->is_void() + ? "" + : ", const " + type_name((*f_iter)->get_returntype()) + "& _return"); + f_header_ << indent() << "void return_" << (*f_iter)->get_name() + << "(::std::function<void(bool ok)> cob, int32_t seqid, " + << "::apache::thrift::protocol::TProtocol* oprot, " + << "void* ctx" << ret_arg << ");" << endl; + if (generator_->gen_templates_) { + f_header_ << indent() << "void return_" << (*f_iter)->get_name() + << "(::std::function<void(bool ok)> cob, int32_t seqid, " + << "Protocol_* oprot, void* ctx" << ret_arg << ");" << endl; + } + // XXX Don't declare throw if it doesn't exist + f_header_ << indent() << "void throw_" << (*f_iter)->get_name() + << "(::std::function<void(bool ok)> cob, int32_t seqid, " + << "::apache::thrift::protocol::TProtocol* oprot, void* ctx, " + << "::apache::thrift::TDelayedException* _throw);" << endl; + if (generator_->gen_templates_) { + f_header_ << indent() << "void throw_" << (*f_iter)->get_name() + << "(::std::function<void(bool ok)> cob, int32_t seqid, " + << "Protocol_* oprot, void* ctx, " + << "::apache::thrift::TDelayedException* _throw);" << endl; + } + } + } + + f_header_ << " public:" << endl << indent() << class_name_ << "(::std::shared_ptr<" << if_name_ + << "> iface) :" << endl; + if (!extends_.empty()) { + f_header_ << indent() << " " << extends_ << "(iface)," << endl; + } + f_header_ << indent() << " iface_(iface) {" << endl; + indent_up(); + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + f_header_ << indent() << "processMap_[\"" << (*f_iter)->get_name() << "\"] = "; + if (generator_->gen_templates_) { + f_header_ << "ProcessFunctions(" << endl; + if (generator_->gen_templates_only_) { + indent(f_header_) << " NULL," << endl; + } else { + indent(f_header_) << " &" << class_name_ << "::process_" << (*f_iter)->get_name() << "," + << endl; + } + indent(f_header_) << " &" << class_name_ << "::process_" << (*f_iter)->get_name() << ")"; + } else { + f_header_ << "&" << class_name_ << "::process_" << (*f_iter)->get_name(); + } + f_header_ << ";" << endl; + } + + indent_down(); + f_header_ << indent() << "}" << endl << endl << indent() << "virtual ~" << class_name_ << "() {}" + << endl; + indent_down(); + f_header_ << "};" << endl << endl; + + if (generator_->gen_templates_) { + // Generate a backwards compatible typedef, for callers who don't know + // about the new template-style code. + // + // We can't use TProtocol as the template parameter, since ProcessorT + // provides overloaded versions of most methods, one of which accepts + // TProtocol pointers, and one which accepts Protocol_ pointers. This + // results in a compile error if instantiated with Protocol_ == TProtocol. + // Therefore, we define TDummyProtocol solely so we can use it as the + // template parameter here. + f_header_ << "typedef " << class_name_ << "< ::apache::thrift::protocol::TDummyProtocol > " + << service_name_ << pstyle_ << "Processor;" << endl << endl; + } +} + +void ProcessorGenerator::generate_dispatch_call(bool template_protocol) { + string protocol = "::apache::thrift::protocol::TProtocol"; + string function_suffix; + if (template_protocol) { + protocol = "Protocol_"; + // We call the generic version dispatchCall(), and the specialized + // version dispatchCallTemplated(). We can't call them both + // dispatchCall(), since this will cause the compiler to issue a warning if + // a service that doesn't use templates inherits from a service that does + // use templates: the compiler complains that the subclass only implements + // the generic version of dispatchCall(), and hides the templated version. + // Using different names for the two functions prevents this. + function_suffix = "Templated"; + } + + f_out_ << template_header_ << ret_type_ << class_name_ << template_suffix_ << "::dispatchCall" + << function_suffix << "(" << finish_cob_ << protocol << "* iprot, " << protocol + << "* oprot, " + << "const std::string& fname, int32_t seqid" << call_context_ << ") {" << endl; + indent_up(); + + // HOT: member function pointer map + f_out_ << indent() << typename_str_ << "ProcessMap::iterator pfn;" << endl << indent() + << "pfn = processMap_.find(fname);" << endl << indent() + << "if (pfn == processMap_.end()) {" << endl; + if (extends_.empty()) { + f_out_ << indent() << " iprot->skip(::apache::thrift::protocol::T_STRUCT);" << endl << indent() + << " iprot->readMessageEnd();" << endl << indent() + << " iprot->getTransport()->readEnd();" << endl << indent() + << " ::apache::thrift::TApplicationException " + "x(::apache::thrift::TApplicationException::UNKNOWN_METHOD, \"Invalid method name: " + "'\"+fname+\"'\");" << endl << indent() + << " oprot->writeMessageBegin(fname, ::apache::thrift::protocol::T_EXCEPTION, seqid);" + << endl << indent() << " x.write(oprot);" << endl << indent() + << " oprot->writeMessageEnd();" << endl << indent() + << " oprot->getTransport()->writeEnd();" << endl << indent() + << " oprot->getTransport()->flush();" << endl << indent() + << (style_ == "Cob" ? " return cob(true);" : " return true;") << endl; + } else { + f_out_ << indent() << " return " << extends_ << "::dispatchCall(" + << (style_ == "Cob" ? "cob, " : "") << "iprot, oprot, fname, seqid" << call_context_arg_ + << ");" << endl; + } + f_out_ << indent() << "}" << endl; + if (template_protocol) { + f_out_ << indent() << "(this->*(pfn->second.specialized))"; + } else { + if (generator_->gen_templates_only_) { + // TODO: This is a null pointer, so nothing good will come from calling + // it. Throw an exception instead. + f_out_ << indent() << "(this->*(pfn->second.generic))"; + } else if (generator_->gen_templates_) { + f_out_ << indent() << "(this->*(pfn->second.generic))"; + } else { + f_out_ << indent() << "(this->*(pfn->second))"; + } + } + f_out_ << "(" << cob_arg_ << "seqid, iprot, oprot" << call_context_arg_ << ");" << endl; + + // TODO(dreiss): return pfn ret? + if (style_ == "Cob") { + f_out_ << indent() << "return;" << endl; + } else { + f_out_ << indent() << "return true;" << endl; + } + + indent_down(); + f_out_ << "}" << endl << endl; +} + +void ProcessorGenerator::generate_process_functions() { + vector<t_function*> functions = service_->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + if (generator_->gen_templates_) { + generator_->generate_process_function(service_, *f_iter, style_, false); + generator_->generate_process_function(service_, *f_iter, style_, true); + } else { + generator_->generate_process_function(service_, *f_iter, style_, false); + } + } +} + +void ProcessorGenerator::generate_factory() { + string if_factory_name = if_name_ + "Factory"; + + // Generate the factory class definition + f_header_ << template_header_ << "class " << factory_class_name_ << " : public ::apache::thrift::" + << (style_ == "Cob" ? "async::TAsyncProcessorFactory" : "TProcessorFactory") << " {" + << endl << " public:" << endl; + indent_up(); + + f_header_ << indent() << factory_class_name_ << "(const ::std::shared_ptr< " << if_factory_name + << " >& handlerFactory) :" << endl << indent() + << " handlerFactory_(handlerFactory) {}" << endl << endl << indent() + << "::std::shared_ptr< ::apache::thrift::" + << (style_ == "Cob" ? "async::TAsyncProcessor" : "TProcessor") << " > " + << "getProcessor(const ::apache::thrift::TConnectionInfo& connInfo);" << endl; + + f_header_ << endl << " protected:" << endl << indent() << "::std::shared_ptr< " + << if_factory_name << " > handlerFactory_;" << endl; + + indent_down(); + f_header_ << "};" << endl << endl; + + // If we are generating templates, output a typedef for the plain + // factory name. + if (generator_->gen_templates_) { + f_header_ << "typedef " << factory_class_name_ + << "< ::apache::thrift::protocol::TDummyProtocol > " << service_name_ << pstyle_ + << "ProcessorFactory;" << endl << endl; + } + + // Generate the getProcessor() method + f_out_ << template_header_ << indent() << "::std::shared_ptr< ::apache::thrift::" + << (style_ == "Cob" ? "async::TAsyncProcessor" : "TProcessor") << " > " + << factory_class_name_ << template_suffix_ << "::getProcessor(" + << "const ::apache::thrift::TConnectionInfo& connInfo) {" << endl; + indent_up(); + + f_out_ << indent() << "::apache::thrift::ReleaseHandler< " << if_factory_name + << " > cleanup(handlerFactory_);" << endl << indent() << "::std::shared_ptr< " + << if_name_ << " > handler(" + << "handlerFactory_->getHandler(connInfo), cleanup);" << endl << indent() + << "::std::shared_ptr< ::apache::thrift::" + << (style_ == "Cob" ? "async::TAsyncProcessor" : "TProcessor") << " > " + << "processor(new " << class_name_ << template_suffix_ << "(handler));" << endl << indent() + << "return processor;" << endl; + + indent_down(); + f_out_ << indent() << "}" << endl << endl; +} + +/** + * Generates a service processor definition. + * + * @param tservice The service to generate a processor for. + */ +void t_cpp_generator::generate_service_processor(t_service* tservice, string style) { + ProcessorGenerator generator(this, tservice, style); + generator.run(); +} + +/** + * Generates a struct and helpers for a function. + * + * @param tfunction The function + */ +void t_cpp_generator::generate_function_helpers(t_service* tservice, t_function* tfunction) { + if (tfunction->is_oneway()) { + return; + } + + std::ostream& out = (gen_templates_ ? f_service_tcc_ : f_service_); + + t_struct result(program_, tservice->get_name() + "_" + tfunction->get_name() + "_result"); + t_field success(tfunction->get_returntype(), "success", 0); + if (!tfunction->get_returntype()->is_void()) { + result.append(&success); + } + + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& fields = xs->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + result.append(*f_iter); + } + + generate_struct_declaration(f_header_, &result, false); + generate_struct_definition(out, f_service_, &result, false); + generate_struct_reader(out, &result); + generate_struct_result_writer(out, &result); + + result.set_name(tservice->get_name() + "_" + tfunction->get_name() + "_presult"); + generate_struct_declaration(f_header_, &result, false, true, true, gen_cob_style_); + generate_struct_definition(out, f_service_, &result, false); + generate_struct_reader(out, &result, true); + if (gen_cob_style_) { + generate_struct_writer(out, &result, true); + } +} + +/** + * Generates a process function definition. + * + * @param tfunction The function to write a dispatcher for + */ +void t_cpp_generator::generate_process_function(t_service* tservice, + t_function* tfunction, + string style, + bool specialized) { + t_struct* arg_struct = tfunction->get_arglist(); + const std::vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + + t_struct* xs = tfunction->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + string service_func_name = "\"" + tservice->get_name() + "." + tfunction->get_name() + "\""; + + std::ostream& out = (gen_templates_ ? f_service_tcc_ : f_service_); + + string prot_type = (specialized ? "Protocol_" : "::apache::thrift::protocol::TProtocol"); + string class_suffix; + if (gen_templates_) { + class_suffix = "T<Protocol_>"; + } + + // I tried to do this as one function. I really did. But it was too hard. + if (style != "Cob") { + // Open function + if (gen_templates_) { + out << indent() << "template <class Protocol_>" << endl; + } + const bool unnamed_oprot_seqid = tfunction->is_oneway() && !(gen_templates_ && !specialized); + out << "void " << tservice->get_name() << "Processor" << class_suffix << "::" + << "process_" << tfunction->get_name() << "(" + << "int32_t" << (unnamed_oprot_seqid ? ", " : " seqid, ") << prot_type << "* iprot, " + << prot_type << "*" << (unnamed_oprot_seqid ? ", " : " oprot, ") << "void* callContext)" + << endl; + scope_up(out); + + string argsname = tservice->get_name() + "_" + tfunction->get_name() + "_args"; + string resultname = tservice->get_name() + "_" + tfunction->get_name() + "_result"; + + if (tfunction->is_oneway() && !unnamed_oprot_seqid) { + out << indent() << "(void) seqid;" << endl << indent() << "(void) oprot;" << endl; + } + + out << indent() << "void* ctx = NULL;" << endl << indent() + << "if (this->eventHandler_.get() != NULL) {" << endl << indent() + << " ctx = this->eventHandler_->getContext(" << service_func_name << ", callContext);" + << endl << indent() << "}" << endl << indent() + << "::apache::thrift::TProcessorContextFreer freer(" + << "this->eventHandler_.get(), ctx, " << service_func_name << ");" << endl << endl + << indent() << "if (this->eventHandler_.get() != NULL) {" << endl << indent() + << " this->eventHandler_->preRead(ctx, " << service_func_name << ");" << endl << indent() + << "}" << endl << endl << indent() << argsname << " args;" << endl << indent() + << "args.read(iprot);" << endl << indent() << "iprot->readMessageEnd();" << endl << indent() + << "uint32_t bytes = iprot->getTransport()->readEnd();" << endl << endl << indent() + << "if (this->eventHandler_.get() != NULL) {" << endl << indent() + << " this->eventHandler_->postRead(ctx, " << service_func_name << ", bytes);" << endl + << indent() << "}" << endl << endl; + + // Declare result + if (!tfunction->is_oneway()) { + out << indent() << resultname << " result;" << endl; + } + + // Try block for functions with exceptions + out << indent() << "try {" << endl; + indent_up(); + + // Generate the function call + bool first = true; + out << indent(); + if (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void()) { + if (is_complex_type(tfunction->get_returntype())) { + first = false; + out << "iface_->" << tfunction->get_name() << "(result.success"; + } else { + out << "result.success = iface_->" << tfunction->get_name() << "("; + } + } else { + out << "iface_->" << tfunction->get_name() << "("; + } + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + out << ", "; + } + out << "args." << (*f_iter)->get_name(); + } + out << ");" << endl; + + // Set isset on success field + if (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void()) { + out << indent() << "result.__isset.success = true;" << endl; + } + + indent_down(); + out << indent() << "}"; + + if (!tfunction->is_oneway()) { + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + out << " catch (" << type_name((*x_iter)->get_type()) << " &" << (*x_iter)->get_name() + << ") {" << endl; + if (!tfunction->is_oneway()) { + indent_up(); + out << indent() << "result." << (*x_iter)->get_name() << " = " << (*x_iter)->get_name() + << ";" << endl << indent() << "result.__isset." << (*x_iter)->get_name() << " = true;" + << endl; + indent_down(); + out << indent() << "}"; + } else { + out << "}"; + } + } + } + + if (!tfunction->is_oneway()) { + out << " catch (const std::exception& e) {" << endl; + } else { + out << " catch (const std::exception&) {" << endl; + } + + indent_up(); + out << indent() << "if (this->eventHandler_.get() != NULL) {" << endl << indent() + << " this->eventHandler_->handlerError(ctx, " << service_func_name << ");" << endl + << indent() << "}" << endl; + + if (!tfunction->is_oneway()) { + out << endl << indent() << "::apache::thrift::TApplicationException x(e.what());" << endl + << indent() << "oprot->writeMessageBegin(\"" << tfunction->get_name() + << "\", ::apache::thrift::protocol::T_EXCEPTION, seqid);" << endl << indent() + << "x.write(oprot);" << endl << indent() << "oprot->writeMessageEnd();" << endl + << indent() << "oprot->getTransport()->writeEnd();" << endl << indent() + << "oprot->getTransport()->flush();" << endl; + } + out << indent() << "return;" << endl; + indent_down(); + out << indent() << "}" << endl << endl; + + // Shortcut out here for oneway functions + if (tfunction->is_oneway()) { + out << indent() << "if (this->eventHandler_.get() != NULL) {" << endl << indent() + << " this->eventHandler_->asyncComplete(ctx, " << service_func_name << ");" << endl + << indent() << "}" << endl << endl << indent() << "return;" << endl; + indent_down(); + out << "}" << endl << endl; + return; + } + + // Serialize the result into a struct + out << indent() << "if (this->eventHandler_.get() != NULL) {" << endl << indent() + << " this->eventHandler_->preWrite(ctx, " << service_func_name << ");" << endl << indent() + << "}" << endl << endl << indent() << "oprot->writeMessageBegin(\"" << tfunction->get_name() + << "\", ::apache::thrift::protocol::T_REPLY, seqid);" << endl << indent() + << "result.write(oprot);" << endl << indent() << "oprot->writeMessageEnd();" << endl + << indent() << "bytes = oprot->getTransport()->writeEnd();" << endl << indent() + << "oprot->getTransport()->flush();" << endl << endl << indent() + << "if (this->eventHandler_.get() != NULL) {" << endl << indent() + << " this->eventHandler_->postWrite(ctx, " << service_func_name << ", bytes);" << endl + << indent() << "}" << endl; + + // Close function + scope_down(out); + out << endl; + } + + // Cob style. + else { + // Processor entry point. + // TODO(edhall) update for callContext when TEventServer is ready + if (gen_templates_) { + out << indent() << "template <class Protocol_>" << endl; + } + out << "void " << tservice->get_name() << "AsyncProcessor" << class_suffix << "::process_" + << tfunction->get_name() << "(::std::function<void(bool ok)> cob, int32_t seqid, " + << prot_type << "* iprot, " << prot_type << "* oprot)" << endl; + scope_up(out); + + // TODO(simpkins): we could try to consoldate this + // with the non-cob code above + if (gen_templates_ && !specialized) { + // If these are instances of Protocol_, instead of any old TProtocol, + // use the specialized process function instead. + out << indent() << "Protocol_* _iprot = dynamic_cast<Protocol_*>(iprot);" << endl << indent() + << "Protocol_* _oprot = dynamic_cast<Protocol_*>(oprot);" << endl << indent() + << "if (_iprot && _oprot) {" << endl << indent() << " return process_" + << tfunction->get_name() << "(cob, seqid, _iprot, _oprot);" << endl << indent() << "}" + << endl << indent() << "T_GENERIC_PROTOCOL(this, iprot, _iprot);" << endl << indent() + << "T_GENERIC_PROTOCOL(this, oprot, _oprot);" << endl << endl; + } + + if (tfunction->is_oneway()) { + out << indent() << "(void) seqid;" << endl << indent() << "(void) oprot;" << endl; + } + + out << indent() << tservice->get_name() + "_" + tfunction->get_name() << "_args args;" << endl + << indent() << "void* ctx = NULL;" << endl << indent() + << "if (this->eventHandler_.get() != NULL) {" << endl << indent() + << " ctx = this->eventHandler_->getContext(" << service_func_name << ", NULL);" << endl + << indent() << "}" << endl << indent() << "::apache::thrift::TProcessorContextFreer freer(" + << "this->eventHandler_.get(), ctx, " << service_func_name << ");" << endl << endl + << indent() << "try {" << endl; + indent_up(); + out << indent() << "if (this->eventHandler_.get() != NULL) {" << endl << indent() + << " this->eventHandler_->preRead(ctx, " << service_func_name << ");" << endl << indent() + << "}" << endl << indent() << "args.read(iprot);" << endl << indent() + << "iprot->readMessageEnd();" << endl << indent() + << "uint32_t bytes = iprot->getTransport()->readEnd();" << endl << indent() + << "if (this->eventHandler_.get() != NULL) {" << endl << indent() + << " this->eventHandler_->postRead(ctx, " << service_func_name << ", bytes);" << endl + << indent() << "}" << endl; + scope_down(out); + + // TODO(dreiss): Handle TExceptions? Expose to server? + out << indent() << "catch (const std::exception&) {" << endl << indent() + << " if (this->eventHandler_.get() != NULL) {" << endl << indent() + << " this->eventHandler_->handlerError(ctx, " << service_func_name << ");" << endl + << indent() << " }" << endl << indent() << " return cob(false);" << endl << indent() + << "}" << endl; + + if (tfunction->is_oneway()) { + out << indent() << "if (this->eventHandler_.get() != NULL) {" << endl << indent() + << " this->eventHandler_->asyncComplete(ctx, " << service_func_name << ");" << endl + << indent() << "}" << endl; + } + // TODO(dreiss): Figure out a strategy for exceptions in async handlers. + out << indent() << "freer.unregister();" << endl; + if (tfunction->is_oneway()) { + // No return. Just hand off our cob. + // TODO(dreiss): Call the cob immediately? + out << indent() << "iface_->" << tfunction->get_name() << "(" + << "::std::bind(cob, true)" << endl; + indent_up(); + indent_up(); + } else { + string ret_arg, ret_placeholder; + if (!tfunction->get_returntype()->is_void()) { + ret_arg = ", const " + type_name(tfunction->get_returntype()) + "& _return"; + ret_placeholder = ", ::std::placeholders::_1"; + } + + // When gen_templates_ is true, the return_ and throw_ functions are + // overloaded. We have to declare pointers to them so that the compiler + // can resolve the correct overloaded version. + out << indent() << "void (" << tservice->get_name() << "AsyncProcessor" << class_suffix + << "::*return_fn)(::std::function<void(bool ok)> " + << "cob, int32_t seqid, " << prot_type << "* oprot, void* ctx" << ret_arg + << ") =" << endl; + out << indent() << " &" << tservice->get_name() << "AsyncProcessor" << class_suffix + << "::return_" << tfunction->get_name() << ";" << endl; + if (!xceptions.empty()) { + out << indent() << "void (" << tservice->get_name() << "AsyncProcessor" << class_suffix + << "::*throw_fn)(::std::function<void(bool ok)> " + << "cob, int32_t seqid, " << prot_type << "* oprot, void* ctx, " + << "::apache::thrift::TDelayedException* _throw) =" << endl; + out << indent() << " &" << tservice->get_name() << "AsyncProcessor" << class_suffix + << "::throw_" << tfunction->get_name() << ";" << endl; + } + + out << indent() << "iface_->" << tfunction->get_name() << "(" << endl; + indent_up(); + indent_up(); + out << indent() << "::std::bind(return_fn, this, cob, seqid, oprot, ctx" << ret_placeholder + << ")"; + if (!xceptions.empty()) { + out << ',' << endl << indent() << "::std::bind(throw_fn, this, cob, seqid, oprot, " + << "ctx, ::std::placeholders::_1)"; + } + } + + // XXX Whitespace cleanup. + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + out << ',' << endl << indent() << "args." << (*f_iter)->get_name(); + } + out << ");" << endl; + indent_down(); + indent_down(); + scope_down(out); + out << endl; + + // Normal return. + if (!tfunction->is_oneway()) { + string ret_arg_decl, ret_arg_name; + if (!tfunction->get_returntype()->is_void()) { + ret_arg_decl = ", const " + type_name(tfunction->get_returntype()) + "& _return"; + ret_arg_name = ", _return"; + } + if (gen_templates_) { + out << indent() << "template <class Protocol_>" << endl; + } + out << "void " << tservice->get_name() << "AsyncProcessor" << class_suffix << "::return_" + << tfunction->get_name() << "(::std::function<void(bool ok)> cob, int32_t seqid, " + << prot_type << "* oprot, void* ctx" << ret_arg_decl << ')' << endl; + scope_up(out); + + if (gen_templates_ && !specialized) { + // If oprot is a Protocol_ instance, + // use the specialized return function instead. + out << indent() << "Protocol_* _oprot = dynamic_cast<Protocol_*>(oprot);" << endl + << indent() << "if (_oprot) {" << endl << indent() << " return return_" + << tfunction->get_name() << "(cob, seqid, _oprot, ctx" << ret_arg_name << ");" << endl + << indent() << "}" << endl << indent() << "T_GENERIC_PROTOCOL(this, oprot, _oprot);" + << endl << endl; + } + + out << indent() << tservice->get_name() << "_" << tfunction->get_name() << "_presult result;" + << endl; + if (!tfunction->get_returntype()->is_void()) { + // The const_cast here is unfortunate, but it would be a pain to avoid, + // and we only do a write with this struct, which is const-safe. + out << indent() << "result.success = const_cast<" << type_name(tfunction->get_returntype()) + << "*>(&_return);" << endl << indent() << "result.__isset.success = true;" << endl; + } + // Serialize the result into a struct + out << endl << indent() << "if (this->eventHandler_.get() != NULL) {" << endl << indent() + << " ctx = this->eventHandler_->getContext(" << service_func_name << ", NULL);" << endl + << indent() << "}" << endl << indent() + << "::apache::thrift::TProcessorContextFreer freer(" + << "this->eventHandler_.get(), ctx, " << service_func_name << ");" << endl << endl + << indent() << "if (this->eventHandler_.get() != NULL) {" << endl << indent() + << " this->eventHandler_->preWrite(ctx, " << service_func_name << ");" << endl + << indent() << "}" << endl << endl << indent() << "oprot->writeMessageBegin(\"" + << tfunction->get_name() << "\", ::apache::thrift::protocol::T_REPLY, seqid);" << endl + << indent() << "result.write(oprot);" << endl << indent() << "oprot->writeMessageEnd();" + << endl << indent() << "uint32_t bytes = oprot->getTransport()->writeEnd();" << endl + << indent() << "oprot->getTransport()->flush();" << endl << indent() + << "if (this->eventHandler_.get() != NULL) {" << endl << indent() + << " this->eventHandler_->postWrite(ctx, " << service_func_name << ", bytes);" << endl + << indent() << "}" << endl << indent() << "return cob(true);" << endl; + scope_down(out); + out << endl; + } + + // Exception return. + if (!tfunction->is_oneway() && !xceptions.empty()) { + if (gen_templates_) { + out << indent() << "template <class Protocol_>" << endl; + } + out << "void " << tservice->get_name() << "AsyncProcessor" << class_suffix << "::throw_" + << tfunction->get_name() << "(::std::function<void(bool ok)> cob, int32_t seqid, " + << prot_type << "* oprot, void* ctx, " + << "::apache::thrift::TDelayedException* _throw)" << endl; + scope_up(out); + + if (gen_templates_ && !specialized) { + // If oprot is a Protocol_ instance, + // use the specialized throw function instead. + out << indent() << "Protocol_* _oprot = dynamic_cast<Protocol_*>(oprot);" << endl + << indent() << "if (_oprot) {" << endl << indent() << " return throw_" + << tfunction->get_name() << "(cob, seqid, _oprot, ctx, _throw);" << endl << indent() + << "}" << endl << indent() << "T_GENERIC_PROTOCOL(this, oprot, _oprot);" << endl + << endl; + } + + // Get the event handler context + out << endl << indent() << "if (this->eventHandler_.get() != NULL) {" << endl << indent() + << " ctx = this->eventHandler_->getContext(" << service_func_name << ", NULL);" << endl + << indent() << "}" << endl << indent() + << "::apache::thrift::TProcessorContextFreer freer(" + << "this->eventHandler_.get(), ctx, " << service_func_name << ");" << endl << endl; + + // Throw the TDelayedException, and catch the result + out << indent() << tservice->get_name() << "_" << tfunction->get_name() << "_result result;" + << endl << endl << indent() << "try {" << endl; + indent_up(); + out << indent() << "_throw->throw_it();" << endl << indent() << "return cob(false);" + << endl; // Is this possible? TBD. + indent_down(); + out << indent() << '}'; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + out << " catch (" << type_name((*x_iter)->get_type()) << " &" << (*x_iter)->get_name() + << ") {" << endl; + indent_up(); + out << indent() << "result." << (*x_iter)->get_name() << " = " << (*x_iter)->get_name() + << ";" << endl << indent() << "result.__isset." << (*x_iter)->get_name() << " = true;" + << endl; + scope_down(out); + } + + // Handle the case where an undeclared exception is thrown + out << " catch (std::exception& e) {" << endl; + indent_up(); + out << indent() << "if (this->eventHandler_.get() != NULL) {" << endl << indent() + << " this->eventHandler_->handlerError(ctx, " << service_func_name << ");" << endl + << indent() << "}" << endl << endl << indent() + << "::apache::thrift::TApplicationException x(e.what());" << endl << indent() + << "oprot->writeMessageBegin(\"" << tfunction->get_name() + << "\", ::apache::thrift::protocol::T_EXCEPTION, seqid);" << endl << indent() + << "x.write(oprot);" << endl << indent() << "oprot->writeMessageEnd();" << endl + << indent() << "oprot->getTransport()->writeEnd();" << endl << indent() + << "oprot->getTransport()->flush();" << endl << + // We pass true to the cob here, since we did successfully write a + // response, even though it is an exception response. + // It looks like the argument is currently ignored, anyway. + indent() << "return cob(true);" << endl; + scope_down(out); + + // Serialize the result into a struct + out << indent() << "if (this->eventHandler_.get() != NULL) {" << endl << indent() + << " this->eventHandler_->preWrite(ctx, " << service_func_name << ");" << endl + << indent() << "}" << endl << endl << indent() << "oprot->writeMessageBegin(\"" + << tfunction->get_name() << "\", ::apache::thrift::protocol::T_REPLY, seqid);" << endl + << indent() << "result.write(oprot);" << endl << indent() << "oprot->writeMessageEnd();" + << endl << indent() << "uint32_t bytes = oprot->getTransport()->writeEnd();" << endl + << indent() << "oprot->getTransport()->flush();" << endl << indent() + << "if (this->eventHandler_.get() != NULL) {" << endl << indent() + << " this->eventHandler_->postWrite(ctx, " << service_func_name << ", bytes);" << endl + << indent() << "}" << endl << indent() << "return cob(true);" << endl; + scope_down(out); + out << endl; + } // for each function + } // cob style +} + +/** + * Generates a skeleton file of a server + * + * @param tservice The service to generate a server for. + */ +void t_cpp_generator::generate_service_skeleton(t_service* tservice) { + string svcname = tservice->get_name(); + + // Service implementation file includes + string f_skeleton_name = get_out_dir() + svcname + "_server.skeleton.cpp"; + + string ns = namespace_prefix(tservice->get_program()->get_namespace("cpp")); + + ofstream_with_content_based_conditional_update f_skeleton; + f_skeleton.open(f_skeleton_name.c_str()); + f_skeleton << "// This autogenerated skeleton file illustrates how to build a server." << endl + << "// You should copy it to another filename to avoid overwriting it." << endl << endl + << "#include \"" << get_include_prefix(*get_program()) << svcname << ".h\"" << endl + << "#include <thrift/protocol/TBinaryProtocol.h>" << endl + << "#include <thrift/server/TSimpleServer.h>" << endl + << "#include <thrift/transport/TServerSocket.h>" << endl + << "#include <thrift/transport/TBufferTransports.h>" << endl << endl + << "using namespace ::apache::thrift;" << endl + << "using namespace ::apache::thrift::protocol;" << endl + << "using namespace ::apache::thrift::transport;" << endl + << "using namespace ::apache::thrift::server;" << endl << endl; + + // the following code would not compile: + // using namespace ; + // using namespace ::; + if ((!ns.empty()) && (ns.compare(" ::") != 0)) { + f_skeleton << "using namespace " << string(ns, 0, ns.size() - 2) << ";" << endl << endl; + } + + f_skeleton << "class " << svcname << "Handler : virtual public " << svcname << "If {" << endl + << " public:" << endl; + indent_up(); + f_skeleton << indent() << svcname << "Handler() {" << endl << indent() + << " // Your initialization goes here" << endl << indent() << "}" << endl << endl; + + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_java_doc(f_skeleton, *f_iter); + f_skeleton << indent() << function_signature(*f_iter, "") << " {" << endl << indent() + << " // Your implementation goes here" << endl << indent() << " printf(\"" + << (*f_iter)->get_name() << "\\n\");" << endl << indent() << "}" << endl << endl; + } + + indent_down(); + f_skeleton << "};" << endl << endl; + + f_skeleton << indent() << "int main(int argc, char **argv) {" << endl; + indent_up(); + f_skeleton + << indent() << "int port = 9090;" << endl << indent() << "::std::shared_ptr<" << svcname + << "Handler> handler(new " << svcname << "Handler());" << endl << indent() + << "::std::shared_ptr<TProcessor> processor(new " << svcname << "Processor(handler));" << endl + << indent() << "::std::shared_ptr<TServerTransport> serverTransport(new TServerSocket(port));" + << endl << indent() + << "::std::shared_ptr<TTransportFactory> transportFactory(new TBufferedTransportFactory());" << endl + << indent() << "::std::shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());" + << endl << endl << indent() + << "TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory);" + << endl << indent() << "server.serve();" << endl << indent() << "return 0;" << endl; + indent_down(); + f_skeleton << "}" << endl << endl; + + // Close the files + f_skeleton.close(); +} + +/** + * Deserializes a field of any type. + */ +void t_cpp_generator::generate_deserialize_field(ostream& out, + t_field* tfield, + string prefix, + string suffix) { + t_type* type = get_true_type(tfield->get_type()); + + if (type->is_void()) { + throw "CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name(); + } + + string name = prefix + tfield->get_name() + suffix; + + if (type->is_struct() || type->is_xception()) { + generate_deserialize_struct(out, (t_struct*)type, name, is_reference(tfield)); + } else if (type->is_container()) { + generate_deserialize_container(out, type, name); + } else if (type->is_base_type()) { + indent(out) << "xfer += iprot->"; + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + out << "readBinary(" << name << ");"; + } else { + out << "readString(" << name << ");"; + } + break; + case t_base_type::TYPE_BOOL: + out << "readBool(" << name << ");"; + break; + case t_base_type::TYPE_I8: + out << "readByte(" << name << ");"; + break; + case t_base_type::TYPE_I16: + out << "readI16(" << name << ");"; + break; + case t_base_type::TYPE_I32: + out << "readI32(" << name << ");"; + break; + case t_base_type::TYPE_I64: + out << "readI64(" << name << ");"; + break; + case t_base_type::TYPE_DOUBLE: + out << "readDouble(" << name << ");"; + break; + default: + throw "compiler error: no C++ reader for base type " + t_base_type::t_base_name(tbase) + name; + } + out << endl; + } else if (type->is_enum()) { + string t = tmp("ecast"); + out << indent() << "int32_t " << t << ";" << endl << indent() << "xfer += iprot->readI32(" << t + << ");" << endl << indent() << name << " = (" << type_name(type) << ")" << t << ";" << endl; + } else { + printf("DO NOT KNOW HOW TO DESERIALIZE FIELD '%s' TYPE '%s'\n", + tfield->get_name().c_str(), + type_name(type).c_str()); + } +} + +/** + * Generates an unserializer for a variable. This makes two key assumptions, + * first that there is a const char* variable named data that points to the + * buffer for deserialization, and that there is a variable protocol which + * is a reference to a TProtocol serialization object. + */ +void t_cpp_generator::generate_deserialize_struct(ostream& out, + t_struct* tstruct, + string prefix, + bool pointer) { + if (pointer) { + indent(out) << "if (!" << prefix << ") { " << endl; + indent(out) << " " << prefix << " = ::std::shared_ptr<" << type_name(tstruct) << ">(new " + << type_name(tstruct) << ");" << endl; + indent(out) << "}" << endl; + indent(out) << "xfer += " << prefix << "->read(iprot);" << endl; + indent(out) << "bool wasSet = false;" << endl; + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = members.begin(); f_iter != members.end(); ++f_iter) { + + indent(out) << "if (" << prefix << "->__isset." << (*f_iter)->get_name() + << ") { wasSet = true; }" << endl; + } + indent(out) << "if (!wasSet) { " << prefix << ".reset(); }" << endl; + } else { + indent(out) << "xfer += " << prefix << ".read(iprot);" << endl; + } +} + +void t_cpp_generator::generate_deserialize_container(ostream& out, t_type* ttype, string prefix) { + scope_up(out); + + string size = tmp("_size"); + string ktype = tmp("_ktype"); + string vtype = tmp("_vtype"); + string etype = tmp("_etype"); + + t_container* tcontainer = (t_container*)ttype; + bool use_push = tcontainer->has_cpp_name(); + + indent(out) << prefix << ".clear();" << endl << indent() << "uint32_t " << size << ";" << endl; + + // Declare variables, read header + if (ttype->is_map()) { + out << indent() << "::apache::thrift::protocol::TType " << ktype << ";" << endl << indent() + << "::apache::thrift::protocol::TType " << vtype << ";" << endl << indent() + << "xfer += iprot->readMapBegin(" << ktype << ", " << vtype << ", " << size << ");" << endl; + } else if (ttype->is_set()) { + out << indent() << "::apache::thrift::protocol::TType " << etype << ";" << endl << indent() + << "xfer += iprot->readSetBegin(" << etype << ", " << size << ");" << endl; + } else if (ttype->is_list()) { + out << indent() << "::apache::thrift::protocol::TType " << etype << ";" << endl << indent() + << "xfer += iprot->readListBegin(" << etype << ", " << size << ");" << endl; + if (!use_push) { + indent(out) << prefix << ".resize(" << size << ");" << endl; + } + } + + // For loop iterates over elements + string i = tmp("_i"); + out << indent() << "uint32_t " << i << ";" << endl << indent() << "for (" << i << " = 0; " << i + << " < " << size << "; ++" << i << ")" << endl; + + scope_up(out); + + if (ttype->is_map()) { + generate_deserialize_map_element(out, (t_map*)ttype, prefix); + } else if (ttype->is_set()) { + generate_deserialize_set_element(out, (t_set*)ttype, prefix); + } else if (ttype->is_list()) { + generate_deserialize_list_element(out, (t_list*)ttype, prefix, use_push, i); + } + + scope_down(out); + + // Read container end + if (ttype->is_map()) { + indent(out) << "xfer += iprot->readMapEnd();" << endl; + } else if (ttype->is_set()) { + indent(out) << "xfer += iprot->readSetEnd();" << endl; + } else if (ttype->is_list()) { + indent(out) << "xfer += iprot->readListEnd();" << endl; + } + + scope_down(out); +} + +/** + * Generates code to deserialize a map + */ +void t_cpp_generator::generate_deserialize_map_element(ostream& out, t_map* tmap, string prefix) { + string key = tmp("_key"); + string val = tmp("_val"); + t_field fkey(tmap->get_key_type(), key); + t_field fval(tmap->get_val_type(), val); + + out << indent() << declare_field(&fkey) << endl; + + generate_deserialize_field(out, &fkey); + indent(out) << declare_field(&fval, false, false, false, true) << " = " << prefix << "[" << key + << "];" << endl; + + generate_deserialize_field(out, &fval); +} + +void t_cpp_generator::generate_deserialize_set_element(ostream& out, t_set* tset, string prefix) { + string elem = tmp("_elem"); + t_field felem(tset->get_elem_type(), elem); + + indent(out) << declare_field(&felem) << endl; + + generate_deserialize_field(out, &felem); + + indent(out) << prefix << ".insert(" << elem << ");" << endl; +} + +void t_cpp_generator::generate_deserialize_list_element(ostream& out, + t_list* tlist, + string prefix, + bool use_push, + string index) { + if (use_push) { + string elem = tmp("_elem"); + t_field felem(tlist->get_elem_type(), elem); + indent(out) << declare_field(&felem) << endl; + generate_deserialize_field(out, &felem); + indent(out) << prefix << ".push_back(" << elem << ");" << endl; + } else { + t_field felem(tlist->get_elem_type(), prefix + "[" + index + "]"); + generate_deserialize_field(out, &felem); + } +} + +/** + * Serializes a field of any type. + * + * @param tfield The field to serialize + * @param prefix Name to prepend to field name + */ +void t_cpp_generator::generate_serialize_field(ostream& out, + t_field* tfield, + string prefix, + string suffix) { + t_type* type = get_true_type(tfield->get_type()); + + string name = prefix + tfield->get_name() + suffix; + + // Do nothing for void types + if (type->is_void()) { + throw "CANNOT GENERATE SERIALIZE CODE FOR void TYPE: " + name; + } + + if (type->is_struct() || type->is_xception()) { + generate_serialize_struct(out, (t_struct*)type, name, is_reference(tfield)); + } else if (type->is_container()) { + generate_serialize_container(out, type, name); + } else if (type->is_base_type() || type->is_enum()) { + + indent(out) << "xfer += oprot->"; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + out << "writeBinary(" << name << ");"; + } else { + out << "writeString(" << name << ");"; + } + break; + case t_base_type::TYPE_BOOL: + out << "writeBool(" << name << ");"; + break; + case t_base_type::TYPE_I8: + out << "writeByte(" << name << ");"; + break; + case t_base_type::TYPE_I16: + out << "writeI16(" << name << ");"; + break; + case t_base_type::TYPE_I32: + out << "writeI32(" << name << ");"; + break; + case t_base_type::TYPE_I64: + out << "writeI64(" << name << ");"; + break; + case t_base_type::TYPE_DOUBLE: + out << "writeDouble(" << name << ");"; + break; + default: + throw "compiler error: no C++ writer for base type " + t_base_type::t_base_name(tbase) + + name; + } + } else if (type->is_enum()) { + out << "writeI32((int32_t)" << name << ");"; + } + out << endl; + } else { + printf("DO NOT KNOW HOW TO SERIALIZE FIELD '%s' TYPE '%s'\n", + name.c_str(), + type_name(type).c_str()); + } +} + +/** + * Serializes all the members of a struct. + * + * @param tstruct The struct to serialize + * @param prefix String prefix to attach to all fields + */ +void t_cpp_generator::generate_serialize_struct(ostream& out, + t_struct* tstruct, + string prefix, + bool pointer) { + if (pointer) { + indent(out) << "if (" << prefix << ") {" << endl; + indent(out) << " xfer += " << prefix << "->write(oprot); " << endl; + indent(out) << "} else {" + << "oprot->writeStructBegin(\"" << tstruct->get_name() << "\"); " << endl; + indent(out) << " oprot->writeStructEnd();" << endl; + indent(out) << " oprot->writeFieldStop();" << endl; + indent(out) << "}" << endl; + } else { + indent(out) << "xfer += " << prefix << ".write(oprot);" << endl; + } +} + +void t_cpp_generator::generate_serialize_container(ostream& out, t_type* ttype, string prefix) { + scope_up(out); + + if (ttype->is_map()) { + indent(out) << "xfer += oprot->writeMapBegin(" << type_to_enum(((t_map*)ttype)->get_key_type()) + << ", " << type_to_enum(((t_map*)ttype)->get_val_type()) << ", " + << "static_cast<uint32_t>(" << prefix << ".size()));" << endl; + } else if (ttype->is_set()) { + indent(out) << "xfer += oprot->writeSetBegin(" << type_to_enum(((t_set*)ttype)->get_elem_type()) + << ", " + << "static_cast<uint32_t>(" << prefix << ".size()));" << endl; + } else if (ttype->is_list()) { + indent(out) << "xfer += oprot->writeListBegin(" + << type_to_enum(((t_list*)ttype)->get_elem_type()) << ", " + << "static_cast<uint32_t>(" << prefix << ".size()));" << endl; + } + + string iter = tmp("_iter"); + out << indent() << type_name(ttype) << "::const_iterator " << iter << ";" << endl << indent() + << "for (" << iter << " = " << prefix << ".begin(); " << iter << " != " << prefix + << ".end(); ++" << iter << ")" << endl; + scope_up(out); + if (ttype->is_map()) { + generate_serialize_map_element(out, (t_map*)ttype, iter); + } else if (ttype->is_set()) { + generate_serialize_set_element(out, (t_set*)ttype, iter); + } else if (ttype->is_list()) { + generate_serialize_list_element(out, (t_list*)ttype, iter); + } + scope_down(out); + + if (ttype->is_map()) { + indent(out) << "xfer += oprot->writeMapEnd();" << endl; + } else if (ttype->is_set()) { + indent(out) << "xfer += oprot->writeSetEnd();" << endl; + } else if (ttype->is_list()) { + indent(out) << "xfer += oprot->writeListEnd();" << endl; + } + + scope_down(out); +} + +/** + * Serializes the members of a map. + * + */ +void t_cpp_generator::generate_serialize_map_element(ostream& out, t_map* tmap, string iter) { + t_field kfield(tmap->get_key_type(), iter + "->first"); + generate_serialize_field(out, &kfield, ""); + + t_field vfield(tmap->get_val_type(), iter + "->second"); + generate_serialize_field(out, &vfield, ""); +} + +/** + * Serializes the members of a set. + */ +void t_cpp_generator::generate_serialize_set_element(ostream& out, t_set* tset, string iter) { + t_field efield(tset->get_elem_type(), "(*" + iter + ")"); + generate_serialize_field(out, &efield, ""); +} + +/** + * Serializes the members of a list. + */ +void t_cpp_generator::generate_serialize_list_element(ostream& out, t_list* tlist, string iter) { + t_field efield(tlist->get_elem_type(), "(*" + iter + ")"); + generate_serialize_field(out, &efield, ""); +} + +/** + * Makes a :: prefix for a namespace + * + * @param ns The namespace, w/ periods in it + * @return Namespaces + */ +string t_cpp_generator::namespace_prefix(string ns) { + // Always start with "::", to avoid possible name collisions with + // other names in one of the current namespaces. + // + // We also need a leading space, in case the name is used inside of a + // template parameter. "MyTemplate<::foo::Bar>" is not valid C++, + // since "<:" is an alternative token for "[". + string result = " ::"; + + if (ns.size() == 0) { + return result; + } + string::size_type loc; + while ((loc = ns.find(".")) != string::npos) { + result += ns.substr(0, loc); + result += "::"; + ns = ns.substr(loc + 1); + } + if (ns.size() > 0) { + result += ns + "::"; + } + return result; +} + +/** + * Opens namespace. + * + * @param ns The namespace, w/ periods in it + * @return Namespaces + */ +string t_cpp_generator::namespace_open(string ns) { + if (ns.size() == 0) { + return ""; + } + string result = ""; + string separator = ""; + string::size_type loc; + while ((loc = ns.find(".")) != string::npos) { + result += separator; + result += "namespace "; + result += ns.substr(0, loc); + result += " {"; + separator = " "; + ns = ns.substr(loc + 1); + } + if (ns.size() > 0) { + result += separator + "namespace " + ns + " {"; + } + return result; +} + +/** + * Closes namespace. + * + * @param ns The namespace, w/ periods in it + * @return Namespaces + */ +string t_cpp_generator::namespace_close(string ns) { + if (ns.size() == 0) { + return ""; + } + string result = "}"; + string::size_type loc; + while ((loc = ns.find(".")) != string::npos) { + result += "}"; + ns = ns.substr(loc + 1); + } + result += " // namespace"; + return result; +} + +/** + * Returns a C++ type name + * + * @param ttype The type + * @return String of the type name, i.e. std::set<type> + */ +string t_cpp_generator::type_name(t_type* ttype, bool in_typedef, bool arg) { + if (ttype->is_base_type()) { + string bname = base_type_name(((t_base_type*)ttype)->get_base()); + std::map<string, string>::iterator it = ttype->annotations_.find("cpp.type"); + if (it != ttype->annotations_.end()) { + bname = it->second; + } + + if (!arg) { + return bname; + } + + if (((t_base_type*)ttype)->get_base() == t_base_type::TYPE_STRING) { + return "const " + bname + "&"; + } else { + return "const " + bname; + } + } + + // Check for a custom overloaded C++ name + if (ttype->is_container()) { + string cname; + + t_container* tcontainer = (t_container*)ttype; + if (tcontainer->has_cpp_name()) { + cname = tcontainer->get_cpp_name(); + } else if (ttype->is_map()) { + t_map* tmap = (t_map*)ttype; + cname = "std::map<" + type_name(tmap->get_key_type(), in_typedef) + ", " + + type_name(tmap->get_val_type(), in_typedef) + "> "; + } else if (ttype->is_set()) { + t_set* tset = (t_set*)ttype; + cname = "std::set<" + type_name(tset->get_elem_type(), in_typedef) + "> "; + } else if (ttype->is_list()) { + t_list* tlist = (t_list*)ttype; + cname = "std::vector<" + type_name(tlist->get_elem_type(), in_typedef) + "> "; + } + + if (arg) { + return "const " + cname + "&"; + } else { + return cname; + } + } + + string class_prefix; + if (in_typedef && (ttype->is_struct() || ttype->is_xception())) { + class_prefix = "class "; + } + + // Check if it needs to be namespaced + string pname; + t_program* program = ttype->get_program(); + if (program != NULL && program != program_) { + pname = class_prefix + namespace_prefix(program->get_namespace("cpp")) + ttype->get_name(); + } else { + pname = class_prefix + ttype->get_name(); + } + + if (ttype->is_enum() && !gen_pure_enums_) { + pname += "::type"; + } + + if (arg) { + if (is_complex_type(ttype)) { + return "const " + pname + "&"; + } else { + return "const " + pname; + } + } else { + return pname; + } +} + +/** + * Returns the C++ type that corresponds to the thrift type. + * + * @param tbase The base type + * @return Explicit C++ type, i.e. "int32_t" + */ +string t_cpp_generator::base_type_name(t_base_type::t_base tbase) { + switch (tbase) { + case t_base_type::TYPE_VOID: + return "void"; + case t_base_type::TYPE_STRING: + return "std::string"; + case t_base_type::TYPE_BOOL: + return "bool"; + case t_base_type::TYPE_I8: + return "int8_t"; + case t_base_type::TYPE_I16: + return "int16_t"; + case t_base_type::TYPE_I32: + return "int32_t"; + case t_base_type::TYPE_I64: + return "int64_t"; + case t_base_type::TYPE_DOUBLE: + return "double"; + default: + throw "compiler error: no C++ base type name for base type " + t_base_type::t_base_name(tbase); + } +} + +/** + * Declares a field, which may include initialization as necessary. + * + * @param ttype The type + * @return Field declaration, i.e. int x = 0; + */ +string t_cpp_generator::declare_field(t_field* tfield, + bool init, + bool pointer, + bool constant, + bool reference) { + // TODO(mcslee): do we ever need to initialize the field? + string result = ""; + if (constant) { + result += "const "; + } + result += type_name(tfield->get_type()); + if (is_reference(tfield)) { + result = "::std::shared_ptr<" + result + ">"; + } + if (pointer) { + result += "*"; + } + if (reference) { + result += "&"; + } + result += " " + tfield->get_name(); + if (init) { + t_type* type = get_true_type(tfield->get_type()); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + case t_base_type::TYPE_STRING: + break; + case t_base_type::TYPE_BOOL: + result += " = false"; + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + result += " = 0"; + break; + case t_base_type::TYPE_DOUBLE: + result += " = (double)0"; + break; + default: + throw "compiler error: no C++ initializer for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + result += " = (" + type_name(type) + ")0"; + } + } + if (!reference) { + result += ";"; + } + return result; +} + +/** + * Renders a function signature of the form 'type name(args)' + * + * @param tfunction Function definition + * @return String of rendered function definition + */ +string t_cpp_generator::function_signature(t_function* tfunction, + string style, + string prefix, + bool name_params) { + t_type* ttype = tfunction->get_returntype(); + t_struct* arglist = tfunction->get_arglist(); + bool has_xceptions = !tfunction->get_xceptions()->get_members().empty(); + + if (style == "") { + if (is_complex_type(ttype)) { + return "void " + prefix + tfunction->get_name() + "(" + type_name(ttype) + + (name_params ? "& _return" : "& /* _return */") + + argument_list(arglist, name_params, true) + ")"; + } else { + return type_name(ttype) + " " + prefix + tfunction->get_name() + "(" + + argument_list(arglist, name_params) + ")"; + } + } else if (style.substr(0, 3) == "Cob") { + string cob_type; + string exn_cob; + if (style == "CobCl") { + cob_type = "(" + service_name_ + "CobClient"; + if (gen_templates_) { + cob_type += "T<Protocol_>"; + } + cob_type += "* client)"; + } else if (style == "CobSv") { + cob_type = (ttype->is_void() ? "()" : ("(" + type_name(ttype) + " const& _return)")); + if (has_xceptions) { + exn_cob + = ", ::std::function<void(::apache::thrift::TDelayedException* _throw)> /* exn_cob */"; + } + } else { + throw "UNKNOWN STYLE"; + } + + return "void " + prefix + tfunction->get_name() + "(::std::function<void" + cob_type + "> cob" + + exn_cob + argument_list(arglist, name_params, true) + ")"; + } else { + throw "UNKNOWN STYLE"; + } +} + +/** + * Renders a field list + * + * @param tstruct The struct definition + * @return Comma sepearated list of all field names in that struct + */ +string t_cpp_generator::argument_list(t_struct* tstruct, bool name_params, bool start_comma) { + string result = ""; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = !start_comma; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + result += ", "; + } + result += type_name((*f_iter)->get_type(), false, true) + " " + + (name_params ? (*f_iter)->get_name() : "/* " + (*f_iter)->get_name() + " */"); + } + return result; +} + +/** + * Converts the parse type to a C++ enum string for the given type. + * + * @param type Thrift Type + * @return String of C++ code to definition of that type constant + */ +string t_cpp_generator::type_to_enum(t_type* type) { + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + return "::apache::thrift::protocol::T_STRING"; + case t_base_type::TYPE_BOOL: + return "::apache::thrift::protocol::T_BOOL"; + case t_base_type::TYPE_I8: + return "::apache::thrift::protocol::T_BYTE"; + case t_base_type::TYPE_I16: + return "::apache::thrift::protocol::T_I16"; + case t_base_type::TYPE_I32: + return "::apache::thrift::protocol::T_I32"; + case t_base_type::TYPE_I64: + return "::apache::thrift::protocol::T_I64"; + case t_base_type::TYPE_DOUBLE: + return "::apache::thrift::protocol::T_DOUBLE"; + } + } else if (type->is_enum()) { + return "::apache::thrift::protocol::T_I32"; + } else if (type->is_struct()) { + return "::apache::thrift::protocol::T_STRUCT"; + } else if (type->is_xception()) { + return "::apache::thrift::protocol::T_STRUCT"; + } else if (type->is_map()) { + return "::apache::thrift::protocol::T_MAP"; + } else if (type->is_set()) { + return "::apache::thrift::protocol::T_SET"; + } else if (type->is_list()) { + return "::apache::thrift::protocol::T_LIST"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +string t_cpp_generator::get_include_prefix(const t_program& program) const { + string include_prefix = program.get_include_prefix(); + if (!use_include_prefix_ || (include_prefix.size() > 0 && include_prefix[0] == '/')) { + // if flag is turned off or this is absolute path, return empty prefix + return ""; + } + + string::size_type last_slash = string::npos; + if ((last_slash = include_prefix.rfind("/")) != string::npos) { + return include_prefix.substr(0, last_slash) + + (get_program()->is_out_path_absolute() ? "/" : "/" + out_dir_base_ + "/"); + } + + return ""; +} + +THRIFT_REGISTER_GENERATOR( + cpp, + "C++", + " cob_style: Generate \"Continuation OBject\"-style classes.\n" + " no_client_completion:\n" + " Omit calls to completion__() in CobClient class.\n" + " no_default_operators:\n" + " Omits generation of default operators ==, != and <\n" + " templates: Generate templatized reader/writer methods.\n" + " pure_enums: Generate pure enums instead of wrapper classes.\n" + " include_prefix: Use full include paths in generated files.\n" + " moveable_types: Generate move constructors and assignment operators.\n" + " no_ostream_operators:\n" + " Omit generation of ostream definitions.\n" + " no_skeleton: Omits generation of skeleton.\n") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_csharp_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_csharp_generator.cc new file mode 100644 index 000000000..a6ece8083 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_csharp_generator.cc @@ -0,0 +1,3261 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +#include <cassert> + +#include <string> +#include <fstream> +#include <iostream> +#include <vector> +#include <cctype> + +#include <stdlib.h> +#include <sys/stat.h> +#include <sstream> + +#include "thrift/platform.h" +#include "thrift/generate/t_oop_generator.h" + +using std::map; +using std::ostream; +using std::ostringstream; +using std::string; +using std::stringstream; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes + +struct member_mapping_scope { + void* scope_member; + std::map<std::string, std::string> mapping_table; +}; + +class t_csharp_generator : public t_oop_generator { +public: + t_csharp_generator(t_program* program, + const std::map<std::string, std::string>& parsed_options, + const std::string& option_string) + : t_oop_generator(program) { + (void)option_string; + + std::map<std::string, std::string>::const_iterator iter; + + async_ = false; + nullable_ = false; + hashcode_ = false; + union_ = false; + serialize_ = false; + wcf_ = false; + wcf_namespace_.clear(); + for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + if( iter->first.compare("async") == 0) { + async_ = true; + } else if( iter->first.compare("nullable") == 0) { + nullable_ = true; + } else if( iter->first.compare("hashcode") == 0) { + hashcode_ = true; + } else if( iter->first.compare("union") == 0) { + union_ = true; + } else if( iter->first.compare("serial") == 0) { + serialize_ = true; + wcf_namespace_ = iter->second; // since there can be only one namespace + } else if( iter->first.compare("wcf") == 0) { + wcf_ = true; + wcf_namespace_ = iter->second; + } else { + throw "unknown option csharp:" + iter->first; + } + } + + pwarning(1, "The 'csharp' target is deprecated. Consider using 'netstd' instead.\n"); + + out_dir_base_ = "gen-csharp"; + } + void init_generator() override; + void close_generator() override; + + void generate_consts(std::vector<t_const*> consts) override; + + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_struct(t_struct* tstruct) override; + void generate_union(t_struct* tunion); + void generate_xception(t_struct* txception) override; + void generate_service(t_service* tservice) override; + void generate_property(ostream& out, t_field* tfield, bool isPublic, bool generateIsset); + void generate_csharp_property(ostream& out, + t_field* tfield, + bool isPublic, + bool includeIsset = true, + std::string fieldPrefix = ""); + bool print_const_value(std::ostream& out, + std::string name, + t_type* type, + t_const_value* value, + bool in_static, + bool defval = false, + bool needtype = false); + std::string render_const_value(std::ostream& out, + std::string name, + t_type* type, + t_const_value* value); + void print_const_constructor(std::ostream& out, std::vector<t_const*> consts); + void print_const_def_value(std::ostream& out, + std::string name, + t_type* type, + t_const_value* value); + + void generate_csharp_struct(t_struct* tstruct, bool is_exception); + void generate_csharp_union(t_struct* tunion); + void generate_csharp_struct_definition(std::ostream& out, + t_struct* tstruct, + bool is_xception = false, + bool in_class = false, + bool is_result = false); + void generate_csharp_union_definition(std::ostream& out, t_struct* tunion); + void generate_csharp_union_class(std::ostream& out, t_struct* tunion, t_field* tfield); + void generate_csharp_wcffault(std::ostream& out, t_struct* tstruct); + void generate_csharp_struct_reader(std::ostream& out, t_struct* tstruct); + void generate_csharp_struct_result_writer(std::ostream& out, t_struct* tstruct); + void generate_csharp_struct_writer(std::ostream& out, t_struct* tstruct); + void generate_csharp_struct_tostring(std::ostream& out, t_struct* tstruct); + void generate_csharp_struct_equals(std::ostream& out, t_struct* tstruct); + void generate_csharp_struct_hashcode(std::ostream& out, t_struct* tstruct); + void generate_csharp_union_reader(std::ostream& out, t_struct* tunion); + + void generate_function_helpers(t_function* tfunction); + void generate_service_interface(t_service* tservice); + void generate_separate_service_interfaces(t_service* tservice); + void generate_sync_service_interface(t_service* tservice); + void generate_async_service_interface(t_service* tservice); + void generate_combined_service_interface(t_service* tservice); + void generate_silverlight_async_methods(t_service* tservice); + void generate_service_helpers(t_service* tservice); + void generate_service_client(t_service* tservice); + void generate_service_server(t_service* tservice); + void generate_service_server_sync(t_service* tservice); + void generate_service_server_async(t_service* tservice); + void generate_process_function(t_service* tservice, t_function* function); + void generate_process_function_async(t_service* tservice, t_function* function); + + void generate_deserialize_field(std::ostream& out, + t_field* tfield, + std::string prefix = "", + bool is_propertyless = false); + void generate_deserialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); + void generate_deserialize_container(std::ostream& out, t_type* ttype, std::string prefix = ""); + void generate_deserialize_set_element(std::ostream& out, t_set* tset, std::string prefix = ""); + void generate_deserialize_map_element(std::ostream& out, t_map* tmap, std::string prefix = ""); + void generate_deserialize_list_element(std::ostream& out, t_list* list, std::string prefix = ""); + void generate_serialize_field(std::ostream& out, + t_field* tfield, + std::string prefix = "", + bool is_element = false, + bool is_propertyless = false); + void generate_serialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); + void generate_serialize_container(std::ostream& out, t_type* ttype, std::string prefix = ""); + void generate_serialize_map_element(std::ostream& out, + t_map* tmap, + std::string iter, + std::string map); + void generate_serialize_set_element(std::ostream& out, t_set* tmap, std::string iter); + void generate_serialize_list_element(std::ostream& out, t_list* tlist, std::string iter); + + void generate_csharp_doc(std::ostream& out, t_field* field); + void generate_csharp_doc(std::ostream& out, t_doc* tdoc); + void generate_csharp_doc(std::ostream& out, t_function* tdoc); + void generate_csharp_docstring_comment(std::ostream& out, string contents); + + void start_csharp_namespace(std::ostream& out); + void end_csharp_namespace(std::ostream& out); + + std::string csharp_type_usings(); + std::string csharp_thrift_usings(); + + std::string type_name(t_type* ttype, + bool in_countainer = false, + bool in_init = false, + bool in_param = false, + bool is_required = false); + std::string base_type_name(t_base_type* tbase, + bool in_container = false, + bool in_param = false, + bool is_required = false); + std::string declare_field(t_field* tfield, bool init = false, std::string prefix = ""); + std::string function_signature_async_begin(t_function* tfunction, std::string prefix = ""); + std::string function_signature_async_end(t_function* tfunction, std::string prefix = ""); + std::string function_signature_async(t_function* tfunction, std::string prefix = ""); + std::string function_signature(t_function* tfunction, std::string prefix = ""); + std::string argument_list(t_struct* tstruct); + std::string type_to_enum(t_type* ttype); + std::string prop_name(t_field* tfield, bool suppress_mapping = false); + std::string get_enum_class_name(t_type* type) override; + + bool field_has_default(t_field* tfield) { return tfield->get_value() != NULL; } + + bool field_is_required(t_field* tfield) { return tfield->get_req() == t_field::T_REQUIRED; } + + bool type_can_be_null(t_type* ttype) { + while (ttype->is_typedef()) { + ttype = ((t_typedef*)ttype)->get_type(); + } + + return ttype->is_container() || ttype->is_struct() || ttype->is_xception() + || ttype->is_string(); + } + +private: + std::string namespace_name_; + ofstream_with_content_based_conditional_update f_service_; + std::string namespace_dir_; + bool async_; + bool nullable_; + bool union_; + bool hashcode_; + bool serialize_; + bool wcf_; + std::string wcf_namespace_; + + std::map<std::string, int> csharp_keywords; + std::vector<member_mapping_scope> member_mapping_scopes; + + void init_keywords(); + std::string normalize_name(std::string name); + std::string make_valid_csharp_identifier(std::string const& fromName); + void prepare_member_name_mapping(t_struct* tstruct); + void prepare_member_name_mapping(void* scope, + const vector<t_field*>& members, + const string& structname); + void cleanup_member_name_mapping(void* scope); + string get_mapped_member_name(string oldname); +}; + +void t_csharp_generator::init_generator() { + MKDIR(get_out_dir().c_str()); + namespace_name_ = program_->get_namespace("csharp"); + + string dir = namespace_name_; + string subdir = get_out_dir().c_str(); + string::size_type loc; + + while ((loc = dir.find(".")) != string::npos) { + subdir = subdir + "/" + dir.substr(0, loc); + MKDIR(subdir.c_str()); + dir = dir.substr(loc + 1); + } + if (dir.size() > 0) { + subdir = subdir + "/" + dir; + MKDIR(subdir.c_str()); + } + + namespace_dir_ = subdir; + init_keywords(); + + while( ! member_mapping_scopes.empty()) { + cleanup_member_name_mapping( member_mapping_scopes.back().scope_member); + } + + pverbose("C# options:\n"); + pverbose("- async ...... %s\n", (async_ ? "ON" : "off")); + pverbose("- nullable ... %s\n", (nullable_ ? "ON" : "off")); + pverbose("- union ...... %s\n", (union_ ? "ON" : "off")); + pverbose("- hashcode ... %s\n", (hashcode_ ? "ON" : "off")); + pverbose("- serialize .. %s\n", (serialize_ ? "ON" : "off")); + pverbose("- wcf ........ %s\n", (wcf_ ? "ON" : "off")); +} + +std::string t_csharp_generator::normalize_name(std::string name) { + string tmp(name); + std::transform(tmp.begin(), tmp.end(), tmp.begin(), static_cast<int (*)(int)>(std::tolower)); + + // un-conflict keywords by prefixing with "@" + if (csharp_keywords.find(tmp) != csharp_keywords.end()) { + return "@" + name; + } + + // no changes necessary + return name; +} + +void t_csharp_generator::init_keywords() { + csharp_keywords.clear(); + + // C# keywords + csharp_keywords["abstract"] = 1; + csharp_keywords["as"] = 1; + csharp_keywords["base"] = 1; + csharp_keywords["bool"] = 1; + csharp_keywords["break"] = 1; + csharp_keywords["byte"] = 1; + csharp_keywords["case"] = 1; + csharp_keywords["catch"] = 1; + csharp_keywords["char"] = 1; + csharp_keywords["checked"] = 1; + csharp_keywords["class"] = 1; + csharp_keywords["const"] = 1; + csharp_keywords["continue"] = 1; + csharp_keywords["decimal"] = 1; + csharp_keywords["default"] = 1; + csharp_keywords["delegate"] = 1; + csharp_keywords["do"] = 1; + csharp_keywords["double"] = 1; + csharp_keywords["else"] = 1; + csharp_keywords["enum"] = 1; + csharp_keywords["event"] = 1; + csharp_keywords["explicit"] = 1; + csharp_keywords["extern"] = 1; + csharp_keywords["false"] = 1; + csharp_keywords["finally"] = 1; + csharp_keywords["fixed"] = 1; + csharp_keywords["float"] = 1; + csharp_keywords["for"] = 1; + csharp_keywords["foreach"] = 1; + csharp_keywords["goto"] = 1; + csharp_keywords["if"] = 1; + csharp_keywords["implicit"] = 1; + csharp_keywords["in"] = 1; + csharp_keywords["int"] = 1; + csharp_keywords["interface"] = 1; + csharp_keywords["internal"] = 1; + csharp_keywords["is"] = 1; + csharp_keywords["lock"] = 1; + csharp_keywords["long"] = 1; + csharp_keywords["namespace"] = 1; + csharp_keywords["new"] = 1; + csharp_keywords["null"] = 1; + csharp_keywords["object"] = 1; + csharp_keywords["operator"] = 1; + csharp_keywords["out"] = 1; + csharp_keywords["override"] = 1; + csharp_keywords["params"] = 1; + csharp_keywords["private"] = 1; + csharp_keywords["protected"] = 1; + csharp_keywords["public"] = 1; + csharp_keywords["readonly"] = 1; + csharp_keywords["ref"] = 1; + csharp_keywords["return"] = 1; + csharp_keywords["sbyte"] = 1; + csharp_keywords["sealed"] = 1; + csharp_keywords["short"] = 1; + csharp_keywords["sizeof"] = 1; + csharp_keywords["stackalloc"] = 1; + csharp_keywords["static"] = 1; + csharp_keywords["string"] = 1; + csharp_keywords["struct"] = 1; + csharp_keywords["switch"] = 1; + csharp_keywords["this"] = 1; + csharp_keywords["throw"] = 1; + csharp_keywords["true"] = 1; + csharp_keywords["try"] = 1; + csharp_keywords["typeof"] = 1; + csharp_keywords["uint"] = 1; + csharp_keywords["ulong"] = 1; + csharp_keywords["unchecked"] = 1; + csharp_keywords["unsafe"] = 1; + csharp_keywords["ushort"] = 1; + csharp_keywords["using"] = 1; + csharp_keywords["virtual"] = 1; + csharp_keywords["void"] = 1; + csharp_keywords["volatile"] = 1; + csharp_keywords["while"] = 1; + + // C# contextual keywords + csharp_keywords["add"] = 1; + csharp_keywords["alias"] = 1; + csharp_keywords["ascending"] = 1; + csharp_keywords["async"] = 1; + csharp_keywords["await"] = 1; + csharp_keywords["descending"] = 1; + csharp_keywords["dynamic"] = 1; + csharp_keywords["from"] = 1; + csharp_keywords["get"] = 1; + csharp_keywords["global"] = 1; + csharp_keywords["group"] = 1; + csharp_keywords["into"] = 1; + csharp_keywords["join"] = 1; + csharp_keywords["let"] = 1; + csharp_keywords["orderby"] = 1; + csharp_keywords["partial"] = 1; + csharp_keywords["remove"] = 1; + csharp_keywords["select"] = 1; + csharp_keywords["set"] = 1; + csharp_keywords["value"] = 1; + csharp_keywords["var"] = 1; + csharp_keywords["where"] = 1; + csharp_keywords["yield"] = 1; +} + +void t_csharp_generator::start_csharp_namespace(ostream& out) { + if (!namespace_name_.empty()) { + out << "namespace " << namespace_name_ << "\n"; + scope_up(out); + } +} + +void t_csharp_generator::end_csharp_namespace(ostream& out) { + if (!namespace_name_.empty()) { + scope_down(out); + } +} + +string t_csharp_generator::csharp_type_usings() { + return string() + "using System;\n" + "using System.Collections;\n" + + "using System.Collections.Generic;\n" + "using System.Text;\n" + "using System.IO;\n" + + ((async_) ? "using System.Threading.Tasks;\n" : "") + "using Thrift;\n" + + "using Thrift.Collections;\n" + ((serialize_ || wcf_) ? "#if !SILVERLIGHT\n" : "") + + ((serialize_ || wcf_) ? "using System.Xml.Serialization;\n" : "") + + ((serialize_ || wcf_) ? "#endif\n" : "") + + "using System.Runtime.Serialization;\n"; +} + +string t_csharp_generator::csharp_thrift_usings() { + return string() + "using Thrift.Protocol;\n" + "using Thrift.Transport;\n"; +} + +void t_csharp_generator::close_generator() { +} +void t_csharp_generator::generate_typedef(t_typedef* ttypedef) { + (void)ttypedef; +} + +void t_csharp_generator::generate_enum(t_enum* tenum) { + string f_enum_name = namespace_dir_ + "/" + (tenum->get_name()) + ".cs"; + ofstream_with_content_based_conditional_update f_enum; + f_enum.open(f_enum_name.c_str()); + + f_enum << autogen_comment() << endl; + + start_csharp_namespace(f_enum); + + generate_csharp_doc(f_enum, tenum); + + indent(f_enum) << "public enum " << tenum->get_name() << "\n"; + scope_up(f_enum); + + vector<t_enum_value*> constants = tenum->get_constants(); + vector<t_enum_value*>::iterator c_iter; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + generate_csharp_doc(f_enum, *c_iter); + + int value = (*c_iter)->get_value(); + indent(f_enum) << (*c_iter)->get_name() << " = " << value << "," << endl; + } + + scope_down(f_enum); + + end_csharp_namespace(f_enum); + + f_enum.close(); +} + +void t_csharp_generator::generate_consts(std::vector<t_const*> consts) { + if (consts.empty()) { + return; + } + string f_consts_name = namespace_dir_ + '/' + program_name_ + ".Constants.cs"; + ofstream_with_content_based_conditional_update f_consts; + f_consts.open(f_consts_name.c_str()); + + f_consts << autogen_comment() << csharp_type_usings() << endl; + + start_csharp_namespace(f_consts); + + indent(f_consts) << "public static class " << make_valid_csharp_identifier(program_name_) + << "Constants" << endl; + scope_up(f_consts); + + vector<t_const*>::iterator c_iter; + bool need_static_constructor = false; + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + generate_csharp_doc(f_consts, (*c_iter)); + if (print_const_value(f_consts, + (*c_iter)->get_name(), + (*c_iter)->get_type(), + (*c_iter)->get_value(), + false)) { + need_static_constructor = true; + } + } + + if (need_static_constructor) { + print_const_constructor(f_consts, consts); + } + + scope_down(f_consts); + end_csharp_namespace(f_consts); + f_consts.close(); +} + +void t_csharp_generator::print_const_def_value(std::ostream& out, + string name, + t_type* type, + t_const_value* value) { + if (type->is_struct() || type->is_xception()) { + const vector<t_field*>& fields = ((t_struct*)type)->get_members(); + vector<t_field*>::const_iterator f_iter; + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + prepare_member_name_mapping((t_struct*)type); + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + t_field* field = NULL; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field = (*f_iter); + } + } + if (field == NULL) { + throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string(); + } + t_type* field_type = field->get_type(); + string val = render_const_value(out, name, field_type, v_iter->second); + indent(out) << name << "." << prop_name(field) << " = " << val << ";" << endl; + } + cleanup_member_name_mapping((t_struct*)type); + } else if (type->is_map()) { + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string key = render_const_value(out, name, ktype, v_iter->first); + string val = render_const_value(out, name, vtype, v_iter->second); + indent(out) << name << "[" << key << "]" + << " = " << val << ";" << endl; + } + } else if (type->is_list() || type->is_set()) { + t_type* etype; + if (type->is_list()) { + etype = ((t_list*)type)->get_elem_type(); + } else { + etype = ((t_set*)type)->get_elem_type(); + } + + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string val = render_const_value(out, name, etype, *v_iter); + indent(out) << name << ".Add(" << val << ");" << endl; + } + } +} + +void t_csharp_generator::print_const_constructor(std::ostream& out, std::vector<t_const*> consts) { + indent(out) << "static " << make_valid_csharp_identifier(program_name_).c_str() << "Constants()" + << endl; + scope_up(out); + vector<t_const*>::iterator c_iter; + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + string name = (*c_iter)->get_name(); + t_type* type = (*c_iter)->get_type(); + t_const_value* value = (*c_iter)->get_value(); + + print_const_def_value(out, name, type, value); + } + scope_down(out); +} + +// it seems like all that methods that call this are using in_static to be the opposite of what it +// would imply +bool t_csharp_generator::print_const_value(std::ostream& out, + string name, + t_type* type, + t_const_value* value, + bool in_static, + bool defval, + bool needtype) { + indent(out); + bool need_static_construction = !in_static; + while (type->is_typedef()) { + type = ((t_typedef*)type)->get_type(); + } + + if (!defval || needtype) { + out << (in_static ? "" : type->is_base_type() ? "public const " : "public static ") + << type_name(type) << " "; + } + if (type->is_base_type()) { + string v2 = render_const_value(out, name, type, value); + out << name << " = " << v2 << ";" << endl; + need_static_construction = false; + } else if (type->is_enum()) { + out << name << " = " << type_name(type, false, true) << "." << value->get_identifier_name() + << ";" << endl; + need_static_construction = false; + } else if (type->is_struct() || type->is_xception()) { + out << name << " = new " << type_name(type) << "();" << endl; + } else if (type->is_map()) { + out << name << " = new " << type_name(type, true, true) << "();" << endl; + } else if (type->is_list() || type->is_set()) { + out << name << " = new " << type_name(type) << "();" << endl; + } + + if (defval && !type->is_base_type() && !type->is_enum()) { + print_const_def_value(out, name, type, value); + } + + return need_static_construction; +} + +std::string t_csharp_generator::render_const_value(ostream& out, + string name, + t_type* type, + t_const_value* value) { + (void)name; + std::ostringstream render; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + render << '"' << get_escaped_string(value) << '"'; + break; + case t_base_type::TYPE_BOOL: + render << ((value->get_integer() > 0) ? "true" : "false"); + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + render << value->get_integer(); + break; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) { + render << value->get_integer(); + } else { + render << value->get_double(); + } + break; + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + render << type->get_name() << "." << value->get_identifier_name(); + } else { + string t = tmp("tmp"); + print_const_value(out, t, type, value, true, true, true); + render << t; + } + + return render.str(); +} + +void t_csharp_generator::generate_struct(t_struct* tstruct) { + if (union_ && tstruct->is_union()) { + generate_csharp_union(tstruct); + } else { + generate_csharp_struct(tstruct, false); + } +} + +void t_csharp_generator::generate_xception(t_struct* txception) { + generate_csharp_struct(txception, true); +} + +void t_csharp_generator::generate_csharp_struct(t_struct* tstruct, bool is_exception) { + string f_struct_name = namespace_dir_ + "/" + (tstruct->get_name()) + ".cs"; + ofstream_with_content_based_conditional_update f_struct; + + f_struct.open(f_struct_name.c_str()); + + f_struct << autogen_comment() << csharp_type_usings() << csharp_thrift_usings() << endl; + + generate_csharp_struct_definition(f_struct, tstruct, is_exception); + + f_struct.close(); +} + +void t_csharp_generator::generate_csharp_struct_definition(ostream& out, + t_struct* tstruct, + bool is_exception, + bool in_class, + bool is_result) { + + if (!in_class) { + start_csharp_namespace(out); + } + + out << endl; + + generate_csharp_doc(out, tstruct); + prepare_member_name_mapping(tstruct); + + indent(out) << "#if !SILVERLIGHT" << endl; + indent(out) << "[Serializable]" << endl; + indent(out) << "#endif" << endl; + if ((serialize_ || wcf_) && !is_exception) { + indent(out) << "[DataContract(Namespace=\"" << wcf_namespace_ << "\")]" + << endl; // do not make exception classes directly WCF serializable, we provide a + // separate "fault" for that + } + bool is_final = (tstruct->annotations_.find("final") != tstruct->annotations_.end()); + + indent(out) << "public " << (is_final ? "sealed " : "") << "partial class " + << normalize_name(tstruct->get_name()) << " : "; + + if (is_exception) { + out << "TException, "; + } + out << "TBase"; + + out << endl; + + scope_up(out); + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + // make private members with public Properties + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + // if the field is requied, then we use auto-properties + if (!field_is_required((*m_iter)) && (!nullable_ || field_has_default((*m_iter)))) { + indent(out) << "private " << declare_field(*m_iter, false, "_") << endl; + } + } + out << endl; + + bool has_non_required_fields = false; + bool has_non_required_default_value_fields = false; + bool has_required_fields = false; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + generate_csharp_doc(out, *m_iter); + generate_property(out, *m_iter, true, true); + bool is_required = field_is_required((*m_iter)); + bool has_default = field_has_default((*m_iter)); + if (is_required) { + has_required_fields = true; + } else { + if (has_default) { + has_non_required_default_value_fields = true; + } + has_non_required_fields = true; + } + } + + bool generate_isset = (nullable_ && has_non_required_default_value_fields) + || (!nullable_ && has_non_required_fields); + if (generate_isset) { + out << endl; + if (serialize_ || wcf_) { + out << indent() << "[XmlIgnore] // XmlSerializer" << endl << indent() + << "[DataMember(Order = 1)] // XmlObjectSerializer, DataContractJsonSerializer, etc." + << endl; + } + out << indent() << "public Isset __isset;" << endl << indent() << "#if !SILVERLIGHT" << endl + << indent() << "[Serializable]" << endl << indent() << "#endif" << endl; + if (serialize_ || wcf_) { + indent(out) << "[DataContract]" << endl; + } + indent(out) << "public struct Isset {" << endl; + indent_up(); + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + bool is_required = field_is_required((*m_iter)); + bool has_default = field_has_default((*m_iter)); + // if it is required, don't need Isset for that variable + // if it is not required, if it has a default value, we need to generate Isset + // if we are not nullable, then we generate Isset + if (!is_required && (!nullable_ || has_default)) { + if (serialize_ || wcf_) { + indent(out) << "[DataMember]" << endl; + } + indent(out) << "public bool " << normalize_name((*m_iter)->get_name()) << ";" << endl; + } + } + + indent_down(); + indent(out) << "}" << endl << endl; + + if (generate_isset && (serialize_ || wcf_)) { + indent(out) << "#region XmlSerializer support" << endl << endl; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + bool is_required = field_is_required((*m_iter)); + bool has_default = field_has_default((*m_iter)); + // if it is required, don't need Isset for that variable + // if it is not required, if it has a default value, we need to generate Isset + // if we are not nullable, then we generate Isset + if (!is_required && (!nullable_ || has_default)) { + indent(out) << "public bool ShouldSerialize" << prop_name((*m_iter)) << "()" << endl; + indent(out) << "{" << endl; + indent_up(); + indent(out) << "return __isset." << normalize_name((*m_iter)->get_name()) << ";" << endl; + indent_down(); + indent(out) << "}" << endl << endl; + } + } + + indent(out) << "#endregion XmlSerializer support" << endl << endl; + } + } + + // We always want a default, no argument constructor for Reading + indent(out) << "public " << normalize_name(tstruct->get_name()) << "() {" << endl; + indent_up(); + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* t = (*m_iter)->get_type(); + while (t->is_typedef()) { + t = ((t_typedef*)t)->get_type(); + } + if ((*m_iter)->get_value() != NULL) { + if (field_is_required((*m_iter))) { + print_const_value(out, "this." + prop_name(*m_iter), t, (*m_iter)->get_value(), true, true); + } else { + print_const_value(out, + "this._" + (*m_iter)->get_name(), + t, + (*m_iter)->get_value(), + true, + true); + // Optionals with defaults are marked set + indent(out) << "this.__isset." << normalize_name((*m_iter)->get_name()) << " = true;" + << endl; + } + } + } + indent_down(); + indent(out) << "}" << endl << endl; + + if (has_required_fields) { + indent(out) << "public " << tstruct->get_name() << "("; + bool first = true; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if (field_is_required((*m_iter))) { + if (first) { + first = false; + } else { + out << ", "; + } + out << type_name((*m_iter)->get_type()) << " " << normalize_name((*m_iter)->get_name()); + } + } + out << ") : this() {" << endl; + indent_up(); + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if (field_is_required((*m_iter))) { + indent(out) << "this." << prop_name((*m_iter)) << " = " << normalize_name((*m_iter)->get_name()) << ";" + << endl; + } + } + + indent_down(); + indent(out) << "}" << endl << endl; + } + + generate_csharp_struct_reader(out, tstruct); + if (is_result) { + generate_csharp_struct_result_writer(out, tstruct); + } else { + generate_csharp_struct_writer(out, tstruct); + } + if (hashcode_) { + generate_csharp_struct_equals(out, tstruct); + generate_csharp_struct_hashcode(out, tstruct); + } + generate_csharp_struct_tostring(out, tstruct); + scope_down(out); + out << endl; + + // generate a corresponding WCF fault to wrap the exception + if ((serialize_ || wcf_) && is_exception) { + generate_csharp_wcffault(out, tstruct); + } + + cleanup_member_name_mapping(tstruct); + if (!in_class) { + end_csharp_namespace(out); + } +} + +void t_csharp_generator::generate_csharp_wcffault(ostream& out, t_struct* tstruct) { + out << endl; + indent(out) << "#if !SILVERLIGHT" << endl; + indent(out) << "[Serializable]" << endl; + indent(out) << "#endif" << endl; + indent(out) << "[DataContract]" << endl; + bool is_final = (tstruct->annotations_.find("final") != tstruct->annotations_.end()); + + indent(out) << "public " << (is_final ? "sealed " : "") << "partial class " << tstruct->get_name() + << "Fault" << endl; + + scope_up(out); + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + // make private members with public Properties + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + // if the field is requied, then we use auto-properties + if (!field_is_required((*m_iter)) && (!nullable_ || field_has_default((*m_iter)))) { + indent(out) << "private " << declare_field(*m_iter, false, "_") << endl; + } + } + out << endl; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + generate_property(out, *m_iter, true, false); + } + + scope_down(out); + out << endl; +} + +void t_csharp_generator::generate_csharp_struct_reader(ostream& out, t_struct* tstruct) { + indent(out) << "public void Read (TProtocol iprot)" << endl; + scope_up(out); + + out << indent() << "iprot.IncrementRecursionDepth();" << endl; + out << indent() << "try" << endl; + scope_up(out); + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + // Required variables aren't in __isset, so we need tmp vars to check them + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (field_is_required((*f_iter))) { + indent(out) << "bool isset_" << (*f_iter)->get_name() << " = false;" << endl; + } + } + + indent(out) << "TField field;" << endl << indent() << "iprot.ReadStructBegin();" << endl; + + indent(out) << "while (true)" << endl; + scope_up(out); + + indent(out) << "field = iprot.ReadFieldBegin();" << endl; + + indent(out) << "if (field.Type == TType.Stop) { " << endl; + indent_up(); + indent(out) << "break;" << endl; + indent_down(); + indent(out) << "}" << endl; + + indent(out) << "switch (field.ID)" << endl; + + scope_up(out); + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + bool is_required = field_is_required((*f_iter)); + indent(out) << "case " << (*f_iter)->get_key() << ":" << endl; + indent_up(); + indent(out) << "if (field.Type == " << type_to_enum((*f_iter)->get_type()) << ") {" << endl; + indent_up(); + + generate_deserialize_field(out, *f_iter); + if (is_required) { + indent(out) << "isset_" << (*f_iter)->get_name() << " = true;" << endl; + } + + indent_down(); + out << indent() << "} else { " << endl << indent() << " TProtocolUtil.Skip(iprot, field.Type);" + << endl << indent() << "}" << endl << indent() << "break;" << endl; + indent_down(); + } + + indent(out) << "default: " << endl; + indent_up(); + indent(out) << "TProtocolUtil.Skip(iprot, field.Type);" << endl; + indent(out) << "break;" << endl; + indent_down(); + + scope_down(out); + + indent(out) << "iprot.ReadFieldEnd();" << endl; + + scope_down(out); + + indent(out) << "iprot.ReadStructEnd();" << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (field_is_required((*f_iter))) { + indent(out) << "if (!isset_" << (*f_iter)->get_name() << ")" << endl; + indent_up(); + out << indent() + << "throw new TProtocolException(TProtocolException.INVALID_DATA, " + << "\"required field " << prop_name((*f_iter)) << " not set\");" + << endl; + indent_down(); + } + } + + scope_down(out); + out << indent() << "finally" << endl; + scope_up(out); + out << indent() << "iprot.DecrementRecursionDepth();" << endl; + scope_down(out); + + indent_down(); + + indent(out) << "}" << endl << endl; +} + +void t_csharp_generator::generate_csharp_struct_writer(ostream& out, t_struct* tstruct) { + out << indent() << "public void Write(TProtocol oprot) {" << endl; + indent_up(); + + out << indent() << "oprot.IncrementRecursionDepth();" << endl; + out << indent() << "try" << endl; + scope_up(out); + + string name = tstruct->get_name(); + const vector<t_field*>& fields = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator f_iter; + + indent(out) << "TStruct struc = new TStruct(\"" << name << "\");" << endl; + indent(out) << "oprot.WriteStructBegin(struc);" << endl; + + if (fields.size() > 0) { + indent(out) << "TField field = new TField();" << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + bool is_required = field_is_required((*f_iter)); + bool has_default = field_has_default((*f_iter)); + bool null_allowed = type_can_be_null((*f_iter)->get_type()); + + if (is_required) + { + if (null_allowed) { + indent(out) << "if (" << prop_name((*f_iter)) << " == null)" << endl; + indent_up(); + out << indent() + << "throw new TProtocolException(TProtocolException.INVALID_DATA, " + << "\"required field " << prop_name((*f_iter)) << " not set\");" + << endl; + indent_down(); + } + } + else + { + if (nullable_ && !has_default) { + indent(out) << "if (" << prop_name((*f_iter)) << " != null) {" << endl; + } + else if (null_allowed) { + out << indent() + << "if (" << prop_name((*f_iter)) << " != null && __isset." + << normalize_name((*f_iter)->get_name()) << ") {" + << endl; + } + else { + indent(out) << "if (__isset." << normalize_name((*f_iter)->get_name()) << ") {" << endl; + } + indent_up(); + } + indent(out) << "field.Name = \"" << (*f_iter)->get_name() << "\";" << endl; + indent(out) << "field.Type = " << type_to_enum((*f_iter)->get_type()) << ";" << endl; + indent(out) << "field.ID = " << (*f_iter)->get_key() << ";" << endl; + indent(out) << "oprot.WriteFieldBegin(field);" << endl; + + generate_serialize_field(out, *f_iter); + + indent(out) << "oprot.WriteFieldEnd();" << endl; + if (!is_required) { + indent_down(); + indent(out) << "}" << endl; + } + } + } + + indent(out) << "oprot.WriteFieldStop();" << endl; + indent(out) << "oprot.WriteStructEnd();" << endl; + + scope_down(out); + out << indent() << "finally" << endl; + scope_up(out); + out << indent() << "oprot.DecrementRecursionDepth();" << endl; + scope_down(out); + + indent_down(); + + indent(out) << "}" << endl << endl; +} + +void t_csharp_generator::generate_csharp_struct_result_writer(ostream& out, t_struct* tstruct) { + indent(out) << "public void Write(TProtocol oprot) {" << endl; + indent_up(); + + out << indent() << "oprot.IncrementRecursionDepth();" << endl; + out << indent() << "try" << endl; + scope_up(out); + + string name = tstruct->get_name(); + const vector<t_field*>& fields = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator f_iter; + + indent(out) << "TStruct struc = new TStruct(\"" << name << "\");" << endl; + indent(out) << "oprot.WriteStructBegin(struc);" << endl; + + if (fields.size() > 0) { + indent(out) << "TField field = new TField();" << endl; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + out << endl << indent() << "if "; + } else { + out << " else if "; + } + + if (nullable_) { + out << "(this." << prop_name((*f_iter)) << " != null) {" << endl; + } else { + out << "(this.__isset." << normalize_name((*f_iter)->get_name()) << ") {" << endl; + } + indent_up(); + + bool null_allowed = !nullable_ && type_can_be_null((*f_iter)->get_type()); + if (null_allowed) { + indent(out) << "if (" << prop_name(*f_iter) << " != null) {" << endl; + indent_up(); + } + + indent(out) << "field.Name = \"" << prop_name(*f_iter) << "\";" << endl; + indent(out) << "field.Type = " << type_to_enum((*f_iter)->get_type()) << ";" << endl; + indent(out) << "field.ID = " << (*f_iter)->get_key() << ";" << endl; + indent(out) << "oprot.WriteFieldBegin(field);" << endl; + + generate_serialize_field(out, *f_iter); + + indent(out) << "oprot.WriteFieldEnd();" << endl; + + if (null_allowed) { + indent_down(); + indent(out) << "}" << endl; + } + + indent_down(); + indent(out) << "}"; + } + } + + out << endl << indent() << "oprot.WriteFieldStop();" << endl << indent() + << "oprot.WriteStructEnd();" << endl; + + scope_down(out); + out << indent() << "finally" << endl; + scope_up(out); + out << indent() << "oprot.DecrementRecursionDepth();" << endl; + scope_down(out); + + indent_down(); + + indent(out) << "}" << endl << endl; +} + +void t_csharp_generator::generate_csharp_struct_tostring(ostream& out, t_struct* tstruct) { + indent(out) << "public override string ToString() {" << endl; + indent_up(); + + indent(out) << "StringBuilder __sb = new StringBuilder(\"" << tstruct->get_name() << "(\");" + << endl; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + bool useFirstFlag = false; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (!field_is_required((*f_iter))) { + indent(out) << "bool __first = true;" << endl; + useFirstFlag = true; + } + break; + } + + bool had_required = false; // set to true after first required field has been processed + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + bool is_required = field_is_required((*f_iter)); + bool has_default = field_has_default((*f_iter)); + if (nullable_ && !has_default && !is_required) { + indent(out) << "if (" << prop_name((*f_iter)) << " != null) {" << endl; + indent_up(); + } else if (!is_required) { + bool null_allowed = type_can_be_null((*f_iter)->get_type()); + if (null_allowed) { + indent(out) << "if (" << prop_name((*f_iter)) << " != null && __isset." + << normalize_name((*f_iter)->get_name()) << ") {" << endl; + indent_up(); + } else { + indent(out) << "if (__isset." << normalize_name((*f_iter)->get_name()) << ") {" << endl; + indent_up(); + } + } + + if (useFirstFlag && (!had_required)) { + indent(out) << "if(!__first) { __sb.Append(\", \"); }" << endl; + if (!is_required) { + indent(out) << "__first = false;" << endl; + } + indent(out) << "__sb.Append(\"" << prop_name((*f_iter)) << ": \");" << endl; + } else { + indent(out) << "__sb.Append(\", " << prop_name((*f_iter)) << ": \");" << endl; + } + + t_type* ttype = (*f_iter)->get_type(); + if (ttype->is_xception() || ttype->is_struct()) { + indent(out) << "__sb.Append(" << prop_name((*f_iter)) + << "== null ? \"<null>\" : " << prop_name((*f_iter)) << ".ToString());" << endl; + } else { + indent(out) << "__sb.Append(" << prop_name((*f_iter)) << ");" << endl; + } + + if (!is_required) { + indent_down(); + indent(out) << "}" << endl; + } else { + had_required = true; // now __first must be false, so we don't need to check it anymore + } + } + + indent(out) << "__sb.Append(\")\");" << endl; + indent(out) << "return __sb.ToString();" << endl; + + indent_down(); + indent(out) << "}" << endl << endl; +} + +void t_csharp_generator::generate_csharp_union(t_struct* tunion) { + string f_union_name = namespace_dir_ + "/" + (tunion->get_name()) + ".cs"; + ofstream_with_content_based_conditional_update f_union; + + f_union.open(f_union_name.c_str()); + + f_union << autogen_comment() << csharp_type_usings() << csharp_thrift_usings() << endl; + + generate_csharp_union_definition(f_union, tunion); + + f_union.close(); +} + +void t_csharp_generator::generate_csharp_union_definition(std::ostream& out, t_struct* tunion) { + // Let's define the class first + start_csharp_namespace(out); + + indent(out) << "public abstract partial class " << tunion->get_name() << " : TAbstractBase {" + << endl; + + indent_up(); + + indent(out) << "public abstract void Write(TProtocol protocol);" << endl; + indent(out) << "public readonly int Isset;" << endl; + indent(out) << "public abstract object Data { get; }" << endl; + + indent(out) << "protected " << tunion->get_name() << "(int isset) {" << endl; + indent_up(); + indent(out) << "Isset = isset;" << endl; + indent_down(); + indent(out) << "}" << endl << endl; + + indent(out) << "public class ___undefined : " << tunion->get_name() << " {" << endl; + indent_up(); + + indent(out) << "public override object Data { get { return null; } }" << endl; + + indent(out) << "public ___undefined() : base(0) {}" << endl << endl; + + indent(out) << "public override void Write(TProtocol protocol) {" << endl; + indent_up(); + indent(out) << "throw new TProtocolException( TProtocolException.INVALID_DATA, \"Cannot persist " + "an union type which is not set.\");" << endl; + indent_down(); + indent(out) << "}" << endl << endl; + + indent_down(); + indent(out) << "}" << endl << endl; + + const vector<t_field*>& fields = tunion->get_members(); + vector<t_field*>::const_iterator f_iter; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + generate_csharp_union_class(out, tunion, (*f_iter)); + } + + generate_csharp_union_reader(out, tunion); + + indent_down(); + indent(out) << "}" << endl << endl; + + end_csharp_namespace(out); +} + +void t_csharp_generator::generate_csharp_union_class(std::ostream& out, + t_struct* tunion, + t_field* tfield) { + indent(out) << "public " << type_name(tfield->get_type()) << " As_" << tfield->get_name() << endl; + indent(out) << "{" << endl; + indent_up(); + indent(out) << "get" << endl; + indent(out) << "{" << endl; + indent_up(); + indent(out) << "return (" << tfield->get_key() << " == Isset) ? (" << type_name(tfield->get_type()) << ")Data : default(" << type_name(tfield->get_type()) << ");" << endl; + indent_down(); + indent(out) << "}" << endl; + indent_down(); + indent(out) << "}" << endl + << endl; + + + indent(out) << "public class " << tfield->get_name() << " : " << tunion->get_name() << " {" + << endl; + indent_up(); + indent(out) << "private " << type_name(tfield->get_type()) << " _data;" << endl; + indent(out) << "public override object Data { get { return _data; } }" << endl; + indent(out) << "public " << tfield->get_name() << "(" << type_name(tfield->get_type()) + << " data) : base("<< tfield->get_key() <<") {" << endl; + indent_up(); + indent(out) << "this._data = data;" << endl; + indent_down(); + indent(out) << "}" << endl; + indent(out) << "public override void Write(TProtocol oprot) {" << endl; + indent_up(); + + out << indent() << "oprot.IncrementRecursionDepth();" << endl; + out << indent() << "try" << endl; + scope_up(out); + + indent(out) << "TStruct struc = new TStruct(\"" << tunion->get_name() << "\");" << endl; + indent(out) << "oprot.WriteStructBegin(struc);" << endl; + + indent(out) << "TField field = new TField();" << endl; + indent(out) << "field.Name = \"" << tfield->get_name() << "\";" << endl; + indent(out) << "field.Type = " << type_to_enum(tfield->get_type()) << ";" << endl; + indent(out) << "field.ID = " << tfield->get_key() << ";" << endl; + indent(out) << "oprot.WriteFieldBegin(field);" << endl; + + generate_serialize_field(out, tfield, "_data", true, true); + + indent(out) << "oprot.WriteFieldEnd();" << endl; + indent(out) << "oprot.WriteFieldStop();" << endl; + indent(out) << "oprot.WriteStructEnd();" << endl; + indent_down(); + + scope_down(out); + out << indent() << "finally" << endl; + scope_up(out); + out << indent() << "oprot.DecrementRecursionDepth();" << endl; + scope_down(out); + + indent(out) << "}" << endl; + + indent_down(); + indent(out) << "}" << endl << endl; +} + +void t_csharp_generator::generate_csharp_struct_equals(ostream& out, t_struct* tstruct) { + indent(out) << "public override bool Equals(object that) {" << endl; + indent_up(); + + indent(out) << "var other = that as " << type_name(tstruct) << ";" << endl; + indent(out) << "if (other == null) return false;" << endl; + indent(out) << "if (ReferenceEquals(this, other)) return true;" << endl; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + bool first = true; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + indent(out) << "return "; + indent_up(); + } else { + out << endl; + indent(out) << "&& "; + } + if (!field_is_required((*f_iter)) && !(nullable_ && !field_has_default((*f_iter)))) { + out << "((__isset." << normalize_name((*f_iter)->get_name()) << " == other.__isset." + << normalize_name((*f_iter)->get_name()) << ") && ((!__isset." + << normalize_name((*f_iter)->get_name()) << ") || ("; + } + t_type* ttype = (*f_iter)->get_type(); + if (ttype->is_container() || ttype->is_binary()) { + out << "TCollections.Equals("; + } else { + out << "System.Object.Equals("; + } + out << prop_name((*f_iter)) << ", other." << prop_name((*f_iter)) << ")"; + if (!field_is_required((*f_iter)) && !(nullable_ && !field_has_default((*f_iter)))) { + out << ")))"; + } + } + if (first) { + indent(out) << "return true;" << endl; + } else { + out << ";" << endl; + indent_down(); + } + + indent_down(); + indent(out) << "}" << endl << endl; +} + +void t_csharp_generator::generate_csharp_struct_hashcode(ostream& out, t_struct* tstruct) { + indent(out) << "public override int GetHashCode() {" << endl; + indent_up(); + + indent(out) << "int hashcode = 0;" << endl; + indent(out) << "unchecked {" << endl; + indent_up(); + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_type* ttype = (*f_iter)->get_type(); + indent(out) << "hashcode = (hashcode * 397) ^ "; + if (field_is_required((*f_iter))) { + out << "("; + } else if (nullable_) { + out << "(" << prop_name((*f_iter)) << " == null ? 0 : "; + } else { + out << "(!__isset." << normalize_name((*f_iter)->get_name()) << " ? 0 : "; + } + if (ttype->is_container()) { + out << "(TCollections.GetHashCode(" << prop_name((*f_iter)) << "))"; + } else { + out << "(" << prop_name((*f_iter)) << ".GetHashCode())"; + } + out << ");" << endl; + } + + indent_down(); + indent(out) << "}" << endl; + indent(out) << "return hashcode;" << endl; + + indent_down(); + indent(out) << "}" << endl << endl; +} + +void t_csharp_generator::generate_service(t_service* tservice) { + string f_service_name = namespace_dir_ + "/" + service_name_ + ".cs"; + f_service_.open(f_service_name.c_str()); + + f_service_ << autogen_comment() << csharp_type_usings() << csharp_thrift_usings() << endl; + + start_csharp_namespace(f_service_); + + indent(f_service_) << "public partial class " << normalize_name(service_name_) << " {" << endl; + indent_up(); + + generate_service_interface(tservice); + generate_service_client(tservice); + generate_service_server(tservice); + generate_service_helpers(tservice); + + indent_down(); + + indent(f_service_) << "}" << endl; + end_csharp_namespace(f_service_); + f_service_.close(); +} + +void t_csharp_generator::generate_service_interface(t_service* tservice) { + generate_separate_service_interfaces(tservice); +} + +void t_csharp_generator::generate_separate_service_interfaces(t_service* tservice) { + generate_sync_service_interface(tservice); + + if (async_) { + generate_async_service_interface(tservice); + } + + generate_combined_service_interface(tservice); +} + +void t_csharp_generator::generate_sync_service_interface(t_service* tservice) { + string extends = ""; + string extends_iface = ""; + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + extends_iface = " : " + extends + ".ISync"; + } + + generate_csharp_doc(f_service_, tservice); + + if (wcf_) { + indent(f_service_) << "[System.ServiceModel.ServiceContract(Namespace=\"" << wcf_namespace_ << "\")]" << endl; + } + indent(f_service_) << "public interface ISync" << extends_iface << " {" << endl; + + indent_up(); + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_csharp_doc(f_service_, *f_iter); + + // if we're using WCF, add the corresponding attributes + if (wcf_) { + indent(f_service_) << "[System.ServiceModel.OperationContract]" << endl; + + const std::vector<t_field*>& xceptions = (*f_iter)->get_xceptions()->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + indent(f_service_) << "[System.ServiceModel.FaultContract(typeof(" + + type_name((*x_iter)->get_type(), false, false) + "Fault))]" << endl; + } + } + + indent(f_service_) << function_signature(*f_iter) << ";" << endl; + } + indent_down(); + f_service_ << indent() << "}" << endl << endl; +} + +void t_csharp_generator::generate_async_service_interface(t_service* tservice) { + string extends = ""; + string extends_iface = ""; + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + extends_iface = " : " + extends + ".IAsync"; + } + + generate_csharp_doc(f_service_, tservice); + + if (wcf_) { + indent(f_service_) << "[System.ServiceModel.ServiceContract(Namespace=\"" << wcf_namespace_ << "\")]" << endl; + } + indent(f_service_) << "public interface IAsync" << extends_iface << " {" << endl; + + indent_up(); + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_csharp_doc(f_service_, *f_iter); + + // if we're using WCF, add the corresponding attributes + if (wcf_) { + indent(f_service_) << "[System.ServiceModel.OperationContract]" << endl; + + const std::vector<t_field*>& xceptions = (*f_iter)->get_xceptions()->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + indent(f_service_) << "[System.ServiceModel.FaultContract(typeof(" + + type_name((*x_iter)->get_type(), false, false) + "Fault))]" << endl; + } + } + + indent(f_service_) << function_signature_async(*f_iter) << ";" << endl; + } + indent_down(); + f_service_ << indent() << "}" << endl << endl; +} + +void t_csharp_generator::generate_combined_service_interface(t_service* tservice) { + string extends_iface = " : ISync"; + + if (async_) { + extends_iface += ", IAsync"; + } + + generate_csharp_doc(f_service_, tservice); + + if (wcf_) { + indent(f_service_) << "[System.ServiceModel.ServiceContract(Namespace=\"" << wcf_namespace_ << "\")]" << endl; + } + + indent(f_service_) << "public interface Iface" << extends_iface << " {" << endl; + + indent_up(); + + // We need to generate extra old style async methods for silverlight. Since + // this isn't something you'd want to implement server-side, just put them into + // the main Iface interface. + generate_silverlight_async_methods(tservice); + + indent_down(); + + f_service_ << indent() << "}" << endl << endl; +} + +void t_csharp_generator::generate_silverlight_async_methods(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_csharp_doc(f_service_, *f_iter); + + // For backwards compatibility, include the Begin_, End_ methods if we're generating + // with the async flag. I'm not sure this is necessary, so someone with more knowledge + // can maybe remove these checks if they know it's safe. + if (!async_) { + indent(f_service_) << "#if SILVERLIGHT" << endl; + } + + indent(f_service_) << function_signature_async_begin(*f_iter, "Begin_") << ";" << endl; + indent(f_service_) << function_signature_async_end(*f_iter, "End_") << ";" << endl; + + if (!async_) { + indent(f_service_) << "#endif" << endl; + } + } +} + +void t_csharp_generator::generate_service_helpers(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* ts = (*f_iter)->get_arglist(); + generate_csharp_struct_definition(f_service_, ts, false, true); + generate_function_helpers(*f_iter); + } +} + +void t_csharp_generator::generate_service_client(t_service* tservice) { + string extends = ""; + string extends_client = ""; + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + extends_client = extends + ".Client, "; + } else { + extends_client = "IDisposable, "; + } + + generate_csharp_doc(f_service_, tservice); + + indent(f_service_) << "public class Client : " << extends_client << "Iface {" << endl; + indent_up(); + indent(f_service_) << "public Client(TProtocol prot) : this(prot, prot)" << endl; + scope_up(f_service_); + scope_down(f_service_); + f_service_ << endl; + + indent(f_service_) << "public Client(TProtocol iprot, TProtocol oprot)"; + if (!extends.empty()) { + f_service_ << " : base(iprot, oprot)"; + } + f_service_ << endl; + + scope_up(f_service_); + if (extends.empty()) { + f_service_ << indent() << "iprot_ = iprot;" << endl << indent() << "oprot_ = oprot;" << endl; + } + scope_down(f_service_); + + f_service_ << endl; + + if (extends.empty()) { + f_service_ << indent() << "protected TProtocol iprot_;" << endl << indent() + << "protected TProtocol oprot_;" << endl << indent() << "protected int seqid_;" + << endl << endl; + + f_service_ << indent() << "public TProtocol InputProtocol" << endl; + scope_up(f_service_); + indent(f_service_) << "get { return iprot_; }" << endl; + scope_down(f_service_); + + f_service_ << indent() << "public TProtocol OutputProtocol" << endl; + scope_up(f_service_); + indent(f_service_) << "get { return oprot_; }" << endl; + scope_down(f_service_); + f_service_ << endl << endl; + + indent(f_service_) << "#region \" IDisposable Support \"" << endl; + indent(f_service_) << "private bool _IsDisposed;" << endl << endl; + indent(f_service_) << "// IDisposable" << endl; + indent(f_service_) << "public void Dispose()" << endl; + scope_up(f_service_); + indent(f_service_) << "Dispose(true);" << endl; + scope_down(f_service_); + indent(f_service_) << endl << endl; + indent(f_service_) << "protected virtual void Dispose(bool disposing)" << endl; + scope_up(f_service_); + indent(f_service_) << "if (!_IsDisposed)" << endl; + scope_up(f_service_); + indent(f_service_) << "if (disposing)" << endl; + scope_up(f_service_); + indent(f_service_) << "if (iprot_ != null)" << endl; + scope_up(f_service_); + indent(f_service_) << "((IDisposable)iprot_).Dispose();" << endl; + scope_down(f_service_); + indent(f_service_) << "if (oprot_ != null)" << endl; + scope_up(f_service_); + indent(f_service_) << "((IDisposable)oprot_).Dispose();" << endl; + scope_down(f_service_); + scope_down(f_service_); + scope_down(f_service_); + indent(f_service_) << "_IsDisposed = true;" << endl; + scope_down(f_service_); + indent(f_service_) << "#endregion" << endl; + f_service_ << endl << endl; + } + + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + string funname = (*f_iter)->get_name(); + + indent(f_service_) << endl; + + if (!async_) { + indent(f_service_) << "#if SILVERLIGHT" << endl; + indent(f_service_) << endl; + } + // Begin_ + indent(f_service_) << "public " << function_signature_async_begin(*f_iter, "Begin_") << endl; + scope_up(f_service_); + indent(f_service_) << "return " + << "send_" << funname << "(callback, state"; + + t_struct* arg_struct = (*f_iter)->get_arglist(); + prepare_member_name_mapping(arg_struct); + + const vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator fld_iter; + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + f_service_ << ", "; + f_service_ << normalize_name((*fld_iter)->get_name()); + } + f_service_ << ");" << endl; + scope_down(f_service_); + f_service_ << endl; + + // End + indent(f_service_) << "public " << function_signature_async_end(*f_iter, "End_") << endl; + scope_up(f_service_); + indent(f_service_) << "oprot_.Transport.EndFlush(asyncResult);" << endl; + if (!(*f_iter)->is_oneway()) { + f_service_ << indent(); + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_ << "return "; + } + f_service_ << "recv_" << funname << "();" << endl; + } + scope_down(f_service_); + f_service_ << endl; + + // async + bool first; + if (async_) { + indent(f_service_) << "public async " << function_signature_async(*f_iter, "") << endl; + scope_up(f_service_); + + if (!(*f_iter)->get_returntype()->is_void()) { + indent(f_service_) << type_name((*f_iter)->get_returntype()) << " retval;" << endl; + indent(f_service_) << "retval = "; + } else { + indent(f_service_); + } + f_service_ << "await Task.Run(() =>" << endl; + scope_up(f_service_); + indent(f_service_); + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_ << "return "; + } + f_service_ << funname << "("; + first = true; + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << (*fld_iter)->get_name(); + } + f_service_ << ");" << endl; + indent_down(); + indent(f_service_) << "});" << endl; + if (!(*f_iter)->get_returntype()->is_void()) { + indent(f_service_) << "return retval;" << endl; + } + scope_down(f_service_); + f_service_ << endl; + } + + if (!async_) { + indent(f_service_) << "#endif" << endl << endl; + } + + generate_csharp_doc(f_service_, *f_iter); + indent(f_service_) << "public " << function_signature(*f_iter) << endl; + scope_up(f_service_); + + // silverlight invoke + if (!async_) { + indent(f_service_) << "#if SILVERLIGHT" << endl; + + indent(f_service_) << "var asyncResult = Begin_" << funname << "(null, null"; + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + f_service_ << ", " << normalize_name((*fld_iter)->get_name()); + } + f_service_ << ");" << endl; + + if (!(*f_iter)->is_oneway()) { + f_service_ << indent(); + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_ << "return "; + } + f_service_ << "End_" << funname << "(asyncResult);" << endl; + } + f_service_ << endl; + + indent(f_service_) << "#else" << endl; + } + + // synchronous invoke + indent(f_service_) << "send_" << funname << "("; + + first = true; + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << normalize_name((*fld_iter)->get_name()); + } + f_service_ << ");" << endl; + + if (!(*f_iter)->is_oneway()) { + f_service_ << indent(); + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_ << "return "; + } + f_service_ << "recv_" << funname << "();" << endl; + } + f_service_ << endl; + + if (!async_) { + indent(f_service_) << "#endif" << endl; + } + scope_down(f_service_); + + // Send + t_function send_function(g_type_void, + string("send_") + (*f_iter)->get_name(), + (*f_iter)->get_arglist()); + + string argsname = (*f_iter)->get_name() + "_args"; + + if (!async_) { + indent(f_service_) << "#if SILVERLIGHT" << endl; + } + + indent(f_service_) << "public " << function_signature_async_begin(&send_function) << endl; + scope_up(f_service_); + + f_service_ << indent() << "oprot_.WriteMessageBegin(new TMessage(\"" << funname << "\", " + << ((*f_iter)->is_oneway() ? "TMessageType.Oneway" : "TMessageType.Call") + << ", seqid_));" << endl << indent() << argsname << " args = new " << argsname + << "();" << endl; + + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + f_service_ << indent() << "args." << prop_name(*fld_iter) << " = " + << normalize_name((*fld_iter)->get_name()) << ";" << endl; + } + + f_service_ << indent() << "args.Write(oprot_);" << endl << indent() + << "oprot_.WriteMessageEnd();" << endl; + indent(f_service_) << "return oprot_.Transport.BeginFlush(callback, state);" << endl; + + scope_down(f_service_); + f_service_ << endl; + + if (!async_) { + indent(f_service_) << "#else" << endl; + f_service_ << endl; + } + + indent(f_service_) << "public " << function_signature(&send_function) << endl; + scope_up(f_service_); + + f_service_ << indent() << "oprot_.WriteMessageBegin(new TMessage(\"" << funname << "\", " + << ((*f_iter)->is_oneway() ? "TMessageType.Oneway" : "TMessageType.Call") + << ", seqid_));" << endl << indent() << argsname << " args = new " << argsname + << "();" << endl; + + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + f_service_ << indent() << "args." << prop_name(*fld_iter) << " = " + << normalize_name((*fld_iter)->get_name()) << ";" << endl; + } + + f_service_ << indent() << "args.Write(oprot_);" << endl << indent() + << "oprot_.WriteMessageEnd();" << endl; + + indent(f_service_) << "oprot_.Transport.Flush();" << endl; + cleanup_member_name_mapping(arg_struct); + scope_down(f_service_); + + if (!async_) { + indent(f_service_) << "#endif" << endl; + } + + f_service_ << endl; + + if (!(*f_iter)->is_oneway()) { + string resultname = (*f_iter)->get_name() + "_result"; + + t_struct noargs(program_); + t_function recv_function((*f_iter)->get_returntype(), + string("recv_") + (*f_iter)->get_name(), + &noargs, + (*f_iter)->get_xceptions()); + indent(f_service_) << "public " << function_signature(&recv_function) << endl; + scope_up(f_service_); + + t_struct* xs = (*f_iter)->get_xceptions(); + prepare_member_name_mapping(xs, xs->get_members(), resultname); + + f_service_ << indent() << "TMessage msg = iprot_.ReadMessageBegin();" << endl << indent() + << "if (msg.Type == TMessageType.Exception) {" << endl; + indent_up(); + f_service_ << indent() << "TApplicationException x = TApplicationException.Read(iprot_);" + << endl << indent() << "iprot_.ReadMessageEnd();" << endl << indent() << "throw x;" + << endl; + indent_down(); + f_service_ << indent() << "}" << endl << indent() << resultname << " result = new " + << resultname << "();" << endl << indent() << "result.Read(iprot_);" << endl + << indent() << "iprot_.ReadMessageEnd();" << endl; + + if (!(*f_iter)->get_returntype()->is_void()) { + if (nullable_) { + if (type_can_be_null((*f_iter)->get_returntype())) { + f_service_ << indent() << "if (result.Success != null) {" << endl << indent() + << " return result.Success;" << endl << indent() << "}" << endl; + } else { + f_service_ << indent() << "if (result.Success.HasValue) {" << endl << indent() + << " return result.Success.Value;" << endl << indent() << "}" << endl; + } + } else { + f_service_ << indent() << "if (result.__isset.success) {" << endl << indent() + << " return result.Success;" << endl << indent() << "}" << endl; + } + } + + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + if (nullable_) { + f_service_ << indent() << "if (result." << prop_name(*x_iter) << " != null) {" << endl + << indent() << " throw result." << prop_name(*x_iter) << ";" << endl + << indent() << "}" << endl; + } else { + f_service_ << indent() << "if (result.__isset." << normalize_name((*x_iter)->get_name()) + << ") {" << endl << indent() << " throw result." << prop_name(*x_iter) << ";" + << endl << indent() << "}" << endl; + } + } + + if ((*f_iter)->get_returntype()->is_void()) { + indent(f_service_) << "return;" << endl; + } else { + f_service_ << indent() + << "throw new " + "TApplicationException(TApplicationException.ExceptionType.MissingResult, \"" + << (*f_iter)->get_name() << " failed: unknown result\");" << endl; + } + + cleanup_member_name_mapping((*f_iter)->get_xceptions()); + scope_down(f_service_); + f_service_ << endl; + } + } + + indent_down(); + indent(f_service_) << "}" << endl; +} + +void t_csharp_generator::generate_service_server(t_service* tservice) { + if (async_) { + generate_service_server_async(tservice); + generate_service_server_sync(tservice); + } + else { + generate_service_server_sync(tservice); + } +} + +void t_csharp_generator::generate_service_server_sync(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + string extends = ""; + string extends_processor = ""; + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + extends_processor = extends + ".Processor, "; + } + + indent(f_service_) << "public class Processor : " << extends_processor << "TProcessor {" << endl; + indent_up(); + + indent(f_service_) << "public Processor(ISync iface)"; + + if (!extends.empty()) { + f_service_ << " : base(iface)"; + } + f_service_ << endl; + scope_up(f_service_); + f_service_ << indent() << "iface_ = iface;" << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + f_service_ << indent() << "processMap_[\"" << (*f_iter)->get_name() + << "\"] = " << (*f_iter)->get_name() << "_Process;" << endl; + } + + scope_down(f_service_); + f_service_ << endl; + + if (extends.empty()) { + f_service_ + << indent() + << "protected delegate void ProcessFunction(int seqid, TProtocol iprot, TProtocol oprot);" + << endl; + } + + f_service_ << indent() << "private ISync iface_;" << endl; + + if (extends.empty()) { + f_service_ << indent() << "protected Dictionary<string, ProcessFunction> processMap_ = new " + "Dictionary<string, ProcessFunction>();" << endl; + } + + f_service_ << endl; + + if (extends.empty()) { + indent(f_service_) << "public bool Process(TProtocol iprot, TProtocol oprot)" << endl; + } + else { + indent(f_service_) << "public new bool Process(TProtocol iprot, TProtocol oprot)" << endl; + } + scope_up(f_service_); + + f_service_ << indent() << "try" << endl; + scope_up(f_service_); + + f_service_ << indent() << "TMessage msg = iprot.ReadMessageBegin();" << endl; + + f_service_ + << indent() << "ProcessFunction fn;" << endl << indent() + << "processMap_.TryGetValue(msg.Name, out fn);" << endl << indent() << "if (fn == null) {" + << endl << indent() << " TProtocolUtil.Skip(iprot, TType.Struct);" << endl << indent() + << " iprot.ReadMessageEnd();" << endl << indent() + << " TApplicationException x = new TApplicationException " + "(TApplicationException.ExceptionType.UnknownMethod, \"Invalid method name: '\" + " + "msg.Name + \"'\");" << endl << indent() + << " oprot.WriteMessageBegin(new TMessage(msg.Name, TMessageType.Exception, msg.SeqID));" + << endl << indent() << " x.Write(oprot);" << endl << indent() << " oprot.WriteMessageEnd();" + << endl << indent() << " oprot.Transport.Flush();" << endl << indent() << " return true;" + << endl << indent() << "}" << endl << indent() << "fn(msg.SeqID, iprot, oprot);" << endl; + + scope_down(f_service_); + + f_service_ << indent() << "catch (IOException)" << endl; + scope_up(f_service_); + f_service_ << indent() << "return false;" << endl; + scope_down(f_service_); + + f_service_ << indent() << "return true;" << endl; + + scope_down(f_service_); + f_service_ << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_process_function(tservice, *f_iter); + } + + indent_down(); + indent(f_service_) << "}" << endl << endl; +} + +void t_csharp_generator::generate_service_server_async(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + string extends = ""; + string extends_processor = ""; + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + extends_processor = extends + ".AsyncProcessor, "; + } + + indent(f_service_) << "public class AsyncProcessor : " << extends_processor << "TAsyncProcessor {" << endl; + indent_up(); + + indent(f_service_) << "public AsyncProcessor(IAsync iface)"; + if (!extends.empty()) { + f_service_ << " : base(iface)"; + } + f_service_ << endl; + scope_up(f_service_); + f_service_ << indent() << "iface_ = iface;" << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + f_service_ << indent() << "processMap_[\"" << (*f_iter)->get_name() + << "\"] = " << (*f_iter)->get_name() << "_ProcessAsync;" << endl; + } + + scope_down(f_service_); + f_service_ << endl; + + if (extends.empty()) { + f_service_ + << indent() + << "protected delegate Task ProcessFunction(int seqid, TProtocol iprot, TProtocol oprot);" + << endl; + } + + f_service_ << indent() << "private IAsync iface_;" << endl; + + if (extends.empty()) { + f_service_ << indent() << "protected Dictionary<string, ProcessFunction> processMap_ = new " + "Dictionary<string, ProcessFunction>();" << endl; + } + + f_service_ << endl; + + if (extends.empty()) { + indent(f_service_) << "public async Task<bool> ProcessAsync(TProtocol iprot, TProtocol oprot)" << endl; + } + else { + indent(f_service_) << "public new async Task<bool> ProcessAsync(TProtocol iprot, TProtocol oprot)" << endl; + } + scope_up(f_service_); + + f_service_ << indent() << "try" << endl; + scope_up(f_service_); + + f_service_ << indent() << "TMessage msg = iprot.ReadMessageBegin();" << endl; + + f_service_ + << indent() << "ProcessFunction fn;" << endl << indent() + << "processMap_.TryGetValue(msg.Name, out fn);" << endl << indent() << "if (fn == null) {" + << endl << indent() << " TProtocolUtil.Skip(iprot, TType.Struct);" << endl << indent() + << " iprot.ReadMessageEnd();" << endl << indent() + << " TApplicationException x = new TApplicationException " + "(TApplicationException.ExceptionType.UnknownMethod, \"Invalid method name: '\" + " + "msg.Name + \"'\");" << endl << indent() + << " oprot.WriteMessageBegin(new TMessage(msg.Name, TMessageType.Exception, msg.SeqID));" + << endl << indent() << " x.Write(oprot);" << endl << indent() << " oprot.WriteMessageEnd();" + << endl << indent() << " oprot.Transport.Flush();" << endl << indent() << " return true;" + << endl << indent() << "}" << endl << indent() << "await fn(msg.SeqID, iprot, oprot);" << endl; + + scope_down(f_service_); + + f_service_ << indent() << "catch (IOException)" << endl; + scope_up(f_service_); + f_service_ << indent() << "return false;" << endl; + scope_down(f_service_); + + f_service_ << indent() << "return true;" << endl; + + scope_down(f_service_); + f_service_ << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_process_function_async(tservice, *f_iter); + } + + indent_down(); + indent(f_service_) << "}" << endl << endl; +} + +void t_csharp_generator::generate_function_helpers(t_function* tfunction) { + if (tfunction->is_oneway()) { + return; + } + + t_struct result(program_, tfunction->get_name() + "_result"); + t_field success(tfunction->get_returntype(), "success", 0); + if (!tfunction->get_returntype()->is_void()) { + result.append(&success); + } + + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& fields = xs->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + result.append(*f_iter); + } + + generate_csharp_struct_definition(f_service_, &result, false, true, true); +} + +void t_csharp_generator::generate_process_function(t_service* tservice, t_function* tfunction) { + (void)tservice; + indent(f_service_) << "public void " << tfunction->get_name() + << "_Process(int seqid, TProtocol iprot, TProtocol oprot)" << endl; + scope_up(f_service_); + + string argsname = tfunction->get_name() + "_args"; + string resultname = tfunction->get_name() + "_result"; + + f_service_ << indent() << argsname << " args = new " << argsname << "();" << endl + << indent() << "args.Read(iprot);" << endl + << indent() << "iprot.ReadMessageEnd();" << endl; + + t_struct* xs = tfunction->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + + if (!tfunction->is_oneway()) { + f_service_ << indent() << resultname << " result = new " << resultname << "();" << endl; + } + + f_service_ << indent() << "try" << endl + << indent() << "{" << endl; + indent_up(); + + if (xceptions.size() > 0) { + f_service_ << indent() << "try" << endl + << indent() << "{" << endl; + indent_up(); + } + + t_struct* arg_struct = tfunction->get_arglist(); + const std::vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + + f_service_ << indent(); + if (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void()) { + f_service_ << "result.Success = "; + } + f_service_ << "iface_." << normalize_name(tfunction->get_name()) << "("; + bool first = true; + prepare_member_name_mapping(arg_struct); + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << "args." << prop_name(*f_iter); + if (nullable_ && !type_can_be_null((*f_iter)->get_type())) { + f_service_ << ".Value"; + } + } + cleanup_member_name_mapping(arg_struct); + f_service_ << ");" << endl; + + prepare_member_name_mapping(xs, xs->get_members(), resultname); + if (xceptions.size() > 0) { + indent_down(); + f_service_ << indent() << "}" << endl; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + f_service_ << indent() << "catch (" << type_name((*x_iter)->get_type(), false, false) << " " + << (*x_iter)->get_name() << ")" << endl + << indent() << "{" << endl; + if (!tfunction->is_oneway()) { + indent_up(); + f_service_ << indent() << "result." << prop_name(*x_iter) << " = " << (*x_iter)->get_name() + << ";" << endl; + indent_down(); + } + f_service_ << indent() << "}" << endl; + } + } + if (!tfunction->is_oneway()) { + f_service_ << indent() << "oprot.WriteMessageBegin(new TMessage(\"" << tfunction->get_name() + << "\", TMessageType.Reply, seqid)); " << endl; + f_service_ << indent() << "result.Write(oprot);" << endl; + } + indent_down(); + + cleanup_member_name_mapping(xs); + + f_service_ << indent() << "}" << endl + << indent() << "catch (TTransportException)" << endl + << indent() << "{" << endl + << indent() << " throw;" << endl + << indent() << "}" << endl + << indent() << "catch (Exception ex)" << endl + << indent() << "{" << endl + << indent() << " Console.Error.WriteLine(\"Error occurred in processor:\");" << endl + << indent() << " Console.Error.WriteLine(ex.ToString());" << endl; + + if (tfunction->is_oneway()) { + f_service_ << indent() << "}" << endl; + } else { + f_service_ << indent() << " TApplicationException x = new TApplicationException" << indent() + << "(TApplicationException.ExceptionType.InternalError,\" Internal error.\");" + << endl + << indent() << " oprot.WriteMessageBegin(new TMessage(\"" << tfunction->get_name() + << "\", TMessageType.Exception, seqid));" << endl + << indent() << " x.Write(oprot);" << endl + << indent() << "}" << endl; + f_service_ << indent() << "oprot.WriteMessageEnd();" << endl + << indent() << "oprot.Transport.Flush();" << endl; + } + + scope_down(f_service_); + + f_service_ << endl; +} + +void t_csharp_generator::generate_process_function_async(t_service* tservice, t_function* tfunction) { + (void)tservice; + indent(f_service_) << "public async Task " << tfunction->get_name() + << "_ProcessAsync(int seqid, TProtocol iprot, TProtocol oprot)" << endl; + scope_up(f_service_); + + string argsname = tfunction->get_name() + "_args"; + string resultname = tfunction->get_name() + "_result"; + + f_service_ << indent() << argsname << " args = new " << argsname << "();" << endl + << indent() << "args.Read(iprot);" << endl + << indent() << "iprot.ReadMessageEnd();" << endl; + + t_struct* xs = tfunction->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + + if (!tfunction->is_oneway()) { + f_service_ << indent() << resultname << " result = new " << resultname << "();" << endl; + } + + f_service_ << indent() << "try" << endl + << indent() << "{" << endl; + indent_up(); + + if (xceptions.size() > 0) { + f_service_ << indent() << "try" << endl + << indent() << "{" << endl; + indent_up(); + } + + t_struct* arg_struct = tfunction->get_arglist(); + const std::vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + + f_service_ << indent(); + if (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void()) { + f_service_ << "result.Success = "; + } + f_service_ << "await iface_." << normalize_name(tfunction->get_name()) << "Async("; + bool first = true; + prepare_member_name_mapping(arg_struct); + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } + else { + f_service_ << ", "; + } + f_service_ << "args." << prop_name(*f_iter); + if (nullable_ && !type_can_be_null((*f_iter)->get_type())) { + f_service_ << ".Value"; + } + } + cleanup_member_name_mapping(arg_struct); + f_service_ << ");" << endl; + + prepare_member_name_mapping(xs, xs->get_members(), resultname); + if (xceptions.size() > 0) { + indent_down(); + f_service_ << indent() << "}" << endl; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + f_service_ << indent() << "catch (" << type_name((*x_iter)->get_type(), false, false) << " " + << (*x_iter)->get_name() << ")" << endl + << indent() << "{" << endl; + if (!tfunction->is_oneway()) { + indent_up(); + f_service_ << indent() << "result." << prop_name(*x_iter) << " = " << (*x_iter)->get_name() + << ";" << endl; + indent_down(); + } + f_service_ << indent() << "}" << endl; + } + } + if (!tfunction->is_oneway()) { + f_service_ << indent() << "oprot.WriteMessageBegin(new TMessage(\"" << tfunction->get_name() + << "\", TMessageType.Reply, seqid)); " << endl; + f_service_ << indent() << "result.Write(oprot);" << endl; + } + indent_down(); + + cleanup_member_name_mapping(xs); + + f_service_ << indent() << "}" << endl + << indent() << "catch (TTransportException)" << endl + << indent() << "{" << endl + << indent() << " throw;" << endl + << indent() << "}" << endl + << indent() << "catch (Exception ex)" << endl + << indent() << "{" << endl + << indent() << " Console.Error.WriteLine(\"Error occurred in processor:\");" << endl + << indent() << " Console.Error.WriteLine(ex.ToString());" << endl; + + if (tfunction->is_oneway()) { + f_service_ << indent() << "}" << endl; + } + else { + f_service_ << indent() << " TApplicationException x = new TApplicationException" << indent() + << "(TApplicationException.ExceptionType.InternalError,\" Internal error.\");" + << endl + << indent() << " oprot.WriteMessageBegin(new TMessage(\"" << tfunction->get_name() + << "\", TMessageType.Exception, seqid));" << endl + << indent() << " x.Write(oprot);" << endl + << indent() << "}" << endl; + f_service_ << indent() << "oprot.WriteMessageEnd();" << endl + << indent() << "oprot.Transport.Flush();" << endl; + } + + scope_down(f_service_); + + f_service_ << endl; +} + +void t_csharp_generator::generate_csharp_union_reader(std::ostream& out, t_struct* tunion) { + // Thanks to THRIFT-1768, we don't need to check for required fields in the union + const vector<t_field*>& fields = tunion->get_members(); + vector<t_field*>::const_iterator f_iter; + + indent(out) << "public static " << tunion->get_name() << " Read(TProtocol iprot)" << endl; + scope_up(out); + + out << indent() << "iprot.IncrementRecursionDepth();" << endl; + out << indent() << "try" << endl; + scope_up(out); + + indent(out) << tunion->get_name() << " retval;" << endl; + indent(out) << "iprot.ReadStructBegin();" << endl; + indent(out) << "TField field = iprot.ReadFieldBegin();" << endl; + // we cannot have the first field be a stop -- we must have a single field defined + indent(out) << "if (field.Type == TType.Stop)" << endl; + scope_up(out); + indent(out) << "iprot.ReadFieldEnd();" << endl; + indent(out) << "retval = new ___undefined();" << endl; + scope_down(out); + indent(out) << "else" << endl; + scope_up(out); + indent(out) << "switch (field.ID)" << endl; + scope_up(out); + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + indent(out) << "case " << (*f_iter)->get_key() << ":" << endl; + indent_up(); + indent(out) << "if (field.Type == " << type_to_enum((*f_iter)->get_type()) << ") {" << endl; + indent_up(); + + indent(out) << type_name((*f_iter)->get_type()) << " temp;" << endl; + generate_deserialize_field(out, (*f_iter), "temp", true); + indent(out) << "retval = new " << (*f_iter)->get_name() << "(temp);" << endl; + + indent_down(); + out << indent() << "} else { " << endl << indent() << " TProtocolUtil.Skip(iprot, field.Type);" + << endl << indent() << " retval = new ___undefined();" << endl << indent() << "}" << endl + << indent() << "break;" << endl; + indent_down(); + } + + indent(out) << "default: " << endl; + indent_up(); + indent(out) << "TProtocolUtil.Skip(iprot, field.Type);" << endl << indent() + << "retval = new ___undefined();" << endl; + indent(out) << "break;" << endl; + indent_down(); + + scope_down(out); + + indent(out) << "iprot.ReadFieldEnd();" << endl; + + indent(out) << "if (iprot.ReadFieldBegin().Type != TType.Stop)" << endl; + scope_up(out); + indent(out) << "throw new TProtocolException(TProtocolException.INVALID_DATA);" << endl; + scope_down(out); + + // end of else for TStop + scope_down(out); + indent(out) << "iprot.ReadStructEnd();" << endl; + indent(out) << "return retval;" << endl; + indent_down(); + + scope_down(out); + out << indent() << "finally" << endl; + scope_up(out); + out << indent() << "iprot.DecrementRecursionDepth();" << endl; + scope_down(out); + + indent(out) << "}" << endl << endl; +} + +void t_csharp_generator::generate_deserialize_field(ostream& out, + t_field* tfield, + string prefix, + bool is_propertyless) { + t_type* type = tfield->get_type(); + while (type->is_typedef()) { + type = ((t_typedef*)type)->get_type(); + } + + if (type->is_void()) { + throw "CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name(); + } + + string name = prefix + (is_propertyless ? "" : prop_name(tfield)); + + if (type->is_struct() || type->is_xception()) { + generate_deserialize_struct(out, (t_struct*)type, name); + } else if (type->is_container()) { + generate_deserialize_container(out, type, name); + } else if (type->is_base_type() || type->is_enum()) { + indent(out) << name << " = "; + + if (type->is_enum()) { + out << "(" << type_name(type, false, true) << ")"; + } + + out << "iprot."; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + out << "ReadBinary();"; + } else { + out << "ReadString();"; + } + break; + case t_base_type::TYPE_BOOL: + out << "ReadBool();"; + break; + case t_base_type::TYPE_I8: + out << "ReadByte();"; + break; + case t_base_type::TYPE_I16: + out << "ReadI16();"; + break; + case t_base_type::TYPE_I32: + out << "ReadI32();"; + break; + case t_base_type::TYPE_I64: + out << "ReadI64();"; + break; + case t_base_type::TYPE_DOUBLE: + out << "ReadDouble();"; + break; + default: + throw "compiler error: no C# name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "ReadI32();"; + } + out << endl; + } else { + printf("DO NOT KNOW HOW TO DESERIALIZE FIELD '%s' TYPE '%s'\n", + tfield->get_name().c_str(), + type_name(type).c_str()); + } +} + +void t_csharp_generator::generate_deserialize_struct(ostream& out, + t_struct* tstruct, + string prefix) { + if (union_ && tstruct->is_union()) { + out << indent() << prefix << " = " << type_name(tstruct) << ".Read(iprot);" << endl; + } else { + out << indent() << prefix << " = new " << type_name(tstruct) << "();" << endl << indent() + << prefix << ".Read(iprot);" << endl; + } +} + +void t_csharp_generator::generate_deserialize_container(ostream& out, + t_type* ttype, + string prefix) { + scope_up(out); + + string obj; + + if (ttype->is_map()) { + obj = tmp("_map"); + } else if (ttype->is_set()) { + obj = tmp("_set"); + } else if (ttype->is_list()) { + obj = tmp("_list"); + } + + indent(out) << prefix << " = new " << type_name(ttype, false, true) << "();" << endl; + if (ttype->is_map()) { + out << indent() << "TMap " << obj << " = iprot.ReadMapBegin();" << endl; + } else if (ttype->is_set()) { + out << indent() << "TSet " << obj << " = iprot.ReadSetBegin();" << endl; + } else if (ttype->is_list()) { + out << indent() << "TList " << obj << " = iprot.ReadListBegin();" << endl; + } + + string i = tmp("_i"); + indent(out) << "for( int " << i << " = 0; " << i << " < " << obj << ".Count" + << "; " + << "++" << i << ")" << endl; + scope_up(out); + + if (ttype->is_map()) { + generate_deserialize_map_element(out, (t_map*)ttype, prefix); + } else if (ttype->is_set()) { + generate_deserialize_set_element(out, (t_set*)ttype, prefix); + } else if (ttype->is_list()) { + generate_deserialize_list_element(out, (t_list*)ttype, prefix); + } + + scope_down(out); + + if (ttype->is_map()) { + indent(out) << "iprot.ReadMapEnd();" << endl; + } else if (ttype->is_set()) { + indent(out) << "iprot.ReadSetEnd();" << endl; + } else if (ttype->is_list()) { + indent(out) << "iprot.ReadListEnd();" << endl; + } + + scope_down(out); +} + +void t_csharp_generator::generate_deserialize_map_element(ostream& out, + t_map* tmap, + string prefix) { + string key = tmp("_key"); + string val = tmp("_val"); + + t_field fkey(tmap->get_key_type(), key); + t_field fval(tmap->get_val_type(), val); + + indent(out) << declare_field(&fkey) << endl; + indent(out) << declare_field(&fval) << endl; + + generate_deserialize_field(out, &fkey); + generate_deserialize_field(out, &fval); + + indent(out) << prefix << "[" << key << "] = " << val << ";" << endl; +} + +void t_csharp_generator::generate_deserialize_set_element(ostream& out, + t_set* tset, + string prefix) { + string elem = tmp("_elem"); + t_field felem(tset->get_elem_type(), elem); + + indent(out) << declare_field(&felem) << endl; + + generate_deserialize_field(out, &felem); + + indent(out) << prefix << ".Add(" << elem << ");" << endl; +} + +void t_csharp_generator::generate_deserialize_list_element(ostream& out, + t_list* tlist, + string prefix) { + string elem = tmp("_elem"); + t_field felem(tlist->get_elem_type(), elem); + + indent(out) << declare_field(&felem) << endl; + + generate_deserialize_field(out, &felem); + + indent(out) << prefix << ".Add(" << elem << ");" << endl; +} + +void t_csharp_generator::generate_serialize_field(ostream& out, + t_field* tfield, + string prefix, + bool is_element, + bool is_propertyless) { + t_type* type = tfield->get_type(); + while (type->is_typedef()) { + type = ((t_typedef*)type)->get_type(); + } + + string name = prefix + (is_propertyless ? "" : prop_name(tfield)); + + if (type->is_void()) { + throw "CANNOT GENERATE SERIALIZE CODE FOR void TYPE: " + name; + } + + if (type->is_struct() || type->is_xception()) { + generate_serialize_struct(out, (t_struct*)type, name); + } else if (type->is_container()) { + generate_serialize_container(out, type, name); + } else if (type->is_base_type() || type->is_enum()) { + indent(out) << "oprot."; + + string nullable_name = nullable_ && !is_element && !field_is_required(tfield) ? name + ".Value" + : name; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + out << "WriteBinary("; + } else { + out << "WriteString("; + } + out << name << ");"; + break; + case t_base_type::TYPE_BOOL: + out << "WriteBool(" << nullable_name << ");"; + break; + case t_base_type::TYPE_I8: + out << "WriteByte(" << nullable_name << ");"; + break; + case t_base_type::TYPE_I16: + out << "WriteI16(" << nullable_name << ");"; + break; + case t_base_type::TYPE_I32: + out << "WriteI32(" << nullable_name << ");"; + break; + case t_base_type::TYPE_I64: + out << "WriteI64(" << nullable_name << ");"; + break; + case t_base_type::TYPE_DOUBLE: + out << "WriteDouble(" << nullable_name << ");"; + break; + default: + throw "compiler error: no C# name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "WriteI32((int)" << nullable_name << ");"; + } + out << endl; + } else { + printf("DO NOT KNOW HOW TO SERIALIZE '%s%s' TYPE '%s'\n", + prefix.c_str(), + tfield->get_name().c_str(), + type_name(type).c_str()); + } +} + +void t_csharp_generator::generate_serialize_struct(ostream& out, + t_struct* tstruct, + string prefix) { + (void)tstruct; + out << indent() << prefix << ".Write(oprot);" << endl; +} + +void t_csharp_generator::generate_serialize_container(ostream& out, t_type* ttype, string prefix) { + scope_up(out); + + if (ttype->is_map()) { + indent(out) << "oprot.WriteMapBegin(new TMap(" << type_to_enum(((t_map*)ttype)->get_key_type()) + << ", " << type_to_enum(((t_map*)ttype)->get_val_type()) << ", " << prefix + << ".Count));" << endl; + } else if (ttype->is_set()) { + indent(out) << "oprot.WriteSetBegin(new TSet(" << type_to_enum(((t_set*)ttype)->get_elem_type()) + << ", " << prefix << ".Count));" << endl; + } else if (ttype->is_list()) { + indent(out) << "oprot.WriteListBegin(new TList(" + << type_to_enum(((t_list*)ttype)->get_elem_type()) << ", " << prefix << ".Count));" + << endl; + } + + string iter = tmp("_iter"); + if (ttype->is_map()) { + indent(out) << "foreach (" << type_name(((t_map*)ttype)->get_key_type()) << " " << iter + << " in " << prefix << ".Keys)"; + } else if (ttype->is_set()) { + indent(out) << "foreach (" << type_name(((t_set*)ttype)->get_elem_type()) << " " << iter + << " in " << prefix << ")"; + } else if (ttype->is_list()) { + indent(out) << "foreach (" << type_name(((t_list*)ttype)->get_elem_type()) << " " << iter + << " in " << prefix << ")"; + } + + out << endl; + scope_up(out); + + if (ttype->is_map()) { + generate_serialize_map_element(out, (t_map*)ttype, iter, prefix); + } else if (ttype->is_set()) { + generate_serialize_set_element(out, (t_set*)ttype, iter); + } else if (ttype->is_list()) { + generate_serialize_list_element(out, (t_list*)ttype, iter); + } + + scope_down(out); + + if (ttype->is_map()) { + indent(out) << "oprot.WriteMapEnd();" << endl; + } else if (ttype->is_set()) { + indent(out) << "oprot.WriteSetEnd();" << endl; + } else if (ttype->is_list()) { + indent(out) << "oprot.WriteListEnd();" << endl; + } + + scope_down(out); +} + +void t_csharp_generator::generate_serialize_map_element(ostream& out, + t_map* tmap, + string iter, + string map) { + t_field kfield(tmap->get_key_type(), iter); + generate_serialize_field(out, &kfield, "", true); + t_field vfield(tmap->get_val_type(), map + "[" + iter + "]"); + generate_serialize_field(out, &vfield, "", true); +} + +void t_csharp_generator::generate_serialize_set_element(ostream& out, t_set* tset, string iter) { + t_field efield(tset->get_elem_type(), iter); + generate_serialize_field(out, &efield, "", true); +} + +void t_csharp_generator::generate_serialize_list_element(ostream& out, + t_list* tlist, + string iter) { + t_field efield(tlist->get_elem_type(), iter); + generate_serialize_field(out, &efield, "", true); +} + +void t_csharp_generator::generate_property(ostream& out, + t_field* tfield, + bool isPublic, + bool generateIsset) { + generate_csharp_property(out, tfield, isPublic, generateIsset, "_"); +} +void t_csharp_generator::generate_csharp_property(ostream& out, + t_field* tfield, + bool isPublic, + bool generateIsset, + std::string fieldPrefix) { + if ((serialize_ || wcf_) && isPublic) { + indent(out) << "[DataMember(Order = 0)]" << endl; + } + bool has_default = field_has_default(tfield); + bool is_required = field_is_required(tfield); + if ((nullable_ && !has_default) || (is_required)) { + indent(out) << (isPublic ? "public " : "private ") + << type_name(tfield->get_type(), false, false, true, is_required) << " " + << prop_name(tfield) << " { get; set; }" << endl; + } else { + indent(out) << (isPublic ? "public " : "private ") + << type_name(tfield->get_type(), false, false, true) << " " << prop_name(tfield) + << endl; + scope_up(out); + indent(out) << "get" << endl; + scope_up(out); + bool use_nullable = false; + if (nullable_) { + t_type* ttype = tfield->get_type(); + while (ttype->is_typedef()) { + ttype = ((t_typedef*)ttype)->get_type(); + } + if (ttype->is_base_type()) { + use_nullable = ((t_base_type*)ttype)->get_base() != t_base_type::TYPE_STRING; + } else if (ttype->is_enum()) { + use_nullable = true; + } + } + indent(out) << "return " << fieldPrefix + tfield->get_name() << ";" << endl; + scope_down(out); + indent(out) << "set" << endl; + scope_up(out); + if (use_nullable) { + if (generateIsset) { + indent(out) << "__isset." << normalize_name(tfield->get_name()) << " = value.HasValue;" + << endl; + } + indent(out) << "if (value.HasValue) this." << fieldPrefix + tfield->get_name() + << " = value.Value;" << endl; + } else { + if (generateIsset) { + indent(out) << "__isset." << normalize_name(tfield->get_name()) << " = true;" << endl; + } + indent(out) << "this." << fieldPrefix + tfield->get_name() << " = value;" << endl; + } + scope_down(out); + scope_down(out); + } + out << endl; +} + +std::string t_csharp_generator::make_valid_csharp_identifier(std::string const& fromName) { + std::string str = fromName; + if (str.empty()) { + return str; + } + + // tests rely on this + assert(('A' < 'Z') && ('a' < 'z') && ('0' < '9')); + + // if the first letter is a number, we add an additional underscore in front of it + char c = str.at(0); + if (('0' <= c) && (c <= '9')) { + str = "_" + str; + } + + // following chars: letter, number or underscore + for (size_t i = 0; i < str.size(); ++i) { + c = str.at(i); + if ((('A' > c) || (c > 'Z')) && (('a' > c) || (c > 'z')) && (('0' > c) || (c > '9')) + && ('_' != c)) { + str.replace(i, 1, "_"); + } + } + + return str; +} + +void t_csharp_generator::cleanup_member_name_mapping(void* scope) { + if( member_mapping_scopes.empty()) { + throw "internal error: cleanup_member_name_mapping() no scope active"; + } + + member_mapping_scope& active = member_mapping_scopes.back(); + if (active.scope_member != scope) { + throw "internal error: cleanup_member_name_mapping() called for wrong struct"; + } + + member_mapping_scopes.pop_back(); +} + +string t_csharp_generator::get_mapped_member_name(string name) { + if( ! member_mapping_scopes.empty()) { + member_mapping_scope& active = member_mapping_scopes.back(); + map<string, string>::iterator iter = active.mapping_table.find(name); + if (active.mapping_table.end() != iter) { + return iter->second; + } + } + + pverbose("no mapping for member %s\n", name.c_str()); + return name; +} + +void t_csharp_generator::prepare_member_name_mapping(t_struct* tstruct) { + prepare_member_name_mapping(tstruct, tstruct->get_members(), tstruct->get_name()); +} + +void t_csharp_generator::prepare_member_name_mapping(void* scope, + const vector<t_field*>& members, + const string& structname) { + // begin new scope + member_mapping_scope dummy; + dummy.scope_member = 0; + member_mapping_scopes.push_back(dummy); + member_mapping_scope& active = member_mapping_scopes.back(); + active.scope_member = scope; + + // current C# generator policy: + // - prop names are always rendered with an Uppercase first letter + // - struct names are used as given + std::set<std::string> used_member_names; + vector<t_field*>::const_iterator iter; + + // prevent name conflicts with struct (CS0542 error) + used_member_names.insert(structname); + + // prevent name conflicts with known methods (THRIFT-2942) + used_member_names.insert("Read"); + used_member_names.insert("Write"); + + for (iter = members.begin(); iter != members.end(); ++iter) { + string oldname = (*iter)->get_name(); + string newname = prop_name(*iter, true); + while (true) { + + // new name conflicts with another member + if (used_member_names.find(newname) != used_member_names.end()) { + pverbose("struct %s: member %s conflicts with another member\n", + structname.c_str(), + newname.c_str()); + newname += '_'; + continue; + } + + // add always, this helps us to detect edge cases like + // different spellings ("foo" and "Foo") within the same struct + pverbose("struct %s: member mapping %s => %s\n", + structname.c_str(), + oldname.c_str(), + newname.c_str()); + active.mapping_table[oldname] = newname; + used_member_names.insert(newname); + break; + } + } +} + +std::string t_csharp_generator::prop_name(t_field* tfield, bool suppress_mapping) { + string name(tfield->get_name()); + if (suppress_mapping) { + name[0] = toupper(name[0]); + } else { + name = get_mapped_member_name(name); + } + return name; +} + +string t_csharp_generator::type_name(t_type* ttype, + bool in_container, + bool in_init, + bool in_param, + bool is_required) { + (void)in_init; + while (ttype->is_typedef()) { + ttype = ((t_typedef*)ttype)->get_type(); + } + + if (ttype->is_base_type()) { + return base_type_name((t_base_type*)ttype, in_container, in_param, is_required); + } else if (ttype->is_map()) { + t_map* tmap = (t_map*)ttype; + return "Dictionary<" + type_name(tmap->get_key_type(), true) + ", " + + type_name(tmap->get_val_type(), true) + ">"; + } else if (ttype->is_set()) { + t_set* tset = (t_set*)ttype; + return "THashSet<" + type_name(tset->get_elem_type(), true) + ">"; + } else if (ttype->is_list()) { + t_list* tlist = (t_list*)ttype; + return "List<" + type_name(tlist->get_elem_type(), true) + ">"; + } + + t_program* program = ttype->get_program(); + string postfix = (!is_required && nullable_ && in_param && ttype->is_enum()) ? "?" : ""; + if (program != NULL && program != program_) { + string ns = program->get_namespace("csharp"); + if (!ns.empty()) { + return ns + "." + normalize_name(ttype->get_name()) + postfix; + } + } + + return normalize_name(ttype->get_name()) + postfix; +} + +string t_csharp_generator::base_type_name(t_base_type* tbase, + bool in_container, + bool in_param, + bool is_required) { + (void)in_container; + string postfix = (!is_required && nullable_ && in_param) ? "?" : ""; + switch (tbase->get_base()) { + case t_base_type::TYPE_VOID: + return "void"; + case t_base_type::TYPE_STRING: + if (tbase->is_binary()) { + return "byte[]"; + } else { + return "string"; + } + case t_base_type::TYPE_BOOL: + return "bool" + postfix; + case t_base_type::TYPE_I8: + return "sbyte" + postfix; + case t_base_type::TYPE_I16: + return "short" + postfix; + case t_base_type::TYPE_I32: + return "int" + postfix; + case t_base_type::TYPE_I64: + return "long" + postfix; + case t_base_type::TYPE_DOUBLE: + return "double" + postfix; + default: + throw "compiler error: no C# name for base type " + t_base_type::t_base_name(tbase->get_base()); + } +} + +string t_csharp_generator::declare_field(t_field* tfield, bool init, std::string prefix) { + string result = type_name(tfield->get_type()) + " " + prefix + tfield->get_name(); + if (init) { + t_type* ttype = tfield->get_type(); + while (ttype->is_typedef()) { + ttype = ((t_typedef*)ttype)->get_type(); + } + if (ttype->is_base_type() && field_has_default(tfield)) { + std::ofstream dummy; + result += " = " + render_const_value(dummy, tfield->get_name(), ttype, tfield->get_value()); + } else if (ttype->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)ttype)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + result += " = null"; + break; + case t_base_type::TYPE_BOOL: + result += " = false"; + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + result += " = 0"; + break; + case t_base_type::TYPE_DOUBLE: + result += " = (double)0"; + break; + } + } else if (ttype->is_enum()) { + result += " = (" + type_name(ttype, false, true) + ")0"; + } else if (ttype->is_container()) { + result += " = new " + type_name(ttype, false, true) + "()"; + } else { + result += " = new " + type_name(ttype, false, true) + "()"; + } + } + return result + ";"; +} + +string t_csharp_generator::function_signature(t_function* tfunction, string prefix) { + t_type* ttype = tfunction->get_returntype(); + return type_name(ttype) + " " + normalize_name(prefix + tfunction->get_name()) + "(" + + argument_list(tfunction->get_arglist()) + ")"; +} + +string t_csharp_generator::function_signature_async_begin(t_function* tfunction, string prefix) { + string comma = (tfunction->get_arglist()->get_members().size() > 0 ? ", " : ""); + return "IAsyncResult " + normalize_name(prefix + tfunction->get_name()) + + "(AsyncCallback callback, object state" + comma + argument_list(tfunction->get_arglist()) + + ")"; +} + +string t_csharp_generator::function_signature_async_end(t_function* tfunction, string prefix) { + t_type* ttype = tfunction->get_returntype(); + return type_name(ttype) + " " + normalize_name(prefix + tfunction->get_name()) + + "(IAsyncResult asyncResult)"; +} + +string t_csharp_generator::function_signature_async(t_function* tfunction, string prefix) { + t_type* ttype = tfunction->get_returntype(); + string task = "Task"; + if (!ttype->is_void()) + task += "<" + type_name(ttype) + ">"; + return task + " " + normalize_name(prefix + tfunction->get_name()) + "Async(" + + argument_list(tfunction->get_arglist()) + ")"; +} + +string t_csharp_generator::argument_list(t_struct* tstruct) { + string result = ""; + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + result += ", "; + } + result += type_name((*f_iter)->get_type()) + " " + normalize_name((*f_iter)->get_name()); + } + return result; +} + +string t_csharp_generator::type_to_enum(t_type* type) { + while (type->is_typedef()) { + type = ((t_typedef*)type)->get_type(); + } + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + return "TType.String"; + case t_base_type::TYPE_BOOL: + return "TType.Bool"; + case t_base_type::TYPE_I8: + return "TType.Byte"; + case t_base_type::TYPE_I16: + return "TType.I16"; + case t_base_type::TYPE_I32: + return "TType.I32"; + case t_base_type::TYPE_I64: + return "TType.I64"; + case t_base_type::TYPE_DOUBLE: + return "TType.Double"; + } + } else if (type->is_enum()) { + return "TType.I32"; + } else if (type->is_struct() || type->is_xception()) { + return "TType.Struct"; + } else if (type->is_map()) { + return "TType.Map"; + } else if (type->is_set()) { + return "TType.Set"; + } else if (type->is_list()) { + return "TType.List"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +void t_csharp_generator::generate_csharp_docstring_comment(ostream& out, string contents) { + generate_docstring_comment(out, "/// <summary>\n", "/// ", contents, "/// </summary>\n"); +} + +void t_csharp_generator::generate_csharp_doc(ostream& out, t_field* field) { + if (field->get_type()->is_enum()) { + string combined_message = field->get_doc() + "\n<seealso cref=\"" + + get_enum_class_name(field->get_type()) + "\"/>"; + generate_csharp_docstring_comment(out, combined_message); + } else { + generate_csharp_doc(out, (t_doc*)field); + } +} + +void t_csharp_generator::generate_csharp_doc(ostream& out, t_doc* tdoc) { + if (tdoc->has_doc()) { + generate_csharp_docstring_comment(out, tdoc->get_doc()); + } +} + +void t_csharp_generator::generate_csharp_doc(ostream& out, t_function* tfunction) { + if (tfunction->has_doc()) { + stringstream ps; + const vector<t_field*>& fields = tfunction->get_arglist()->get_members(); + vector<t_field*>::const_iterator p_iter; + for (p_iter = fields.begin(); p_iter != fields.end(); ++p_iter) { + t_field* p = *p_iter; + ps << "\n<param name=\"" << p->get_name() << "\">"; + if (p->has_doc()) { + std::string str = p->get_doc(); + str.erase(std::remove(str.begin(), str.end(), '\n'), + str.end()); // remove the newlines that appear from the parser + ps << str; + } + ps << "</param>"; + } + generate_docstring_comment(out, + "", + "/// ", + "<summary>\n" + tfunction->get_doc() + "</summary>" + ps.str(), + ""); + } +} + +std::string t_csharp_generator::get_enum_class_name(t_type* type) { + string package = ""; + t_program* program = type->get_program(); + if (program != NULL && program != program_) { + package = program->get_namespace("csharp") + "."; + } + return package + type->get_name(); +} + + +THRIFT_REGISTER_GENERATOR( + csharp, + "C#", + " async: Adds Async support using Task.Run.\n" + " wcf: Adds bindings for WCF to generated classes.\n" + " serial: Add serialization support to generated classes.\n" + " nullable: Use nullable types for properties.\n" + " hashcode: Generate a hashcode and equals implementation for classes.\n" + " union: Use new union typing, which includes a static read function for union " + "types.\n") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_d_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_d_generator.cc new file mode 100644 index 000000000..65f4b445e --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_d_generator.cc @@ -0,0 +1,774 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +#include <cassert> + +#include <fstream> +#include <iostream> +#include <set> +#include <sstream> +#include <string> +#include <vector> + +#include <sys/stat.h> + +#include "thrift/platform.h" +#include "thrift/generate/t_oop_generator.h" + +using std::map; +using std::ofstream; +using std::ostream; +using std::ostringstream; +using std::set; +using std::string; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes + +/** + * D code generator. + * + * generate_*() functions are called by the base class to emit code for the + * given entity, print_*() functions write a piece of code to the passed + * stream, and render_*() return a string containing the D representation of + * the passed entity. + */ +class t_d_generator : public t_oop_generator { +public: + t_d_generator(t_program* program, + const std::map<string, string>& parsed_options, + const string& option_string) + : t_oop_generator(program) { + (void)option_string; + std::map<std::string, std::string>::const_iterator iter; + + /* no options yet */ + for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + throw "unknown option d:" + iter->first; + } + + out_dir_base_ = "gen-d"; + } + +protected: + + // D reserved words are suffixed with an underscore + static string suffix_if_reserved(const string& name) { + const bool isIn = std::binary_search(std::begin(d_reserved_words), std::end(d_reserved_words), name); + string ret = isIn ? name + "_" : name; + return ret; + } + + void init_generator() override { + // Make output directory + MKDIR(get_out_dir().c_str()); + + string dir = program_->get_namespace("d"); + string subdir = get_out_dir(); + string::size_type loc; + while ((loc = dir.find(".")) != string::npos) { + subdir = subdir + "/" + dir.substr(0, loc); + MKDIR(subdir.c_str()); + dir = dir.substr(loc + 1); + } + if (!dir.empty()) { + subdir = subdir + "/" + dir; + MKDIR(subdir.c_str()); + } + + package_dir_ = subdir + "/"; + + // Make output file + string f_types_name = package_dir_ + program_name_ + "_types.d"; + f_types_.open(f_types_name.c_str()); + + // Print header + f_types_ << autogen_comment() << "module " << render_package(*program_) << program_name_ + << "_types;" << endl << endl; + + print_default_imports(f_types_); + + // Include type modules from other imported programs. + const vector<t_program*>& includes = program_->get_includes(); + for (auto include : includes) { + f_types_ << "public import " << render_package(*include) << include->get_name() + << "_types;" << endl; + } + if (!includes.empty()) + f_types_ << endl; + } + + void close_generator() override { + // Close output file + f_types_.close(); + } + + void generate_consts(std::vector<t_const*> consts) override { + if (!consts.empty()) { + string f_consts_name = package_dir_ + program_name_ + "_constants.d"; + ofstream_with_content_based_conditional_update f_consts; + f_consts.open(f_consts_name.c_str()); + + f_consts << autogen_comment() << "module " << render_package(*program_) << program_name_ + << "_constants;" << endl << endl; + + print_default_imports(f_consts); + + f_consts << "import " << render_package(*get_program()) << program_name_ << "_types;" << endl + << endl; + + vector<t_const*>::iterator c_iter; + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + this->emit_doc(*c_iter, f_consts); + string name = suffix_if_reserved((*c_iter)->get_name()); + t_type* type = (*c_iter)->get_type(); + indent(f_consts) << "immutable(" << render_type_name(type) << ") " << name << ";" << endl; + } + + f_consts << endl << "shared static this() {" << endl; + indent_up(); + + bool first = true; + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + if (first) { + first = false; + } else { + f_consts << endl; + } + t_type* type = (*c_iter)->get_type(); + indent(f_consts) << suffix_if_reserved((*c_iter)->get_name()) << " = "; + if (!is_immutable_type(type)) { + f_consts << "cast(immutable(" << render_type_name(type) << ")) "; + } + f_consts << render_const_value(type, (*c_iter)->get_value()) << ";" << endl; + } + indent_down(); + indent(f_consts) << "}" << endl; + } + } + + void generate_typedef(t_typedef* ttypedef) override { + this->emit_doc(ttypedef, f_types_); + f_types_ << indent() << "alias " << render_type_name(ttypedef->get_type()) << " " + << ttypedef->get_symbolic() << ";" << endl << endl; + } + + void generate_enum(t_enum* tenum) override { + vector<t_enum_value*> constants = tenum->get_constants(); + + this->emit_doc(tenum, f_types_); + string enum_name = suffix_if_reserved(tenum->get_name()); + f_types_ << indent() << "enum " << enum_name << " {" << endl; + + indent_up(); + + vector<t_enum_value*>::const_iterator c_iter; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + this->emit_doc(*c_iter, f_types_); + indent(f_types_) << suffix_if_reserved((*c_iter)->get_name()); + f_types_ << " = " << (*c_iter)->get_value() << ","; + } + + f_types_ << endl; + indent_down(); + indent(f_types_) << "}" << endl; + + f_types_ << endl; + } + + void generate_struct(t_struct* tstruct) override { + print_struct_definition(f_types_, tstruct, false); + } + + void generate_xception(t_struct* txception) override { + print_struct_definition(f_types_, txception, true); + } + + void generate_service(t_service* tservice) override { + string svc_name = suffix_if_reserved(tservice->get_name()); + + // Service implementation file includes + string f_servicename = package_dir_ + svc_name + ".d"; + ofstream_with_content_based_conditional_update f_service; + f_service.open(f_servicename.c_str()); + f_service << autogen_comment() << "module " << suffix_if_reserved(render_package(*program_)) << svc_name << ";" + << endl << endl; + + print_default_imports(f_service); + + f_service << "import " << suffix_if_reserved(render_package(*get_program())) << program_name_ << "_types;" << endl; + + t_service* extends_service = tservice->get_extends(); + if (extends_service != NULL) { + f_service << "import " << suffix_if_reserved(render_package(*(extends_service->get_program()))) + << suffix_if_reserved(extends_service->get_name()) << ";" << endl; + } + + f_service << endl; + + string extends = ""; + if (tservice->get_extends() != NULL) { + extends = " : " + suffix_if_reserved(render_type_name(tservice->get_extends())); + } + + this->emit_doc(tservice, f_service); + f_service << indent() << "interface " << svc_name << extends << " {" << endl; + indent_up(); + + // Collect all the exception types service methods can throw so we can + // emit the necessary aliases later. + set<t_type*> exception_types; + + // Print the method signatures. + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator fn_iter; + for (fn_iter = functions.begin(); fn_iter != functions.end(); ++fn_iter) { + this->emit_doc(*fn_iter, f_service); + f_service << indent(); + print_function_signature(f_service, *fn_iter); + f_service << ";" << endl; + + const vector<t_field*>& exceptions = (*fn_iter)->get_xceptions()->get_members(); + vector<t_field*>::const_iterator ex_iter; + for (ex_iter = exceptions.begin(); ex_iter != exceptions.end(); ++ex_iter) { + exception_types.insert((*ex_iter)->get_type()); + } + } + + // Alias the exception types into the current scope. + if (!exception_types.empty()) + f_service << endl; + set<t_type*>::const_iterator et_iter; + for (et_iter = exception_types.begin(); et_iter != exception_types.end(); ++et_iter) { + indent(f_service) << "alias " << render_package(*(*et_iter)->get_program()) + << (*et_iter)->get_program()->get_name() << "_types" + << "." << (*et_iter)->get_name() << " " << (*et_iter)->get_name() << ";" + << endl; + } + + // Write the method metadata. + ostringstream meta; + indent_up(); + bool first = true; + for (fn_iter = functions.begin(); fn_iter != functions.end(); ++fn_iter) { + if ((*fn_iter)->get_arglist()->get_members().empty() + && (*fn_iter)->get_xceptions()->get_members().empty() && !(*fn_iter)->is_oneway()) { + continue; + } + + if (first) { + first = false; + } else { + meta << ","; + } + + meta << endl << indent() << "TMethodMeta(`" << suffix_if_reserved((*fn_iter)->get_name()) << "`, " << endl; + indent_up(); + indent(meta) << "["; + + bool first = true; + const vector<t_field*>& params = (*fn_iter)->get_arglist()->get_members(); + vector<t_field*>::const_iterator p_iter; + for (p_iter = params.begin(); p_iter != params.end(); ++p_iter) { + if (first) { + first = false; + } else { + meta << ", "; + } + + meta << "TParamMeta(`" << suffix_if_reserved((*p_iter)->get_name()) << "`, " << (*p_iter)->get_key(); + + t_const_value* cv = (*p_iter)->get_value(); + if (cv != NULL) { + meta << ", q{" << render_const_value((*p_iter)->get_type(), cv) << "}"; + } + meta << ")"; + } + + meta << "]"; + + if (!(*fn_iter)->get_xceptions()->get_members().empty() || (*fn_iter)->is_oneway()) { + meta << "," << endl << indent() << "["; + + bool first = true; + const vector<t_field*>& exceptions = (*fn_iter)->get_xceptions()->get_members(); + vector<t_field*>::const_iterator ex_iter; + for (ex_iter = exceptions.begin(); ex_iter != exceptions.end(); ++ex_iter) { + if (first) { + first = false; + } else { + meta << ", "; + } + + meta << "TExceptionMeta(`" << suffix_if_reserved((*ex_iter)->get_name()) << "`, " + << (*ex_iter)->get_key() << ", `" << (*ex_iter)->get_type()->get_name() << "`)"; + } + + meta << "]"; + } + + if ((*fn_iter)->is_oneway()) { + meta << "," << endl << indent() << "TMethodType.ONEWAY"; + } + + indent_down(); + meta << endl << indent() << ")"; + } + indent_down(); + + string meta_str(meta.str()); + if (!meta_str.empty()) { + f_service << endl << indent() << "enum methodMeta = [" << meta_str << endl << indent() << "];" + << endl; + } + + indent_down(); + indent(f_service) << "}" << endl; + + // Server skeleton generation. + string f_skeletonname = package_dir_ + svc_name + "_server.skeleton.d"; + ofstream_with_content_based_conditional_update f_skeleton; + f_skeleton.open(f_skeletonname.c_str()); + print_server_skeleton(f_skeleton, tservice); + f_skeleton.close(); + } + + void emit_doc(t_doc *doc, std::ostream& out) { + if (!doc->has_doc()) { + return; + } + indent(out) << "/**" << std::endl; + indent_up(); + // No endl -- comments reliably have a newline at the end. + // This is true even for stuff like: + // /** method infos */ void foo(/** huh?*/ 1: i64 stuff) + indent(out) << doc->get_doc(); + indent_down(); + indent(out) << "*/" << std::endl; + } + +private: + /** + * Writes a server skeleton for the passed service to out. + */ + void print_server_skeleton(ostream& out, t_service* tservice) { + string svc_name = suffix_if_reserved(tservice->get_name()); + + out << "/*" << endl + << " * This auto-generated skeleton file illustrates how to build a server. If you" << endl + << " * intend to customize it, you should edit a copy with another file name to " << endl + << " * avoid overwriting it when running the generator again." << endl << " */" << endl + << "module " << render_package(*tservice->get_program()) << svc_name << "_server;" << endl + << endl << "import std.stdio;" << endl << "import thrift.codegen.processor;" << endl + << "import thrift.protocol.binary;" << endl << "import thrift.server.simple;" << endl + << "import thrift.server.transport.socket;" << endl << "import thrift.transport.buffered;" + << endl << "import thrift.util.hashset;" << endl << endl << "import " + << render_package(*tservice->get_program()) << svc_name << ";" << endl << "import " + << render_package(*get_program()) << program_name_ << "_types;" << endl << endl << endl + << "class " << svc_name << "Handler : " << svc_name << " {" << endl; + + indent_up(); + out << indent() << "this() {" << endl << indent() << " // Your initialization goes here." + << endl << indent() << "}" << endl << endl; + + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + out << indent(); + print_function_signature(out, *f_iter); + out << " {" << endl; + + indent_up(); + + out << indent() << "// Your implementation goes here." << endl << indent() << "writeln(\"" + << suffix_if_reserved((*f_iter)->get_name()) << " called\");" << endl; + + t_type* rt = (*f_iter)->get_returntype(); + if (!rt->is_void()) { + indent(out) << "return typeof(return).init;" << endl; + } + + indent_down(); + + out << indent() << "}" << endl << endl; + } + + indent_down(); + out << "}" << endl << endl; + + out << indent() << "void main() {" << endl; + indent_up(); + out << indent() << "auto protocolFactory = new TBinaryProtocolFactory!();" << endl << indent() + << "auto processor = new TServiceProcessor!" << svc_name << "(new " << svc_name + << "Handler);" << endl << indent() << "auto serverTransport = new TServerSocket(9090);" + << endl << indent() << "auto transportFactory = new TBufferedTransportFactory;" << endl + << indent() << "auto server = new TSimpleServer(" << endl << indent() + << " processor, serverTransport, transportFactory, protocolFactory);" << endl << indent() + << "server.serve();" << endl; + indent_down(); + out << "}" << endl; + } + + /** + * Writes the definition of a struct or an exception type to out. + */ + void print_struct_definition(ostream& out, t_struct* tstruct, bool is_exception) { + const vector<t_field*>& members = tstruct->get_members(); + + if (is_exception) { + indent(out) << "class " << suffix_if_reserved(tstruct->get_name()) << " : TException {" << endl; + } else { + indent(out) << "struct " << suffix_if_reserved(tstruct->get_name()) << " {" << endl; + } + indent_up(); + + // Declare all fields. + vector<t_field*>::const_iterator m_iter; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + indent(out) << render_type_name((*m_iter)->get_type()) << " " << suffix_if_reserved((*m_iter)->get_name()) << ";" + << endl; + } + + if (!members.empty()) + indent(out) << endl; + indent(out) << "mixin TStructHelpers!("; + + if (!members.empty()) { + // If there are any fields, construct the TFieldMeta array to pass to + // TStructHelpers. We can't just pass an empty array if not because [] + // doesn't pass the TFieldMeta[] constraint. + out << "["; + indent_up(); + + bool first = true; + vector<t_field*>::const_iterator m_iter; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if (first) { + first = false; + } else { + out << ","; + } + out << endl; + + indent(out) << "TFieldMeta(`" << suffix_if_reserved((*m_iter)->get_name()) << "`, " << (*m_iter)->get_key(); + + t_const_value* cv = (*m_iter)->get_value(); + t_field::e_req req = (*m_iter)->get_req(); + out << ", " << render_req(req); + if (cv != NULL) { + out << ", q{" << render_const_value((*m_iter)->get_type(), cv) << "}"; + } + out << ")"; + } + + indent_down(); + out << endl << indent() << "]"; + } + + out << ");" << endl; + + indent_down(); + indent(out) << "}" << endl << endl; + } + + /** + * Prints the D function signature (including return type) for the given + * method. + */ + void print_function_signature(ostream& out, t_function* fn) { + out << render_type_name(fn->get_returntype()) << " " << suffix_if_reserved(fn->get_name()) << "("; + + const vector<t_field*>& fields = fn->get_arglist()->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + out << ", "; + } + out << render_type_name((*f_iter)->get_type(), true) << " " << suffix_if_reserved((*f_iter)->get_name()); + } + + out << ")"; + } + + /** + * Returns the D representation of value. The result is guaranteed to be a + * single expression; for complex types, immediately called delegate + * literals are used to achieve this. + */ + string render_const_value(t_type* type, t_const_value* value) { + // Resolve any typedefs. + type = get_true_type(type); + + ostringstream out; + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + out << '"' << get_escaped_string(value) << '"'; + break; + case t_base_type::TYPE_BOOL: + out << ((value->get_integer() > 0) ? "true" : "false"); + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + out << "cast(" << render_type_name(type) << ")" << value->get_integer(); + break; + case t_base_type::TYPE_I32: + out << value->get_integer(); + break; + case t_base_type::TYPE_I64: + out << value->get_integer() << "L"; + break; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) { + out << value->get_integer(); + } else { + out << value->get_double(); + } + break; + default: + throw "Compiler error: No const of base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "cast(" << render_type_name(type) << ")" << value->get_integer(); + } else { + out << "{" << endl; + indent_up(); + + indent(out) << render_type_name(type) << " v;" << endl; + if (type->is_struct() || type->is_xception()) { + indent(out) << "v = " << (type->is_xception() ? "new " : "") << render_type_name(type) + << "();" << endl; + + const vector<t_field*>& fields = ((t_struct*)type)->get_members(); + vector<t_field*>::const_iterator f_iter; + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + t_type* field_type = NULL; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field_type = (*f_iter)->get_type(); + } + } + if (field_type == NULL) { + throw "Type error: " + type->get_name() + " has no field " + + v_iter->first->get_string(); + } + string val = render_const_value(field_type, v_iter->second); + indent(out) << "v.set!`" << v_iter->first->get_string() << "`(" << val << ");" << endl; + } + } else if (type->is_map()) { + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string key = render_const_value(ktype, v_iter->first); + string val = render_const_value(vtype, v_iter->second); + indent(out) << "v["; + if (!is_immutable_type(ktype)) { + out << "cast(immutable(" << render_type_name(ktype) << "))"; + } + out << key << "] = " << val << ";" << endl; + } + } else if (type->is_list()) { + t_type* etype = ((t_list*)type)->get_elem_type(); + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string val = render_const_value(etype, *v_iter); + indent(out) << "v ~= " << val << ";" << endl; + } + } else if (type->is_set()) { + t_type* etype = ((t_set*)type)->get_elem_type(); + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string val = render_const_value(etype, *v_iter); + indent(out) << "v ~= " << val << ";" << endl; + } + } else { + throw "Compiler error: Invalid type in render_const_value: " + type->get_name(); + } + indent(out) << "return v;" << endl; + + indent_down(); + indent(out) << "}()"; + } + + return out.str(); + } + + /** + * Returns the D package to which modules for program are written (with a + * trailing dot, if not empty). + */ + string render_package(const t_program& program) const { + string package = program.get_namespace("d"); + if (package.size() == 0) + return ""; + return package + "."; + } + + /** + * Returns the name of the D repesentation of ttype. + * + * If isArg is true, a const reference to the type will be returned for + * structs. + */ + string render_type_name(const t_type* ttype, bool isArg = false) const { + if (ttype->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)ttype)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + return "void"; + case t_base_type::TYPE_STRING: + return "string"; + case t_base_type::TYPE_BOOL: + return "bool"; + case t_base_type::TYPE_I8: + return "byte"; + case t_base_type::TYPE_I16: + return "short"; + case t_base_type::TYPE_I32: + return "int"; + case t_base_type::TYPE_I64: + return "long"; + case t_base_type::TYPE_DOUBLE: + return "double"; + default: + throw "Compiler error: No D type name for base type " + t_base_type::t_base_name(tbase); + } + } + + if (ttype->is_container()) { + t_container* tcontainer = (t_container*)ttype; + if (tcontainer->has_cpp_name()) { + return tcontainer->get_cpp_name(); + } else if (ttype->is_map()) { + t_map* tmap = (t_map*)ttype; + t_type* ktype = tmap->get_key_type(); + + string name = render_type_name(tmap->get_val_type()) + "["; + if (!is_immutable_type(ktype)) { + name += "immutable("; + } + name += render_type_name(ktype); + if (!is_immutable_type(ktype)) { + name += ")"; + } + name += "]"; + return name; + } else if (ttype->is_set()) { + t_set* tset = (t_set*)ttype; + return "HashSet!(" + render_type_name(tset->get_elem_type()) + ")"; + } else if (ttype->is_list()) { + t_list* tlist = (t_list*)ttype; + return render_type_name(tlist->get_elem_type()) + "[]"; + } + } + + if (ttype->is_struct() && isArg) { + return "ref const(" + ttype->get_name() + ")"; + } else { + return ttype->get_name(); + } + } + + /** + * Returns the D TReq enum member corresponding to req. + */ + string render_req(t_field::e_req req) const { + switch (req) { + case t_field::T_OPT_IN_REQ_OUT: + return "TReq.OPT_IN_REQ_OUT"; + case t_field::T_OPTIONAL: + return "TReq.OPTIONAL"; + case t_field::T_REQUIRED: + return "TReq.REQUIRED"; + default: { + std::stringstream ss; + ss << "Compiler error: Invalid requirement level " << req; + throw ss.str(); + } + } + } + + /** + * Writes the default list of imports (which are written to every generated + * module) to f. + */ + void print_default_imports(ostream& out) { + indent(out) << "import thrift.base;" << endl << "import thrift.codegen.base;" << endl + << "import thrift.util.hashset;" << endl << endl; + } + + /** + * Returns whether type is »intrinsically immutable«, in the sense that + * a value of that type is implicitly castable to immutable(type), and it is + * allowed for AA keys without an immutable() qualifier. + */ + bool is_immutable_type(t_type* type) const { + t_type* ttype = get_true_type(type); + return ttype->is_base_type() || ttype->is_enum(); + } + + /* + * File streams, stored here to avoid passing them as parameters to every + * function. + */ + ofstream_with_content_based_conditional_update f_types_; + ofstream_with_content_based_conditional_update f_header_; + + string package_dir_; + + protected: + static vector<string> d_reserved_words; + +}; + +vector<string> t_d_generator::d_reserved_words = { + // The keywords are extracted from https://dlang.org/spec/lex.html + // and sorted for use with std::binary_search + "__FILE_FULL_PATH__", "__FILE__", "__FUNCTION__", "__LINE__", "__MODULE__", + "__PRETTY_FUNCTION__", "__gshared", "__parameters", "__traits", "__vector", + "abstract", "alias", "align", "asm", "assert", "auto", "body", "bool", + "break", "byte", "case", "cast", "catch", "cdouble", "cent", "cfloat", + "char", "class", "const", "continue", "creal", "dchar", "debug", "default", + "delegate", "delete", "deprecated", "do", "double", "else", "enum", + "export", "extern", "false", "final", "finally", "float", "for", "foreach", + "foreach_reverse", "function", "goto", "idouble", "if", "ifloat", "immutable", + "import", "in", "inout", "int", "interface", "invariant", "ireal", "is", + "lazy", "long", "macro ", "mixin", "module", "new", "nothrow", "null", "out", + "override", "package", "pragma", "private", "protected", "public", "pure", + "real", "ref", "return", "scope", "shared", "short", "static", "struct", + "super", "switch", "synchronized", "template", "this", "throw", "true", "try", + "typeid", "typeof", "ubyte", "ucent", "uint", "ulong", "union", "unittest", + "ushort", "version", "void", "wchar", "while", "with" +}; + +THRIFT_REGISTER_GENERATOR(d, "D", "") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_dart_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_dart_generator.cc new file mode 100644 index 000000000..fbbb9e6d3 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_dart_generator.cc @@ -0,0 +1,2514 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <sstream> +#include <string> +#include <fstream> +#include <iostream> +#include <vector> +#include <cctype> + +#include <sys/stat.h> +#include <stdexcept> + +#include "thrift/platform.h" +#include "thrift/generate/t_oop_generator.h" + +using std::map; +using std::ostream; +using std::ostringstream; +using std::string; +using std::stringstream; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes +static const string endl2 = "\n\n"; + +/** + * Use the current Thrift version for static libraries. When releasing, update + * the version in these files. + * - lib/dart/pubspec.yaml + * - test/dart/test_client/pubspec.yaml + * - tutorial/dart/client/pubspec.yaml + * - tutorial/dart/console_client/pubspec.yaml + * - tutorial/dart/server/pubspec.yaml + * See https://thrift.apache.org/docs/committers/HowToVersion + */ +static const string dart_thrift_version = THRIFT_VERSION; + +/* forward declarations */ +string initial_caps_to_underscores(string name); + +/** + * Dart code generator + * + */ +class t_dart_generator : public t_oop_generator { +public: + t_dart_generator(t_program* program, + const std::map<std::string, std::string>& parsed_options, + const std::string& option_string) + : t_oop_generator(program) { + (void)option_string; + std::map<std::string, std::string>::const_iterator iter; + + library_name_ = ""; + library_prefix_ = ""; + package_prefix_ = ""; + pubspec_lib_ = ""; + for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + if( iter->first.compare("library_name") == 0) { + library_name_ = (iter->second); + } else if( iter->first.compare("library_prefix") == 0) { + library_prefix_ = (iter->second) + "."; + package_prefix_ = replace_all(library_prefix_, ".", "/"); + } else if( iter->first.compare("pubspec_lib") == 0) { + pubspec_lib_ = (iter->second); + } else { + throw "unknown option dart:" + iter->first; + } + } + + out_dir_base_ = "gen-dart"; + } + + void scope_up(std::ostream& out, std::string prefix=" ") { + out << prefix << "{" << endl; + indent_up(); + } + + void scope_down(std::ostream& out, std::string postfix=endl) { + indent_down(); + indent(out) << "}" << postfix; + } + + string replace_all(string contents, string search, string repl) { + string str(contents); + + size_t slen = search.length(); + size_t rlen = repl.length(); + size_t incr = (rlen > 0) ? rlen : 1; + + if (slen > 0) { + size_t found = str.find(search); + while ((found != string::npos) && (found < str.length())) { + str.replace(found, slen, repl); + found = str.find(search, found + incr); + } + } + + return str; + } + + + /** + * Init and close methods + */ + + void init_generator() override; + void close_generator() override; + + void export_class_to_library(string file_name, string class_name); + + void generate_dart_library(); + void generate_dart_pubspec(); + + void generate_consts(std::vector<t_const*> consts) override; + + /** + * Program-level generation functions + */ + + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_struct(t_struct* tstruct) override; + void generate_xception(t_struct* txception) override; + void generate_service(t_service* tservice) override; + + void print_const_value(std::ostream& out, + std::string name, + t_type* type, + t_const_value* value, + bool in_static, + bool defval = false); + std::string render_const_value(ostream& out, + std::string name, + t_type* type, + t_const_value* value); + + /** + * Service-level generation functions + */ + + void generate_dart_struct(t_struct* tstruct, bool is_exception); + + void generate_dart_struct_definition(std::ostream& out, + t_struct* tstruct, + bool is_xception = false, + bool is_result = false, + string export_file_name = ""); + void generate_dart_struct_reader(std::ostream& out, t_struct* tstruct); + void generate_dart_validator(std::ostream& out, t_struct* tstruct); + void generate_dart_struct_result_writer(std::ostream& out, t_struct* tstruct); + void generate_dart_struct_writer(std::ostream& out, t_struct* tstruct); + void generate_dart_struct_tostring(std::ostream& out, t_struct* tstruct); + std::string get_dart_type_string(t_type* type); + void generate_generic_field_getters(std::ostream& out, t_struct* tstruct); + void generate_generic_field_setters(std::ostream& out, t_struct* tstruct); + void generate_generic_isset_method(std::ostream& out, t_struct* tstruct); + void generate_dart_bean_boilerplate(std::ostream& out, t_struct* tstruct); + + void generate_function_helpers(t_function* tfunction); + std::string init_value(t_field* tfield); + std::string get_cap_name(std::string name); + std::string get_member_name(std::string name); + std::string get_args_class_name(std::string name); + std::string get_result_class_name(std::string name); + std::string get_file_name(std::string name); + std::string get_constants_class_name(std::string name); + std::string generate_isset_check(t_field* field); + std::string generate_isset_check(std::string field); + void generate_isset_set(ostream& out, t_field* field); + + void generate_service_interface(t_service* tservice); + void generate_service_helpers(t_service* tservice); + void generate_service_client(t_service* tservice); + void generate_service_server(t_service* tservice); + void generate_process_function(t_service* tservice, t_function* tfunction); + + /** + * Serialization constructs + */ + + void generate_deserialize_field(std::ostream& out, t_field* tfield, std::string prefix = ""); + + void generate_deserialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); + + void generate_deserialize_container(std::ostream& out, t_type* ttype, std::string prefix = ""); + + void generate_deserialize_set_element(std::ostream& out, t_set* tset, std::string prefix = ""); + + void generate_deserialize_map_element(std::ostream& out, t_map* tmap, std::string prefix = ""); + + void generate_deserialize_list_element(std::ostream& out, + t_list* tlist, + std::string prefix = ""); + + void generate_serialize_field(std::ostream& out, t_field* tfield, std::string prefix = ""); + + void generate_serialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); + + void generate_serialize_container(std::ostream& out, t_type* ttype, std::string prefix = ""); + + void generate_serialize_map_element(std::ostream& out, + t_map* tmap, + std::string iter, + std::string map); + + void generate_serialize_set_element(std::ostream& out, t_set* tmap, std::string iter); + + void generate_serialize_list_element(std::ostream& out, t_list* tlist, std::string iter); + + void generate_dart_doc(std::ostream& out, t_doc* tdoc); + + void generate_dart_doc(std::ostream& out, t_function* tdoc); + + /** + * Helper rendering functions + */ + + std::string find_library_name(t_program* program); + std::string dart_library(string file_name); + std::string service_imports(); + std::string dart_thrift_imports(); + std::string type_name(t_type* ttype); + std::string base_type_name(t_base_type* tbase); + std::string declare_field(t_field* tfield, bool init = false); + std::string function_signature(t_function* tfunction); + std::string argument_list(t_struct* tstruct); + std::string type_to_enum(t_type* ttype); + std::string get_ttype_class_name(t_type* ttype); + + bool type_can_be_null(t_type* ttype) { + ttype = get_true_type(ttype); + + return ttype->is_container() || ttype->is_struct() || ttype->is_xception() + || ttype->is_string(); + } + + vector<std::string> split(const string& s, char delim) { + vector<std::string> elems; + stringstream ss(s); + string item; + while (getline(ss, item, delim)) { + elems.push_back(item); + } + return elems; + } + + std::string constant_name(std::string name); + +private: + ofstream_with_content_based_conditional_update f_service_; + + std::string library_name_; + std::string library_prefix_; + std::string package_prefix_; + std::string pubspec_lib_; + + std::string base_dir_; + std::string src_dir_; + std::string library_exports_; +}; + +/** + * Prepares for file generation by opening up the necessary file output + * streams. + * + * @param tprogram The program to generate + */ +void t_dart_generator::init_generator() { + MKDIR(get_out_dir().c_str()); + + if (library_name_.empty()) { + library_name_ = find_library_name(program_); + } + + string subdir = get_out_dir() + "/" + library_name_; + MKDIR(subdir.c_str()); + base_dir_ = subdir; + + if (library_prefix_.empty()) { + subdir += "/lib"; + MKDIR(subdir.c_str()); + subdir += "/src"; + MKDIR(subdir.c_str()); + src_dir_ = subdir; + } else { + src_dir_ = base_dir_; + } +} + +string t_dart_generator::find_library_name(t_program* program) { + string name = program->get_namespace("dart"); + if (name.empty()) { + name = program->get_name(); + } + name = replace_all(name, ".", "_"); + name = replace_all(name, "-", "_"); + return name; +} + +/** + * The Dart library + * + * @return String of the library, e.g. "library myservice;" + */ +string t_dart_generator::dart_library(string file_name) { + string out = "library " + library_prefix_ + library_name_; + if (!file_name.empty()) { + if (library_prefix_.empty()) { + out += ".src." + file_name; + } else { + out += "." + file_name; + } + } + return out + ";\n"; +} + +/** + * Prints imports for services + * + * @return List of imports for services + */ +string t_dart_generator::service_imports() { + return "import 'dart:async';" + endl; +} + +/** + * Prints standard dart imports + * + * @return List of imports necessary for thrift + */ +string t_dart_generator::dart_thrift_imports() { + string imports = "import 'dart:typed_data' show Uint8List;" + endl + + "import 'package:thrift/thrift.dart';" + endl; + + // add import for this library + if (package_prefix_.empty()) { + imports += "import 'package:" + library_name_ + "/" + library_name_ + ".dart';" + endl; + } else { + imports += "import 'package:" + package_prefix_ + library_name_ + ".dart';" + endl; + } + + // add imports for included thrift files + const vector<t_program*>& includes = program_->get_includes(); + for (auto include : includes) { + string include_name = find_library_name(include); + string named_import = "t_" + include_name; + if (package_prefix_.empty()) { + imports += "import 'package:" + include_name + "/" + include_name + ".dart' as " + named_import + ";" + endl; + } else { + imports += "import 'package:" + package_prefix_ + include_name + ".dart' as " + named_import + ";" + endl; + } + } + + return imports; +} + +/** + * Not used + */ +void t_dart_generator::close_generator() { + generate_dart_library(); + + if (library_prefix_.empty()) { + generate_dart_pubspec(); + } +} + +void t_dart_generator::generate_dart_library() { + string f_library_name; + if (library_prefix_.empty()) { + f_library_name = base_dir_ + "/lib/" + library_name_ + ".dart"; + } else { + f_library_name = get_out_dir() + "/" + library_name_ + ".dart"; + } + + ofstream_with_content_based_conditional_update f_library; + f_library.open(f_library_name.c_str()); + + f_library << autogen_comment() << endl; + f_library << "library " << library_prefix_ << library_name_ << ";" << endl2; + f_library << library_exports_; + + f_library.close(); +} + +void t_dart_generator::export_class_to_library(string file_name, string class_name) { + string subdir; + if (library_prefix_.empty()) { + subdir = "src"; + } else { + subdir = library_name_; + } + library_exports_ += "export '" + subdir + "/" + file_name + ".dart' show " + class_name + ";" + endl; +} + +void t_dart_generator::generate_dart_pubspec() { + string f_pubspec_name = base_dir_ + "/pubspec.yaml"; + ofstream_with_content_based_conditional_update f_pubspec; + f_pubspec.open(f_pubspec_name.c_str()); + + indent(f_pubspec) << "name: " << library_name_ << endl; + indent(f_pubspec) << "version: 0.0.1" << endl; + indent(f_pubspec) << "description: Autogenerated by Thrift Compiler" << endl; + f_pubspec << endl; + + indent(f_pubspec) << "environment:" << endl; + indent_up(); + indent(f_pubspec) << "sdk: '>=1.24.3 <3.0.0'" << endl; + indent_down(); + f_pubspec << endl; + + indent(f_pubspec) << "dependencies:" << endl; + indent_up(); + + if (pubspec_lib_.empty()) { + // default to relative path within working directory, which works for tests + indent(f_pubspec) << "thrift: # ^" << dart_thrift_version << endl; + indent_up(); + indent(f_pubspec) << "path: ../../../../lib/dart" << endl; + indent_down(); + } else { + const vector<std::string> lines = split(pubspec_lib_, '|'); + for (const auto & line : lines) { + indent(f_pubspec) << line << endl; + } + } + + // add included thrift files as dependencies + const vector<t_program*>& includes = program_->get_includes(); + for (auto include : includes) { + string include_name = find_library_name(include); + indent(f_pubspec) << include_name << ":" << endl; + indent_up(); + indent(f_pubspec) << "path: ../" << include_name << endl; + indent_down(); + } + + indent_down(); + f_pubspec << endl; + + f_pubspec.close(); +} + +/** + * Not used + * + * @param ttypedef The type definition + */ +void t_dart_generator::generate_typedef(t_typedef* ttypedef) { + (void)ttypedef; +} + +/** + * Enums are a class with a set of static constants. + * + * @param tenum The enumeration + */ +void t_dart_generator::generate_enum(t_enum* tenum) { + // Make output file + string file_name = get_file_name(tenum->get_name()); + + string f_enum_name = src_dir_ + "/" + file_name + ".dart"; + ofstream_with_content_based_conditional_update f_enum; + f_enum.open(f_enum_name.c_str()); + + // Comment and add library + f_enum << autogen_comment() << dart_library(file_name) << endl; + + string class_name = tenum->get_name(); + export_class_to_library(file_name, class_name); + f_enum << "class " << class_name; + scope_up(f_enum); + + vector<t_enum_value*> constants = tenum->get_constants(); + vector<t_enum_value*>::iterator c_iter; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + int value = (*c_iter)->get_value(); + indent(f_enum) << "static const int " << (*c_iter)->get_name() << " = " << value << ";" + << endl; + } + + // Create a static Set with all valid values for this enum + f_enum << endl; + + indent(f_enum) << "static final Set<int> VALID_VALUES = new Set.from([" << endl; + indent_up(); + bool firstValue = true; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + // populate set + indent(f_enum) << (firstValue ? "" : ", "); + f_enum << (*c_iter)->get_name() << endl; + firstValue = false; + } + indent_down(); + indent(f_enum) << "]);" << endl; + + indent(f_enum) << "static final Map<int, String> VALUES_TO_NAMES = {" << endl; + indent_up(); + firstValue = true; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + indent(f_enum) << (firstValue ? "" : ", "); + f_enum << (*c_iter)->get_name() << ": '" << (*c_iter)->get_name() << "'" << endl; + firstValue = false; + } + indent_down(); + indent(f_enum) << "};" << endl; + + scope_down(f_enum); // end class + + f_enum.close(); +} + +/** + * Generates a class that holds all the constants. + */ +void t_dart_generator::generate_consts(std::vector<t_const*> consts) { + if (consts.empty()) { + return; + } + + string class_name = get_constants_class_name(program_name_); + string file_name = get_file_name(class_name); + + string f_consts_name = src_dir_ + "/" + file_name + ".dart"; + ofstream_with_content_based_conditional_update f_consts; + f_consts.open(f_consts_name.c_str()); + + // Print header + f_consts << autogen_comment() << dart_library(file_name) << endl; + f_consts << dart_thrift_imports() << endl; + + export_class_to_library(file_name, class_name); + indent(f_consts) << "class " << class_name; + scope_up(f_consts); + + vector<t_const*>::iterator c_iter; + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + print_const_value(f_consts, + (*c_iter)->get_name(), + (*c_iter)->get_type(), + (*c_iter)->get_value(), + false); + f_consts << endl; + } + + scope_down(f_consts); + + f_consts.close(); +} + +void t_dart_generator::print_const_value(std::ostream& out, + string name, + t_type* type, + t_const_value* value, + bool in_static, + bool defval) { + type = get_true_type(type); + + indent(out); + if (!defval) { + out << (in_static ? "var " : "static final "); + } + if (type->is_base_type()) { + if (!defval) { + out << type_name(type) << " "; + } + string v2 = render_const_value(out, name, type, value); + out << name; + out << " = " << v2 << ";" << endl << endl; + } else if (type->is_enum()) { + if (!defval) { + out << type_name(type) << " "; + } + out << name; + out << " = " << value->get_integer() << ";" << endl << endl; + } else if (type->is_struct() || type->is_xception()) { + const vector<t_field*>& fields = ((t_struct*)type)->get_members(); + vector<t_field*>::const_iterator f_iter; + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + out << type_name(type) << " " << name << " = new " << type_name(type) << "()"; + indent_up(); + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + t_type* field_type = NULL; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field_type = (*f_iter)->get_type(); + } + } + if (field_type == NULL) { + throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string(); + } + string val = render_const_value(out, name, field_type, v_iter->second); + out << endl; + indent(out) << ".." << v_iter->first->get_string() << " = " << val; + } + indent_down(); + out << ";" << endl; + } else if (type->is_map()) { + if (!defval) { + out << type_name(type) << " "; + } + out << name << " ="; + scope_up(out); + + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string key = render_const_value(out, name, ktype, v_iter->first); + string val = render_const_value(out, name, vtype, v_iter->second); + indent(out) << key << ": " << val << "," << endl; + } + scope_down(out, ";" + endl); + + out << endl; + } else if (type->is_list() || type->is_set()) { + if (!defval) { + out << type_name(type) << " "; + } + out << name << " = "; + t_type* etype; + if (type->is_list()) { + out << "[" << endl; + etype = ((t_list*)type)->get_elem_type(); + } else { + out << "new " << type_name(type) << ".from([" << endl; + etype = ((t_set*)type)->get_elem_type(); + } + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + + indent_up(); + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string val = render_const_value(out, name, etype, *v_iter); + indent(out) << val << "," << endl; + } + indent_down(); + + if (type->is_list()) { + indent(out) << "];" << endl; + } else { + indent(out) << "]);" << endl; + } + + } else { + throw "compiler error: no const of type " + type->get_name(); + } +} + +string t_dart_generator::render_const_value(ostream& out, + string name, + t_type* type, + t_const_value* value) { + (void)name; + type = get_true_type(type); + std::ostringstream render; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + render << "'" << get_escaped_string(value) << "'"; + break; + case t_base_type::TYPE_BOOL: + render << ((value->get_integer() > 0) ? "true" : "false"); + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + render << value->get_integer(); + break; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) { + render << value->get_integer(); + } else { + render << value->get_double(); + } + break; + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + render << value->get_integer(); + } else { + string t = tmp("tmp"); + print_const_value(out, t, type, value, true); + out << endl; + render << t; + } + + return render.str(); +} + +/** + * Generates a struct definition for a thrift data type. This is a class + * with data members, read(), write(), and an inner Isset class. + * + * @param tstruct The struct definition + */ +void t_dart_generator::generate_struct(t_struct* tstruct) { + generate_dart_struct(tstruct, false); +} + +/** + * Exceptions are structs, but they inherit from Exception + * + * @param tstruct The struct definition + */ +void t_dart_generator::generate_xception(t_struct* txception) { + generate_dart_struct(txception, true); +} + +/** + * Dart struct definition. + * + * @param tstruct The struct definition + */ +void t_dart_generator::generate_dart_struct(t_struct* tstruct, bool is_exception) { + string file_name = get_file_name(tstruct->get_name()); + string f_struct_name = src_dir_ + "/" + file_name + ".dart"; + ofstream_with_content_based_conditional_update f_struct; + f_struct.open(f_struct_name.c_str()); + + f_struct << autogen_comment() << dart_library(file_name) << endl; + + string imports; + + f_struct << dart_thrift_imports() << endl; + + generate_dart_struct_definition(f_struct, tstruct, is_exception, false, file_name); + + f_struct.close(); +} + +/** + * Dart struct definition. This has various parameters, as it could be + * generated standalone or inside another class as a helper. If it + * is a helper than it is a static class. + * + * @param tstruct The struct definition + * @param is_exception Is this an exception? + * @param in_class If inside a class, needs to be static class + * @param is_result If this is a result it needs a different writer + */ +void t_dart_generator::generate_dart_struct_definition(ostream& out, + t_struct* tstruct, + bool is_exception, + bool is_result, + string export_file_name) { + generate_dart_doc(out, tstruct); + + string class_name = tstruct->get_name(); + if (!export_file_name.empty()) { + export_class_to_library(export_file_name, class_name); + } + indent(out) << "class " << class_name << " "; + + out << "implements TBase"; + if (is_exception) { + out << ", Exception "; + } + scope_up(out); + + indent(out) << "static final TStruct _STRUCT_DESC = new TStruct(\"" << class_name + << "\");" << endl; + + // Members are public for -dart, private for -dartbean + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + indent(out) << "static final TField _" << constant_name((*m_iter)->get_name()) + << "_FIELD_DESC = new TField(\"" << (*m_iter)->get_name() << "\", " + << type_to_enum((*m_iter)->get_type()) << ", " << (*m_iter)->get_key() << ");" + << endl; + } + + out << endl; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + generate_dart_doc(out, *m_iter); + indent(out) << type_name((*m_iter)->get_type()) + " _" + << get_member_name((*m_iter)->get_name()) << init_value(*m_iter) << ";" << endl; + + indent(out) << "static const int " << upcase_string((*m_iter)->get_name()) + << " = " << (*m_iter)->get_key() << ";" << endl; + } + + out << endl; + + // Inner Isset class + if (members.size() > 0) { + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if (!type_can_be_null((*m_iter)->get_type())) { + string field_name = get_member_name((*m_iter)->get_name()); + indent(out) << "bool __isset_" << field_name << " = false;" << endl; + } + } + } + + out << endl; + + // Default constructor + indent(out) << tstruct->get_name() << "()"; + scope_up(out); + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* t = get_true_type((*m_iter)->get_type()); + if ((*m_iter)->get_value() != NULL) { + print_const_value(out, + "this." + get_member_name((*m_iter)->get_name()), + t, + (*m_iter)->get_value(), + true, + true); + } + } + scope_down(out); + out << endl; + + generate_dart_bean_boilerplate(out, tstruct); + generate_generic_field_getters(out, tstruct); + generate_generic_field_setters(out, tstruct); + generate_generic_isset_method(out, tstruct); + + generate_dart_struct_reader(out, tstruct); + if (is_result) { + generate_dart_struct_result_writer(out, tstruct); + } else { + generate_dart_struct_writer(out, tstruct); + } + generate_dart_struct_tostring(out, tstruct); + generate_dart_validator(out, tstruct); + scope_down(out); + out << endl; +} + +/** + * Generates a function to read all the fields of the struct. + * + * @param tstruct The struct definition + */ +void t_dart_generator::generate_dart_struct_reader(ostream& out, t_struct* tstruct) { + indent(out) << "read(TProtocol iprot)"; + scope_up(out); + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + // Declare stack tmp variables and read struct header + indent(out) << "TField field;" << endl; + indent(out) << "iprot.readStructBegin();" << endl; + + // Loop over reading in fields + indent(out) << "while (true)"; + scope_up(out); + + // Read beginning field marker + indent(out) << "field = iprot.readFieldBegin();" << endl; + + // Check for field STOP marker and break + indent(out) << "if (field.type == TType.STOP)"; + scope_up(out); + indent(out) << "break;" << endl; + scope_down(out); + + // Switch statement on the field we are reading + indent(out) << "switch (field.id)"; + scope_up(out); + + // Generate deserialization code for known cases + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + indent(out) << "case " << upcase_string((*f_iter)->get_name()) << ":" << endl; + indent_up(); + + indent(out) << "if (field.type == " << type_to_enum((*f_iter)->get_type()) << ")"; + scope_up(out); + + generate_deserialize_field(out, *f_iter, "this."); + generate_isset_set(out, *f_iter); + + scope_down(out, " else"); + scope_up(out); + indent(out) << "TProtocolUtil.skip(iprot, field.type);" << endl; + scope_down(out); + + indent(out) << "break;" << endl; + indent_down(); + } + + // In the default case we skip the field + indent(out) << "default:" << endl; + indent_up(); + indent(out) << "TProtocolUtil.skip(iprot, field.type);" << endl; + indent(out) << "break;" << endl; + indent_down(); + + scope_down(out); + + // Read field end marker + indent(out) << "iprot.readFieldEnd();" << endl; + + scope_down(out); + + indent(out) << "iprot.readStructEnd();" << endl2; + + // in non-beans style, check for required fields of primitive type + // (which can be checked here but not in the general validate method) + indent(out) << "// check for required fields of primitive type, which can't be " + "checked in the validate method" << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_REQUIRED && !type_can_be_null((*f_iter)->get_type())) { + string field_name = get_member_name((*f_iter)->get_name()); + indent(out) << "if (!__isset_" << field_name << ")"; + scope_up(out); + indent(out) << " throw new TProtocolError(TProtocolErrorType.UNKNOWN, \"Required field '" + << field_name + << "' was not found in serialized data! Struct: \" + toString());" << endl; + scope_down(out, endl2); + } + } + + // performs various checks (e.g. check that all required fields are set) + indent(out) << "validate();" << endl; + + scope_down(out, endl2); +} + +// generates dart method to perform various checks +// (e.g. check that all required fields are set) +void t_dart_generator::generate_dart_validator(ostream& out, t_struct* tstruct) { + indent(out) << "validate()"; + scope_up(out); + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + indent(out) << "// check for required fields" << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_REQUIRED) { + string field_name = get_member_name((*f_iter)->get_name()); + if (type_can_be_null((*f_iter)->get_type())) { + indent(out) << "if (" << field_name << " == null)"; + scope_up(out); + indent(out) << "throw new TProtocolError(TProtocolErrorType.UNKNOWN, \"Required field '" + << field_name << "' was not present! Struct: \" + toString());" + << endl; + scope_down(out); + } else { + indent(out) << "// alas, we cannot check '" << field_name + << "' because it's a primitive and you chose the non-beans generator." << endl; + } + } + } + + // check that fields of type enum have valid values + indent(out) << "// check that fields of type enum have valid values" << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = (*f_iter); + t_type* type = field->get_type(); + // if field is an enum, check that its value is valid + if (type->is_enum()) { + string field_name = get_member_name(field->get_name()); + indent(out) << "if (" << generate_isset_check(field) << " && !" << get_ttype_class_name(type) + << ".VALID_VALUES.contains(" << field_name << "))"; + scope_up(out); + indent(out) << "throw new TProtocolError(TProtocolErrorType.UNKNOWN, \"The field '" + << field_name << "' has been assigned the invalid value " + << "$" << field_name << "\");" << endl; + scope_down(out); + } + } + + scope_down(out, endl2); +} + +/** + * Generates a function to write all the fields of the struct + * + * @param tstruct The struct definition + */ +void t_dart_generator::generate_dart_struct_writer(ostream& out, t_struct* tstruct) { + out << indent() << "write(TProtocol oprot)"; + scope_up(out); + + const vector<t_field*>& fields = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator f_iter; + + // performs various checks (e.g. check that all required fields are set) + indent(out) << "validate();" << endl2; + + indent(out) << "oprot.writeStructBegin(_STRUCT_DESC);" << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + string field_name = get_member_name((*f_iter)->get_name()); + bool could_be_unset = (*f_iter)->get_req() == t_field::T_OPTIONAL; + if (could_be_unset) { + indent(out) << "if (" << generate_isset_check(*f_iter) << ")"; + scope_up(out); + } + bool null_allowed = type_can_be_null((*f_iter)->get_type()); + if (null_allowed) { + indent(out) << "if (this." << field_name << " != null)"; + scope_up(out); + } + + indent(out) << "oprot.writeFieldBegin(_" << constant_name((*f_iter)->get_name()) + << "_FIELD_DESC);" << endl; + + // Write field contents + generate_serialize_field(out, *f_iter, "this."); + + // Write field closer + indent(out) << "oprot.writeFieldEnd();" << endl; + + if (null_allowed) { + scope_down(out); + } + if (could_be_unset) { + scope_down(out); + } + } + // Write the struct map + indent(out) << "oprot.writeFieldStop();" << endl << indent() << "oprot.writeStructEnd();" + << endl; + + scope_down(out, endl2); +} + +/** + * Generates a function to write all the fields of the struct, + * which is a function result. These fields are only written + * if they are set in the Isset array, and only one of them + * can be set at a time. + * + * @param tstruct The struct definition + */ +void t_dart_generator::generate_dart_struct_result_writer(ostream& out, t_struct* tstruct) { + indent(out) << "write(TProtocol oprot)"; + scope_up(out); + + const vector<t_field*>& fields = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator f_iter; + + indent(out) << "oprot.writeStructBegin(_STRUCT_DESC);" << endl2; + + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + indent(out) << "if "; + } else { + out << " else if "; + } + + out << "(this." << generate_isset_check(*f_iter) << ")"; + scope_up(out); + + indent(out) << "oprot.writeFieldBegin(_" << constant_name((*f_iter)->get_name()) + << "_FIELD_DESC);" << endl; + + // Write field contents + generate_serialize_field(out, *f_iter, "this."); + + // Write field closer + indent(out) << "oprot.writeFieldEnd();" << endl; + + scope_down(out, ""); + } + out << endl; + + // Write the struct map + indent(out) << "oprot.writeFieldStop();" << endl << indent() + << "oprot.writeStructEnd();" << endl; + + scope_down(out, endl2); +} + +void t_dart_generator::generate_generic_field_getters(std::ostream& out, + t_struct* tstruct) { + // create the getter + indent(out) << "getFieldValue(int fieldID)"; + scope_up(out); + + indent(out) << "switch (fieldID)"; + scope_up(out); + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = *f_iter; + std::string field_name = get_member_name(field->get_name()); + + indent(out) << "case " << upcase_string(field_name) << ":" << endl; + indent_up(); + indent(out) << "return this." << field_name << ";" << endl; + indent_down(); + } + + indent(out) << "default:" << endl; + indent_up(); + indent(out) << "throw new ArgumentError(\"Field $fieldID doesn't exist!\");" << endl; + indent_down(); + + scope_down(out); // switch + scope_down(out, endl2); // method +} + +void t_dart_generator::generate_generic_field_setters(std::ostream& out, + t_struct* tstruct) { + + // create the setter + indent(out) << "setFieldValue(int fieldID, Object value)"; + scope_up(out); + + indent(out) << "switch (fieldID)"; + scope_up(out); + + // build up the bodies of both the getter and setter at once + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = *f_iter; + std::string field_name = get_member_name(field->get_name()); + + indent(out) << "case " << upcase_string(field_name) << ":" << endl; + indent_up(); + + indent(out) << "if (value == null)"; + scope_up(out); + indent(out) << "unset" << get_cap_name(field_name) << "();" << endl; + + scope_down(out, " else"); + scope_up(out); + indent(out) << "this." << field_name << " = value;" << endl; + scope_down(out); + + indent(out) << "break;" << endl; + + indent_down(); + out << endl; + } + + indent(out) << "default:" << endl; + indent_up(); + indent(out) << "throw new ArgumentError(\"Field $fieldID doesn't exist!\");" << endl; + indent_down(); + + scope_down(out); // switch + scope_down(out, endl2); // method +} + +// Creates a generic isSet method that takes the field number as argument +void t_dart_generator::generate_generic_isset_method(std::ostream& out, t_struct* tstruct) { + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + // create the isSet method + indent(out) << "// Returns true if field corresponding to fieldID is set (has been assigned a " + "value) and false otherwise" << endl; + indent(out) << "bool isSet(int fieldID)"; + scope_up(out); + + indent(out) << "switch (fieldID)"; + scope_up(out); + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = *f_iter; + indent(out) << "case " << upcase_string(field->get_name()) << ":" << endl; + indent_up(); + indent(out) << "return " << generate_isset_check(field) << ";" << endl; + indent_down(); + } + + indent(out) << "default:" << endl; + indent_up(); + indent(out) << "throw new ArgumentError(\"Field $fieldID doesn't exist!\");" << endl; + indent_down(); + + scope_down(out); // switch + scope_down(out, endl2); // method +} + +/** + * Generates a set of Dart Bean boilerplate functions (setters, getters, etc.) + * for the given struct. + * + * @param tstruct The struct definition + */ +void t_dart_generator::generate_dart_bean_boilerplate(ostream& out, + t_struct* tstruct) { + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = *f_iter; + t_type* type = get_true_type(field->get_type()); + std::string field_name = get_member_name(field->get_name()); + std::string cap_name = get_cap_name(field_name); + + indent(out) << "// " << field_name << endl; + + // Simple getter + generate_dart_doc(out, field); + indent(out) << type_name(type) << " get " << field_name << " => this._" << field_name << ";" << endl2; + + // Simple setter + generate_dart_doc(out, field); + indent(out) << "set " << field_name << "(" << type_name(type) << " " << field_name << ")"; + scope_up(out); + indent(out) << "this._" << field_name << " = " << field_name << ";" << endl; + generate_isset_set(out, field); + scope_down(out, endl2); + + // isSet method + indent(out) << "bool is" << get_cap_name("set") << cap_name << "()"; + if (type_can_be_null(type)) { + out << " => this." << field_name << " != null;" << endl2; + } else { + out << " => this.__isset_" << field_name << ";" << endl2; + } + + // Unsetter + indent(out) << "unset" << cap_name << "()"; + scope_up(out); + if (type_can_be_null(type)) { + indent(out) << "this." << field_name << " = null;" << endl; + } else { + indent(out) << "this.__isset_" << field_name << " = false;" << endl; + } + scope_down(out, endl2); + } +} + +/** + * Generates a toString() method for the given struct + * + * @param tstruct The struct definition + */ +void t_dart_generator::generate_dart_struct_tostring(ostream& out, + t_struct* tstruct) { + indent(out) << "String toString()"; + scope_up(out); + + indent(out) << "StringBuffer ret = new StringBuffer(\"" + << tstruct->get_name() << "(\");" << endl2; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + bool could_be_unset = (*f_iter)->get_req() == t_field::T_OPTIONAL; + if (could_be_unset) { + indent(out) << "if (" << generate_isset_check(*f_iter) << ")"; + scope_up(out); + } + + t_field* field = (*f_iter); + std::string field_name = get_member_name(field->get_name()); + + if (!first) { + indent(out) << "ret.write(\", \");" << endl; + } + indent(out) << "ret.write(\"" << field_name << ":\");" << endl; + bool can_be_null = type_can_be_null(field->get_type()); + if (can_be_null) { + indent(out) << "if (this." << field_name << " == null)"; + scope_up(out); + indent(out) << "ret.write(\"null\");" << endl; + scope_down(out, " else"); + scope_up(out); + } + + if (field->get_type()->is_binary()) { + indent(out) << "ret.write(\"BINARY\");" << endl; + } else if (field->get_type()->is_enum()) { + indent(out) << "String " << field_name << "_name = " + << get_ttype_class_name(field->get_type()) + << ".VALUES_TO_NAMES[this." << field_name << "];" << endl; + indent(out) << "if (" << field_name << "_name != null)"; + scope_up(out); + indent(out) << "ret.write(" << field_name << "_name);" << endl; + indent(out) << "ret.write(\" (\");" << endl; + scope_down(out); + indent(out) << "ret.write(this." << field_name << ");" << endl; + indent(out) << "if (" << field_name << "_name != null)"; + scope_up(out); + indent(out) << "ret.write(\")\");" << endl; + scope_down(out); + } else { + indent(out) << "ret.write(this." << field_name << ");" << endl; + } + + if (can_be_null) { + scope_down(out); + } + if (could_be_unset) { + scope_down(out); + } + + out << endl; + first = false; + } + + indent(out) << "ret.write(\")\");" << endl2; + + indent(out) << "return ret.toString();" << endl; + + scope_down(out, endl2); +} + +/** + * Returns a string with the dart representation of the given thrift type + * (e.g. for the type struct it returns "TType.STRUCT") + */ +std::string t_dart_generator::get_dart_type_string(t_type* type) { + if (type->is_list()) { + return "TType.LIST"; + } else if (type->is_map()) { + return "TType.MAP"; + } else if (type->is_set()) { + return "TType.SET"; + } else if (type->is_struct() || type->is_xception()) { + return "TType.STRUCT"; + } else if (type->is_enum()) { + return "TType.I32"; + } else if (type->is_typedef()) { + return get_dart_type_string(((t_typedef*)type)->get_type()); + } else if (type->is_base_type()) { + switch (((t_base_type*)type)->get_base()) { + case t_base_type::TYPE_VOID: + return "TType.VOID"; + break; + case t_base_type::TYPE_STRING: + return "TType.STRING"; + break; + case t_base_type::TYPE_BOOL: + return "TType.BOOL"; + break; + case t_base_type::TYPE_I8: + return "TType.BYTE"; + break; + case t_base_type::TYPE_I16: + return "TType.I16"; + break; + case t_base_type::TYPE_I32: + return "TType.I32"; + break; + case t_base_type::TYPE_I64: + return "TType.I64"; + break; + case t_base_type::TYPE_DOUBLE: + return "TType.DOUBLE"; + break; + default: + throw std::runtime_error("Unknown thrift type \"" + type->get_name() + + "\" passed to t_dart_generator::get_dart_type_string!"); + break; // This should never happen! + } + } else { + throw std::runtime_error( + "Unknown thrift type \"" + type->get_name() + + "\" passed to t_dart_generator::get_dart_type_string!"); // This should never happen! + } +} + +void t_dart_generator::generate_service(t_service* tservice) { + string file_name = get_file_name(service_name_); + string f_service_name = src_dir_ + "/" + file_name + ".dart"; + f_service_.open(f_service_name.c_str()); + + f_service_ << autogen_comment() << dart_library(file_name) << endl; + f_service_ << service_imports() << dart_thrift_imports() << endl; + f_service_ << endl; + + generate_service_interface(tservice); + generate_service_client(tservice); + generate_service_server(tservice); + generate_service_helpers(tservice); + + f_service_.close(); +} + +/** + * Generates a service interface definition. + * + * @param tservice The service to generate a header definition for + */ +void t_dart_generator::generate_service_interface(t_service* tservice) { + string extends_iface = ""; + if (tservice->get_extends() != NULL) { + extends_iface = " extends " + get_ttype_class_name(tservice->get_extends()); + } + + generate_dart_doc(f_service_, tservice); + + string class_name = service_name_; + export_class_to_library(get_file_name(service_name_), class_name); + indent(f_service_) << "abstract class " << class_name << extends_iface; + scope_up(f_service_); + + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + f_service_ << endl; + generate_dart_doc(f_service_, *f_iter); + indent(f_service_) << function_signature(*f_iter) << ";" << endl; + } + + scope_down(f_service_, endl2); +} + +/** + * Generates structs for all the service args and return types + * + * @param tservice The service + */ +void t_dart_generator::generate_service_helpers(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* ts = (*f_iter)->get_arglist(); + generate_dart_struct_definition(f_service_, ts, false, false); + generate_function_helpers(*f_iter); + } +} + +/** + * Generates a service client definition. + * + * @param tservice The service to generate a server for. + */ +void t_dart_generator::generate_service_client(t_service* tservice) { + string extends = ""; + string extends_client = ""; + if (tservice->get_extends() != NULL) { + extends = get_ttype_class_name(tservice->get_extends()); + extends_client = " extends " + extends + "Client"; + } + + string class_name = service_name_ + "Client"; + export_class_to_library(get_file_name(service_name_), class_name); + indent(f_service_) << "class " << class_name << extends_client + << " implements " << service_name_; + scope_up(f_service_); + f_service_ << endl; + + indent(f_service_) << class_name << "(TProtocol iprot, [TProtocol oprot = null])"; + + if (!extends.empty()) { + indent_up(); + f_service_ << endl; + indent(f_service_) << ": super(iprot, oprot);" << endl; + indent_down(); + } else { + scope_up(f_service_); + indent(f_service_) << "_iprot = iprot;" << endl; + indent(f_service_) << "_oprot = (oprot == null) ? iprot : oprot;" << endl; + scope_down(f_service_); + } + f_service_ << endl; + + if (extends.empty()) { + indent(f_service_) << "TProtocol _iprot;" << endl2; + indent(f_service_) << "TProtocol get iprot => _iprot;" << endl2; + indent(f_service_) << "TProtocol _oprot;" << endl2; + indent(f_service_) << "TProtocol get oprot => _oprot;" << endl2; + indent(f_service_) << "int _seqid = 0;" << endl2; + indent(f_service_) << "int get seqid => _seqid;" << endl2; + indent(f_service_) << "int nextSeqid() => ++_seqid;" << endl2; + } + + // Generate client method implementations + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + // Open function + indent(f_service_) << function_signature(*f_iter) << " async"; + scope_up(f_service_); + + // Get the struct of function call params + t_struct* arg_struct = (*f_iter)->get_arglist(); + + string argsname = get_args_class_name((*f_iter)->get_name()); + vector<t_field*>::const_iterator fld_iter; + const vector<t_field*>& fields = arg_struct->get_members(); + + // Serialize the request + indent(f_service_) << "oprot.writeMessageBegin(new TMessage(\"" << (*f_iter)->get_name() << "\", " + << ((*f_iter)->is_oneway() ? "TMessageType.ONEWAY" : "TMessageType.CALL") + << ", nextSeqid()));" << endl; + indent(f_service_) << argsname << " args = new " << argsname << "();" << endl; + + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + string arg_field_name = get_member_name((*fld_iter)->get_name()); + indent(f_service_) << "args." << arg_field_name << " = " + << arg_field_name << ";" << endl; + } + + indent(f_service_) << "args.write(oprot);" << endl; + indent(f_service_) << "oprot.writeMessageEnd();" << endl2; + + indent(f_service_) << "await oprot.transport.flush();" << endl2; + + if (!(*f_iter)->is_oneway()) { + indent(f_service_) << "TMessage msg = iprot.readMessageBegin();" << endl; + indent(f_service_) << "if (msg.type == TMessageType.EXCEPTION)"; + scope_up(f_service_); + indent(f_service_) << "TApplicationError error = TApplicationError.read(iprot);" << endl; + indent(f_service_) << "iprot.readMessageEnd();" << endl; + indent(f_service_) << "throw error;" << endl; + scope_down(f_service_, endl2); + + string result_class = get_result_class_name((*f_iter)->get_name()); + indent(f_service_) << result_class << " result = new " << result_class << "();" << endl; + indent(f_service_) << "result.read(iprot);" << endl; + indent(f_service_) << "iprot.readMessageEnd();" << endl; + + // Careful, only return _result if not a void function + if (!(*f_iter)->get_returntype()->is_void()) { + indent(f_service_) << "if (result." << generate_isset_check("success") << ")"; + scope_up(f_service_); + indent(f_service_) << "return result.success;" << endl; + scope_down(f_service_, endl2); + } + + t_struct* xs = (*f_iter)->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + string result_field_name = get_member_name((*x_iter)->get_name()); + indent(f_service_) << "if (result." << result_field_name << " != null)"; + scope_up(f_service_); + indent(f_service_) << "throw result." << result_field_name << ";" << endl; + scope_down(f_service_); + } + + // If you get here it's an exception, unless a void function + if ((*f_iter)->get_returntype()->is_void()) { + indent(f_service_) << "return;" << endl; + } else { + indent(f_service_) << "throw new TApplicationError(TApplicationErrorType.MISSING_RESULT, \"" + << (*f_iter)->get_name() << " failed: unknown result\");" << endl; + } + } + + scope_down(f_service_, endl2); + } + + scope_down(f_service_, endl2); +} + +/** + * Generates a service server definition. + * + * @param tservice The service to generate a server for. + */ +void t_dart_generator::generate_service_server(t_service* tservice) { + // Generate the dispatch methods + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + // typedef + indent(f_service_) << "typedef void ProcessFunction(int seqid, TProtocol iprot, TProtocol oprot);" << endl2; + + // Extends stuff + string extends = ""; + string extends_processor = ""; + if (tservice->get_extends() != NULL) { + extends = get_ttype_class_name(tservice->get_extends()); + extends_processor = " extends " + extends + "Processor"; + } + + // Generate the header portion + string class_name = service_name_ + "Processor"; + export_class_to_library(get_file_name(service_name_), class_name); + indent(f_service_) << "class " << class_name << extends_processor << " implements TProcessor"; + scope_up(f_service_); + + indent(f_service_) << class_name << "(" << service_name_ << " iface)"; + if (!extends.empty()) { + indent_up(); + f_service_ << endl; + indent(f_service_) << ": super(iface)"; + indent_down(); + } + scope_up(f_service_); + + if (extends.empty()) { + indent(f_service_) << "iface_ = iface;" << endl; + } + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + indent(f_service_) << "PROCESS_MAP[\"" << (*f_iter)->get_name() + << "\"] = " << get_member_name((*f_iter)->get_name()) << ";" << endl; + } + scope_down(f_service_, endl2); + + indent(f_service_) << service_name_ << " iface_;" << endl; + + if (extends.empty()) { + indent(f_service_) << "final Map<String, ProcessFunction> PROCESS_MAP = {};" << endl; + } + + f_service_ << endl; + + // Generate the server implementation + indent(f_service_) << "bool process(TProtocol iprot, TProtocol oprot)"; + scope_up(f_service_); + indent(f_service_) << "TMessage msg = iprot.readMessageBegin();" << endl; + indent(f_service_) << "ProcessFunction fn = PROCESS_MAP[msg.name];" << endl; + indent(f_service_) << "if (fn == null)"; + scope_up(f_service_); + indent(f_service_) << "TProtocolUtil.skip(iprot, TType.STRUCT);" << endl; + indent(f_service_) << "iprot.readMessageEnd();" << endl; + indent(f_service_) << "TApplicationError x = new TApplicationError(TApplicationErrorType.UNKNOWN_METHOD, " + "\"Invalid method name: '\"+msg.name+\"'\");" << endl; + indent(f_service_) << "oprot.writeMessageBegin(new TMessage(msg.name, TMessageType.EXCEPTION, msg.seqid));" << endl; + indent(f_service_) << "x.write(oprot);" << endl; + indent(f_service_) << "oprot.writeMessageEnd();" << endl; + indent(f_service_) << "oprot.transport.flush();" << endl; + indent(f_service_) << "return true;" << endl; + scope_down(f_service_); + indent(f_service_) << "fn(msg.seqid, iprot, oprot);" << endl; + indent(f_service_) << "return true;" << endl; + scope_down(f_service_, endl2); // process function + + // Generate the process subfunctions + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_process_function(tservice, *f_iter); + } + + scope_down(f_service_, endl2); // class +} + +/** + * Generates a struct and helpers for a function. + * + * @param tfunction The function + */ +void t_dart_generator::generate_function_helpers(t_function* tfunction) { + if (tfunction->is_oneway()) { + return; + } + + t_struct result(program_, get_result_class_name(tfunction->get_name())); + t_field success(tfunction->get_returntype(), "success", 0); + if (!tfunction->get_returntype()->is_void()) { + result.append(&success); + } + + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& fields = xs->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + result.append(*f_iter); + } + + generate_dart_struct_definition(f_service_, &result, false, true); +} + +/** + * Generates a process function definition. + * + * @param tfunction The function to write a dispatcher for + */ +void t_dart_generator::generate_process_function(t_service* tservice, t_function* tfunction) { + (void)tservice; + + bool await_result = (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void()); + + indent(f_service_) << get_member_name(tfunction->get_name()) << "(int seqid, TProtocol iprot, TProtocol oprot)"; + if (await_result) { + f_service_ << " async"; + } + scope_up(f_service_); + + string argsname = get_args_class_name(tfunction->get_name()); + string resultname = get_result_class_name(tfunction->get_name()); + + indent(f_service_) << argsname << " args = new " << argsname << "();" << endl; + indent(f_service_) << "args.read(iprot);" << endl; + indent(f_service_) << "iprot.readMessageEnd();" << endl; + + t_struct* xs = tfunction->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + + if (!tfunction->is_oneway()) { + indent(f_service_) << resultname << " result = new " << resultname << "();" << endl; + } + + if (!tfunction->is_oneway() && xceptions.size() > 0) { + indent(f_service_) << "try"; + scope_up(f_service_); + } + + // Generate the function call + t_struct* arg_struct = tfunction->get_arglist(); + const std::vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + + f_service_ << indent(); + if (await_result) { + f_service_ << "result.success = await "; + } + f_service_ << "iface_." << get_member_name(tfunction->get_name()) << "("; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << "args." << get_member_name((*f_iter)->get_name()); + } + f_service_ << ");" << endl; + + if (!tfunction->is_oneway() && xceptions.size() > 0) { + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + string result_field_name = get_member_name((*x_iter)->get_name()); + scope_down(f_service_, ""); + f_service_ << " on " << type_name((*x_iter)->get_type()) + << " catch(" << result_field_name << ")"; + scope_up(f_service_); + if (!tfunction->is_oneway()) { + indent(f_service_) << "result." << result_field_name << " = " + << result_field_name << ";" << endl; + } + } + scope_down(f_service_, " "); + f_service_ << "catch (th)"; + scope_up(f_service_); + indent(f_service_) << "// Internal error" << endl; + indent(f_service_) << "TApplicationError x = new " + "TApplicationError(TApplicationErrorType.INTERNAL_ERROR, \"Internal error processing " + << tfunction->get_name() << "\");" << endl; + indent(f_service_) << "oprot.writeMessageBegin(new TMessage(\"" << tfunction->get_name() + << "\", TMessageType.EXCEPTION, seqid));" << endl; + indent(f_service_) << "x.write(oprot);" << endl; + indent(f_service_) << "oprot.writeMessageEnd();" << endl; + indent(f_service_) << "oprot.transport.flush();" << endl; + indent(f_service_) << "return;" << endl; + scope_down(f_service_); + } + + if (tfunction->is_oneway()) { + indent(f_service_) << "return;" << endl; + } else { + indent(f_service_) << "oprot.writeMessageBegin(new TMessage(\"" << tfunction->get_name() + << "\", TMessageType.REPLY, seqid));" << endl; + indent(f_service_) << "result.write(oprot);" << endl; + indent(f_service_) << "oprot.writeMessageEnd();" << endl; + indent(f_service_) << "oprot.transport.flush();" << endl; + } + + scope_down(f_service_, endl2); +} + +/** + * Deserializes a field of any type. + * + * @param tfield The field + * @param prefix The variable name or container for this field + */ +void t_dart_generator::generate_deserialize_field(ostream& out, t_field* tfield, string prefix) { + t_type* type = get_true_type(tfield->get_type()); + string field_name = get_member_name(tfield->get_name()); + + if (type->is_void()) { + throw "CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " + prefix + field_name; + } + + string name = prefix + field_name; + + if (type->is_struct() || type->is_xception()) { + generate_deserialize_struct(out, (t_struct*)type, name); + } else if (type->is_container()) { + generate_deserialize_container(out, type, name); + } else if (type->is_base_type() || type->is_enum()) { + + indent(out) << name << " = iprot."; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + out << "readBinary();"; + } else { + out << "readString();"; + } + break; + case t_base_type::TYPE_BOOL: + out << "readBool();"; + break; + case t_base_type::TYPE_I8: + out << "readByte();"; + break; + case t_base_type::TYPE_I16: + out << "readI16();"; + break; + case t_base_type::TYPE_I32: + out << "readI32();"; + break; + case t_base_type::TYPE_I64: + out << "readI64();"; + break; + case t_base_type::TYPE_DOUBLE: + out << "readDouble();"; + break; + default: + throw "compiler error: no Dart name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "readI32();"; + } + out << endl; + } else { + printf("DO NOT KNOW HOW TO DESERIALIZE FIELD '%s' TYPE '%s'\n", + field_name.c_str(), + type_name(type).c_str()); + } +} + +/** + * Generates an unserializer for a struct, invokes read() + */ +void t_dart_generator::generate_deserialize_struct(ostream& out, t_struct* tstruct, string prefix) { + indent(out) << prefix << " = new " << type_name(tstruct) << "();" << endl; + indent(out) << prefix << ".read(iprot);" << endl; +} + +/** + * Deserializes a container by reading its size and then iterating + */ +void t_dart_generator::generate_deserialize_container(ostream& out, t_type* ttype, string prefix) { + indent(out); + scope_up(out, ""); + + string obj; + + if (ttype->is_map()) { + obj = tmp("_map"); + } else if (ttype->is_set()) { + obj = tmp("_set"); + } else if (ttype->is_list()) { + obj = tmp("_list"); + } + + // Declare variables, read header + if (ttype->is_map()) { + indent(out) << "TMap " << obj << " = iprot.readMapBegin();" << endl; + } else if (ttype->is_set()) { + indent(out) << "TSet " << obj << " = iprot.readSetBegin();" << endl; + } else if (ttype->is_list()) { + indent(out) << "TList " << obj << " = iprot.readListBegin();" << endl; + } + + indent(out) << prefix << " = new " << type_name(ttype) << "();" << endl; + + // For loop iterates over elements + string i = tmp("_i"); + indent(out) << "for (int " << i << " = 0; " << i << " < " << obj << ".length" + << "; " + << "++" << i << ")"; + scope_up(out); + + if (ttype->is_map()) { + generate_deserialize_map_element(out, (t_map*)ttype, prefix); + } else if (ttype->is_set()) { + generate_deserialize_set_element(out, (t_set*)ttype, prefix); + } else if (ttype->is_list()) { + generate_deserialize_list_element(out, (t_list*)ttype, prefix); + } + + scope_down(out); + + // Read container end + if (ttype->is_map()) { + indent(out) << "iprot.readMapEnd();" << endl; + } else if (ttype->is_set()) { + indent(out) << "iprot.readSetEnd();" << endl; + } else if (ttype->is_list()) { + indent(out) << "iprot.readListEnd();" << endl; + } + + scope_down(out); +} + +/** + * Generates code to deserialize a map + */ +void t_dart_generator::generate_deserialize_map_element(ostream& out, t_map* tmap, string prefix) { + string key = tmp("_key"); + string val = tmp("_val"); + t_field fkey(tmap->get_key_type(), key); + t_field fval(tmap->get_val_type(), val); + + indent(out) << declare_field(&fkey) << endl; + indent(out) << declare_field(&fval) << endl; + + generate_deserialize_field(out, &fkey); + generate_deserialize_field(out, &fval); + + indent(out) << prefix << "[" << key << "] = " << val << ";" << endl; +} + +/** + * Deserializes a set element + */ +void t_dart_generator::generate_deserialize_set_element(ostream& out, t_set* tset, string prefix) { + string elem = tmp("_elem"); + t_field felem(tset->get_elem_type(), elem); + + indent(out) << declare_field(&felem) << endl; + + generate_deserialize_field(out, &felem); + + indent(out) << prefix << ".add(" << elem << ");" << endl; +} + +/** + * Deserializes a list element + */ +void t_dart_generator::generate_deserialize_list_element(ostream& out, + t_list* tlist, + string prefix) { + string elem = tmp("_elem"); + t_field felem(tlist->get_elem_type(), elem); + + indent(out) << declare_field(&felem) << endl; + + generate_deserialize_field(out, &felem); + + indent(out) << prefix << ".add(" << elem << ");" << endl; +} + +/** + * Serializes a field of any type. + * + * @param tfield The field to serialize + * @param prefix Name to prepend to field name + */ +void t_dart_generator::generate_serialize_field(ostream& out, t_field* tfield, string prefix) { + t_type* type = get_true_type(tfield->get_type()); + string field_name = get_member_name(tfield->get_name()); + + // Do nothing for void types + if (type->is_void()) { + throw "CANNOT GENERATE SERIALIZE CODE FOR void TYPE: " + prefix + field_name; + } + + if (type->is_struct() || type->is_xception()) { + generate_serialize_struct(out, (t_struct*)type, prefix + field_name); + } else if (type->is_container()) { + generate_serialize_container(out, type, prefix + field_name); + } else if (type->is_base_type() || type->is_enum()) { + + string name = prefix + field_name; + indent(out) << "oprot."; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + out << "writeBinary(" << name << ");"; + } else { + out << "writeString(" << name << ");"; + } + break; + case t_base_type::TYPE_BOOL: + out << "writeBool(" << name << ");"; + break; + case t_base_type::TYPE_I8: + out << "writeByte(" << name << ");"; + break; + case t_base_type::TYPE_I16: + out << "writeI16(" << name << ");"; + break; + case t_base_type::TYPE_I32: + out << "writeI32(" << name << ");"; + break; + case t_base_type::TYPE_I64: + out << "writeI64(" << name << ");"; + break; + case t_base_type::TYPE_DOUBLE: + out << "writeDouble(" << name << ");"; + break; + default: + throw "compiler error: no Dart name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "writeI32(" << name << ");"; + } + out << endl; + } else { + printf("DO NOT KNOW HOW TO SERIALIZE FIELD '%s%s' TYPE '%s'\n", + prefix.c_str(), + field_name.c_str(), + type_name(type).c_str()); + } +} + +/** + * Serializes all the members of a struct. + * + * @param tstruct The struct to serialize + * @param prefix String prefix to attach to all fields + */ +void t_dart_generator::generate_serialize_struct(ostream& out, t_struct* tstruct, string prefix) { + (void)tstruct; + indent(out) << prefix << ".write(oprot);" << endl; +} + +/** + * Serializes a container by writing its size then the elements. + * + * @param ttype The type of container + * @param prefix String prefix for fields + */ +void t_dart_generator::generate_serialize_container(ostream& out, t_type* ttype, string prefix) { + indent(out); + scope_up(out, ""); + + if (ttype->is_map()) { + string iter = tmp("_key"); + indent(out) << "oprot.writeMapBegin(new TMap(" << type_to_enum(((t_map*)ttype)->get_key_type()) + << ", " << type_to_enum(((t_map*)ttype)->get_val_type()) << ", " << prefix << ".length));" + << endl; + } else if (ttype->is_set()) { + indent(out) << "oprot.writeSetBegin(new TSet(" << type_to_enum(((t_set*)ttype)->get_elem_type()) + << ", " << prefix << ".length));" << endl; + } else if (ttype->is_list()) { + indent(out) << "oprot.writeListBegin(new TList(" + << type_to_enum(((t_list*)ttype)->get_elem_type()) << ", " << prefix << ".length));" + << endl; + } + + string iter = tmp("elem"); + if (ttype->is_map()) { + indent(out) << "for (var " << iter << " in " << prefix << ".keys)"; + } else if (ttype->is_set() || ttype->is_list()) { + indent(out) << "for (var " << iter << " in " << prefix << ")"; + } + + scope_up(out); + + if (ttype->is_map()) { + generate_serialize_map_element(out, (t_map*)ttype, iter, prefix); + } else if (ttype->is_set()) { + generate_serialize_set_element(out, (t_set*)ttype, iter); + } else if (ttype->is_list()) { + generate_serialize_list_element(out, (t_list*)ttype, iter); + } + + scope_down(out); + + if (ttype->is_map()) { + indent(out) << "oprot.writeMapEnd();" << endl; + } else if (ttype->is_set()) { + indent(out) << "oprot.writeSetEnd();" << endl; + } else if (ttype->is_list()) { + indent(out) << "oprot.writeListEnd();" << endl; + } + + scope_down(out); +} + +/** + * Serializes the members of a map. + */ +void t_dart_generator::generate_serialize_map_element(ostream& out, + t_map* tmap, + string iter, + string map) { + t_field kfield(tmap->get_key_type(), iter); + generate_serialize_field(out, &kfield, ""); + t_field vfield(tmap->get_val_type(), map + "[" + iter + "]"); + generate_serialize_field(out, &vfield, ""); +} + +/** + * Serializes the members of a set. + */ +void t_dart_generator::generate_serialize_set_element(ostream& out, t_set* tset, string iter) { + t_field efield(tset->get_elem_type(), iter); + generate_serialize_field(out, &efield, ""); +} + +/** + * Serializes the members of a list. + */ +void t_dart_generator::generate_serialize_list_element(ostream& out, t_list* tlist, string iter) { + t_field efield(tlist->get_elem_type(), iter); + generate_serialize_field(out, &efield, ""); +} + +/** + * Returns a Dart type name + * + * @param ttype The type + * @return Dart type name, i.e. Map<Key, Value> + */ +string t_dart_generator::type_name(t_type* ttype) { + ttype = get_true_type(ttype); + + if (ttype->is_base_type()) { + return base_type_name((t_base_type*)ttype); + } else if (ttype->is_enum()) { + return "int"; + } else if (ttype->is_map()) { + t_map* tmap = (t_map*)ttype; + return "Map<" + type_name(tmap->get_key_type()) + ", " + + type_name(tmap->get_val_type()) + ">"; + } else if (ttype->is_set()) { + t_set* tset = (t_set*)ttype; + return "Set<" + type_name(tset->get_elem_type()) + ">"; + } else if (ttype->is_list()) { + t_list* tlist = (t_list*)ttype; + return "List<" + type_name(tlist->get_elem_type()) + ">"; + } + + return get_ttype_class_name(ttype); +} + +/** + * Returns the Dart type that corresponds to the thrift type. + * + * @param tbase The base type + */ +string t_dart_generator::base_type_name(t_base_type* type) { + t_base_type::t_base tbase = type->get_base(); + + switch (tbase) { + case t_base_type::TYPE_VOID: + return "void"; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + return "Uint8List"; + } else { + return "String"; + } + case t_base_type::TYPE_BOOL: + return "bool"; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + return "int"; + case t_base_type::TYPE_DOUBLE: + return "double"; + default: + throw "compiler error: no Dart name for base type " + t_base_type::t_base_name(tbase); + } +} + +/** + * Declares a field, which may include initialization as necessary. + * + * @param ttype The type + */ +string t_dart_generator::declare_field(t_field* tfield, bool init) { + string field_name = get_member_name(tfield->get_name()); + string result = type_name(tfield->get_type()) + " " + field_name; + if (init) { + t_type* ttype = get_true_type(tfield->get_type()); + if (ttype->is_base_type() && tfield->get_value() != NULL) { + std:: ofstream dummy; + result += " = " + render_const_value(dummy, field_name, ttype, tfield->get_value()); + } else if (ttype->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)ttype)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + result += " = null"; + break; + case t_base_type::TYPE_BOOL: + result += " = false"; + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + result += " = 0"; + break; + case t_base_type::TYPE_DOUBLE: + result += " = 0.0"; + break; + } + + } else if (ttype->is_enum()) { + result += " = 0"; + } else if (ttype->is_container()) { + result += " = new " + type_name(ttype) + "()"; + } else { + result += " = new " + type_name(ttype) + "()"; + ; + } + } + return result + ";"; +} + +/** + * Renders a function signature of the form 'type name(args)' + * + * @param tfunction Function definition + * @return String of rendered function definition + */ +string t_dart_generator::function_signature(t_function* tfunction) { + std::string arguments = argument_list(tfunction->get_arglist()); + + std::string returntype; + if (tfunction->get_returntype()->is_void()) { + returntype = "Future"; + } else { + returntype = "Future<" + type_name(tfunction->get_returntype()) + ">"; + } + + std::string result = returntype + " " + get_member_name(tfunction->get_name()) + + "(" + arguments + ")"; + return result; +} + +/** + * Renders a comma separated field list, with type names + */ +string t_dart_generator::argument_list(t_struct* tstruct) { + string result = ""; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + result += ", "; + } + string field_name = get_member_name((*f_iter)->get_name()); + result += type_name((*f_iter)->get_type()) + " " + field_name; + } + return result; +} + +/** + * Converts the parse type to a C++ enum string for the given type. + */ +string t_dart_generator::type_to_enum(t_type* type) { + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + return "TType.STRING"; + case t_base_type::TYPE_BOOL: + return "TType.BOOL"; + case t_base_type::TYPE_I8: + return "TType.BYTE"; + case t_base_type::TYPE_I16: + return "TType.I16"; + case t_base_type::TYPE_I32: + return "TType.I32"; + case t_base_type::TYPE_I64: + return "TType.I64"; + case t_base_type::TYPE_DOUBLE: + return "TType.DOUBLE"; + } + } else if (type->is_enum()) { + return "TType.I32"; + } else if (type->is_struct() || type->is_xception()) { + return "TType.STRUCT"; + } else if (type->is_map()) { + return "TType.MAP"; + } else if (type->is_set()) { + return "TType.SET"; + } else if (type->is_list()) { + return "TType.LIST"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +std::string t_dart_generator::init_value(t_field* field) { + // Do not initialize optional fields + if (field->get_req() == t_field::T_OPTIONAL) { + return ""; + } + + t_type* ttype = field->get_type(); + + // Get the actual type for a typedef + if (ttype->is_typedef()) { + ttype = ((t_typedef*)ttype)->get_type(); + } + + // Only consider base types for default initialization + if (!ttype->is_base_type()) { + return ""; + } + t_base_type::t_base tbase = ((t_base_type*)ttype)->get_base(); + + // Initialize bools, ints, and doubles with sane defaults + string result; + switch (tbase) { + case t_base_type::TYPE_BOOL: + result = " = false"; + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + result = " = 0"; + break; + case t_base_type::TYPE_DOUBLE: + result = " = 0.0"; + break; + case t_base_type::TYPE_VOID: + case t_base_type::TYPE_STRING: + result = ""; + break; + } + + return result; +} + +std::string t_dart_generator::get_cap_name(std::string name) { + name[0] = toupper(name[0]); + return name; +} + +std::string t_dart_generator::get_member_name(std::string name) { + name[0] = tolower(name[0]); + return name; +} + +std::string t_dart_generator::get_args_class_name(std::string name) { + return name + "_args"; +} + +std::string t_dart_generator::get_result_class_name(std::string name) { + return name + "_result"; +} + +std::string t_dart_generator::get_file_name(std::string name) { + // e.g. change APIForFileIO to api_for_file_io + + string ret; + const char* tmp = name.c_str(); + bool is_prev_lc = true; + bool is_current_lc = tmp[0] == tolower(tmp[0]); + bool is_next_lc = false; + + for (unsigned int i = 0; i < name.length(); i++) { + char lc = tolower(tmp[i]); + + if (i == name.length() - 1) { + is_next_lc = false; + } else { + is_next_lc = (tmp[i+1] == tolower(tmp[i+1])); + } + + if (i != 0 && !is_current_lc && (is_prev_lc || is_next_lc)) { + ret += "_"; + } + ret += lc; + + is_prev_lc = is_current_lc; + is_current_lc = is_next_lc; + } + + return ret; +} + +std::string t_dart_generator::get_constants_class_name(std::string name) { + // e.g. change my_great_model to MyGreatModelConstants + string ret; + const char* tmp = name.c_str(); + bool is_prev_underscore = true; + + for (unsigned int i = 0; i < name.length(); i++) { + if (tmp[i] == '_') { + is_prev_underscore = true; + } else { + if (is_prev_underscore) { + ret += toupper(tmp[i]); + } else { + ret += tmp[i]; + } + + is_prev_underscore = false; + } + } + + return ret + "Constants"; +} + +string t_dart_generator::constant_name(string name) { + string constant_name; + + bool is_first = true; + bool was_previous_char_upper = false; + for (char character : name) { + bool is_upper = isupper(character); + + if (is_upper && !is_first && !was_previous_char_upper) { + constant_name += '_'; + } + constant_name += toupper(character); + + is_first = false; + was_previous_char_upper = is_upper; + } + + return constant_name; +} + +/** + * Emits a doc comment if the provided object has a doc in Thrift + */ +void t_dart_generator::generate_dart_doc(ostream& out, t_doc* tdoc) { + if (tdoc->has_doc()) { + generate_docstring_comment(out, "", "/// ", tdoc->get_doc(), ""); + } +} + +/** + * Emits a doc comment if the provided function object has a doc in Thrift + */ +void t_dart_generator::generate_dart_doc(ostream& out, t_function* tfunction) { + if (tfunction->has_doc()) { + stringstream ss; + ss << tfunction->get_doc(); + const vector<t_field*>& fields = tfunction->get_arglist()->get_members(); + vector<t_field*>::const_iterator p_iter; + for (p_iter = fields.begin(); p_iter != fields.end(); ++p_iter) { + t_field* p = *p_iter; + string field_name = get_member_name(p->get_name()); + ss << "\n@param " << field_name; + if (p->has_doc()) { + ss << " " << p->get_doc(); + } + } + generate_docstring_comment(out, "", "/// ", ss.str(), ""); + } +} + +std::string t_dart_generator::generate_isset_check(t_field* field) { + string field_name = get_member_name(field->get_name()); + return generate_isset_check(field_name); +} + +std::string t_dart_generator::generate_isset_check(std::string field_name) { + return "is" + get_cap_name("set") + get_cap_name(field_name) + "()"; +} + +void t_dart_generator::generate_isset_set(ostream& out, t_field* field) { + if (!type_can_be_null(field->get_type())) { + string field_name = get_member_name(field->get_name()); + indent(out) << "this.__isset_" << field_name << " = true;" << endl; + } +} + +std::string t_dart_generator::get_ttype_class_name(t_type* ttype) { + if (program_ == ttype->get_program()) { + return ttype->get_name(); + } else { + string named_import = "t_" + find_library_name(ttype->get_program()); + return named_import + "." + ttype->get_name(); + } +} + +THRIFT_REGISTER_GENERATOR( + dart, + "Dart", + " library_name: Optional override for library name.\n" + " library_prefix: Generate code that can be used within an existing library.\n" + " Use a dot-separated string, e.g. \"my_parent_lib.src.gen\"\n" + " pubspec_lib: Optional override for thrift lib dependency in pubspec.yaml,\n" + " e.g. \"thrift: 0.x.x\". Use a pipe delimiter to separate lines,\n" + " e.g. \"thrift:| git:| url: git@foo.com\"\n" +) diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_delphi_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_delphi_generator.cc new file mode 100644 index 000000000..4a2ebdaaf --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_delphi_generator.cc @@ -0,0 +1,4011 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +#include <cassert> + +#include <string> +#include <fstream> +#include <iostream> +#include <vector> +#include <list> + +#include <stdlib.h> +#include <sys/stat.h> +#include <sstream> +#include <cctype> + +#include "thrift/platform.h" +#include "thrift/generate/t_oop_generator.h" + +using std::map; +using std::ofstream; +using std::ostream; +using std::ostringstream; +using std::string; +using std::stringstream; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes + +class t_delphi_generator : public t_oop_generator { +public: + t_delphi_generator(t_program* program, + const std::map<std::string, std::string>& parsed_options, + const std::string& option_string) + : t_oop_generator(program) { + (void)option_string; + indent_impl_ = 0; + has_forward = false; + has_enum = false; + has_const = false; + std::map<std::string, std::string>::const_iterator iter; + + ansistr_binary_ = false; + register_types_ = false; + constprefix_ = false; + events_ = false; + xmldoc_ = false; + async_ = false; + for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + if( iter->first.compare("ansistr_binary") == 0) { + ansistr_binary_ = true; + } else if( iter->first.compare("register_types") == 0) { + register_types_ = true; + } else if( iter->first.compare("constprefix") == 0) { + constprefix_ = true; + } else if( iter->first.compare("events") == 0) { + events_ = true; + } else if( iter->first.compare("xmldoc") == 0) { + xmldoc_ = true; + } else if( iter->first.compare("async") == 0) { + async_ = true; + } else { + throw "unknown option delphi:" + iter->first; + } + } + + out_dir_base_ = "gen-delphi"; + escape_.clear(); + escape_['\''] = "''"; + } + + void init_generator() override; + void close_generator() override; + + void generate_consts(std::vector<t_const*> consts) override; + + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_forward_declaration(t_struct* tstruct) override; + void generate_struct(t_struct* tstruct) override; + void generate_xception(t_struct* txception) override; + void generate_service(t_service* tservice) override; + void generate_property(ostream& out, t_field* tfield, bool isPublic, bool is_xception); + void generate_property_writer_(ostream& out, t_field* tfield, bool isPublic); + + void generate_delphi_property(ostream& out, + bool struct_is_exception, + t_field* tfield, + bool isPublic, + std::string fieldPrefix = ""); + void generate_delphi_isset_reader_definition(ostream& out, t_field* tfield, bool is_xception); + void generate_delphi_property_reader_definition(ostream& out, + t_field* tfield, + bool is_xception_class); + void generate_delphi_property_writer_definition(ostream& out, + t_field* tfield, + bool is_xception_class); + void generate_delphi_property_reader_impl(ostream& out, + std::string cls_prefix, + std::string name, + t_type* type, + t_field* tfield, + std::string fieldPrefix, + bool is_xception_class); + void generate_delphi_property_writer_impl(ostream& out, + std::string cls_prefix, + std::string name, + t_type* type, + t_field* tfield, + std::string fieldPrefix, + bool is_xception_class, + bool is_union, + bool is_xception_factory, + std::string xception_factory_name); + void generate_delphi_clear_union_value(ostream& out, + std::string cls_prefix, + std::string name, + t_type* type, + t_field* tfield, + std::string fieldPrefix, + bool is_xception_class, + bool is_union, + bool is_xception_factory, + std::string xception_factory_name); + void generate_delphi_isset_reader_impl(ostream& out, + std::string cls_prefix, + std::string name, + t_type* type, + t_field* tfield, + std::string fieldPrefix, + bool is_xception); + void generate_delphi_struct_writer_impl(ostream& out, + std::string cls_prefix, + t_struct* tstruct, + bool is_exception); + void generate_delphi_struct_result_writer_impl(ostream& out, + std::string cls_prefix, + t_struct* tstruct, + bool is_exception); + + void generate_delphi_struct_tostring_impl(ostream& out, + std::string cls_prefix, + t_struct* tstruct, + bool is_exception, + bool is_x_factory); + + void add_delphi_uses_list(string unitname); + + void generate_delphi_struct_reader_impl(ostream& out, + std::string cls_prefix, + t_struct* tstruct, + bool is_exception); + void generate_delphi_create_exception_impl(ostream& out, + string cls_prefix, + t_struct* tstruct, + bool is_exception); + + bool const_needs_var(t_type* type); + void print_const_prop(std::ostream& out, string name, t_type* type, t_const_value* value); + void print_private_field(std::ostream& out, string name, t_type* type, t_const_value* value); + void print_const_value(std::ostream& vars, + std::ostream& out, + std::string name, + t_type* type, + t_const_value* value); + void initialize_field(std::ostream& vars, + std::ostream& out, + std::string name, + t_type* type, + t_const_value* value); + void finalize_field(std::ostream& out, + std::string name, + t_type* type, + t_const_value* value, + std::string cls_nm = ""); + std::string render_const_value(std::ostream& local_vars, + std::ostream& out, + std::string name, + t_type* type, + t_const_value* value); + void print_const_def_value(std::ostream& vars, + std::ostream& out, + std::string name, + t_type* type, + t_const_value* value, + std::string cls_nm = ""); + std::string make_constants_classname(); + + void generate_delphi_struct(t_struct* tstruct, bool is_exception); + void generate_delphi_struct_impl(ostream& out, + std::string cls_prefix, + t_struct* tstruct, + bool is_exception, + bool is_result = false, + bool is_x_factory = false); + void print_delphi_struct_type_factory_func(ostream& out, t_struct* tstruct); + void generate_delphi_struct_type_factory(ostream& out, + std::string cls_prefix, + t_struct* tstruct, + bool is_exception, + bool is_result = false, + bool is_x_factory = false); + void generate_delphi_struct_type_factory_registration(ostream& out, + std::string cls_prefix, + t_struct* tstruct, + bool is_exception, + bool is_result = false, + bool is_x_factory = false); + void generate_delphi_struct_definition(std::ostream& out, + t_struct* tstruct, + bool is_xception = false, + bool in_class = false, + bool is_result = false, + bool is_x_factory = false); + void generate_delphi_struct_reader(std::ostream& out, t_struct* tstruct); + void generate_delphi_struct_result_writer(std::ostream& out, t_struct* tstruct); + void generate_delphi_struct_writer(std::ostream& out, t_struct* tstruct); + void generate_delphi_struct_tostring(std::ostream& out, t_struct* tstruct); + + void generate_function_helpers(t_function* tfunction); + void generate_service_interface(t_service* tservice); + void generate_service_interface(t_service* tservice, bool for_async); + void generate_service_helpers(t_service* tservice); + void generate_service_client(t_service* tservice); + void generate_service_server(t_service* tservice); + void generate_process_function(t_service* tservice, t_function* function); + + void generate_deserialize_field(std::ostream& out, + bool is_xception, + t_field* tfield, + std::string prefix, + std::ostream& local_vars); + void generate_deserialize_struct(std::ostream& out, + t_struct* tstruct, + std::string name, + std::string prefix); + void generate_deserialize_container(ostream& out, + bool is_xception, + t_type* ttype, + string name, + std::ostream& local_vars); + + void generate_deserialize_set_element(std::ostream& out, + bool is_xception, + t_set* tset, + std::string prefix, + std::ostream& local_vars); + void generate_deserialize_map_element(std::ostream& out, + bool is_xception, + t_map* tmap, + std::string prefix, + std::ostream& local_vars); + void generate_deserialize_list_element(std::ostream& out, + bool is_xception, + t_list* list, + std::string prefix, + std::ostream& local_vars); + + void generate_serialize_field(std::ostream& out, + bool is_xception, + t_field* tfield, + std::string prefix, + std::ostream& local_vars); + void generate_serialize_struct(std::ostream& out, + t_struct* tstruct, + std::string prefix, + std::ostream& local_vars); + void generate_serialize_container(std::ostream& out, + bool is_xception, + t_type* ttype, + std::string prefix, + std::ostream& local_vars); + void generate_serialize_map_element(std::ostream& out, + bool is_xception, + t_map* tmap, + std::string iter, + std::string map, + std::ostream& local_vars); + void generate_serialize_set_element(std::ostream& out, + bool is_xception, + t_set* tmap, + std::string iter, + std::ostream& local_vars); + void generate_serialize_list_element(std::ostream& out, + bool is_xception, + t_list* tlist, + std::string iter, + std::ostream& local_vars); + + void delphi_type_usings(std::ostream& out); + std::string delphi_thrift_usings(); + + std::string type_name(t_type* ttype, + bool b_cls = false, + bool b_no_postfix = false, + bool b_exception_factory = false, + bool b_full_exception_factory = false); + std::string normalize_clsnm(std::string name, + std::string prefix, + bool b_no_check_keyword = false); + std::string make_valid_delphi_identifier(std::string const& fromName); + std::string input_arg_prefix(t_type* ttype); + + std::string base_type_name(t_base_type* tbase); + std::string declare_field(t_field* tfield, + bool init = false, + std::string prefix = "", + bool is_xception_class = false); + std::string function_signature(t_function* tfunction, + bool for_async, + std::string full_cls = "", + bool is_xception = false); + std::string argument_list(t_struct* tstruct); + std::string constructor_argument_list(t_struct* tstruct, std::string current_indent); + std::string type_to_enum(t_type* ttype); + std::string prop_name(t_field* tfield, bool is_xception = false); + std::string prop_name(std::string name, bool is_xception = false); + std::string constructor_param_name(string name); + + void write_enum(std::string line); + void write_forward_decr(std::string line); + void write_const(std::string line); + void write_struct(std::string line); + void write_service(std::string line); + + std::string autogen_comment() override { + return std::string("(**\n") + " * Autogenerated by Thrift Compiler (" + THRIFT_VERSION + ")\n" + + " *\n" + " * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n" + + " *)\n"; + } + + string replace_all(string contents, string search, string replace); + string xml_encode(string contents); + string xmldoc_encode(string contents); + string xmlattrib_encode(string contents); + void generate_delphi_doc(std::ostream& out, t_field* field); + void generate_delphi_doc(std::ostream& out, t_doc* tdoc); + void generate_delphi_doc(std::ostream& out, t_function* tdoc); + void generate_delphi_docstring_comment(std::ostream& out, string contents); + + bool type_can_be_null(t_type* ttype) { + while (ttype->is_typedef()) { + ttype = ((t_typedef*)ttype)->get_type(); + } + + return ttype->is_container() || ttype->is_struct() || ttype->is_xception(); + } + +private: + std::string namespace_name_; + std::ostringstream s_forward_decr; + std::ostringstream s_enum; + std::ostringstream s_const; + std::ostringstream s_struct; + std::ostringstream s_service; + std::ostringstream s_const_impl; + std::ostringstream s_struct_impl; + std::ostringstream s_service_impl; + std::ostringstream s_type_factory_registration; + std::ostringstream s_type_factory_funcs; + bool has_forward; + bool has_enum; + bool has_const; + std::string namespace_dir_; + std::map<std::string, int> delphi_keywords; + std::map<std::string, int> delphi_reserved_method; + std::map<std::string, int> delphi_reserved_method_exception; + std::map<std::string, int> types_known; + std::list<t_typedef*> typedefs_pending; + std::vector<std::string> uses_list; + void create_keywords(); + bool find_keyword(std::map<std::string, int>& keyword_map, std::string name); + std::string normalize_name(std::string name, + bool b_method = false, + bool b_exception_method = false); + std::string empty_value(t_type* type); + bool is_fully_defined_type(t_type* ttype); + void add_defined_type(t_type* ttype); + void init_known_types_list(); + bool is_void(t_type* type); + int indent_impl_; + bool ansistr_binary_; + bool register_types_; + bool constprefix_; + bool events_; + bool xmldoc_; + bool async_; + void indent_up_impl() { ++indent_impl_; }; + void indent_down_impl() { --indent_impl_; }; + std::string indent_impl() { + std::string ind = ""; + int i; + for (i = 0; i < indent_impl_; ++i) { + ind += " "; + } + return ind; + }; + std::ostream& indent_impl(std::ostream& os) { return os << indent_impl(); }; +}; + +string t_delphi_generator::replace_all(string contents, string search, string repl) { + string str(contents); + + size_t slen = search.length(); + size_t rlen = repl.length(); + size_t incr = (rlen > 0) ? rlen : 1; + + if (slen > 0) { + size_t found = str.find(search); + while ((found != string::npos) && (found < str.length())) { + str.replace(found, slen, repl); + found = str.find(search, found + incr); + } + } + + return str; +} + +// XML encoding +string t_delphi_generator::xml_encode(string contents) { + string str(contents); + + // escape the escape + str = replace_all(str, "&", "&"); + + // other standard XML entities + str = replace_all(str, "<", "<"); + str = replace_all(str, ">", ">"); + + return str; +} + +// XML attribute encoding +string t_delphi_generator::xmlattrib_encode(string contents) { + string str(xml_encode(contents)); + + // our attribs are enclosed in " + str = replace_all(str, "\"", "\\\""); + + return str; +} + +// XML encoding for doc comments +string t_delphi_generator::xmldoc_encode(string contents) { + string str(xml_encode(contents)); + + // XMLDoc specific: convert linebreaks into <para>graphs</para> + str = replace_all(str, "\r\n", "\r"); + str = replace_all(str, "\n", "\r"); + str = replace_all(str, "\r", "</para>\n<para>"); + + return str; +} + +void t_delphi_generator::generate_delphi_docstring_comment(ostream& out, string contents) { + if (xmldoc_) { + generate_docstring_comment(out, + "{$REGION 'XMLDoc'}/// <summary>\n", + "/// ", + "<para>" + contents + "</para>", + "/// </summary>\n{$ENDREGION}\n"); + } +} + +void t_delphi_generator::generate_delphi_doc(ostream& out, t_field* field) { + if (xmldoc_) { + if (field->get_type()->is_enum()) { + string combined_message = xmldoc_encode(field->get_doc()) + "\n<seealso cref=\"" + + xmldoc_encode(type_name(field->get_type())) + "\"/>"; + generate_delphi_docstring_comment(out, combined_message); + } else { + generate_delphi_doc(out, (t_doc*)field); + } + } +} + +void t_delphi_generator::generate_delphi_doc(ostream& out, t_doc* tdoc) { + if (tdoc->has_doc() && xmldoc_) { + generate_delphi_docstring_comment(out, xmldoc_encode(tdoc->get_doc())); + } +} + +void t_delphi_generator::generate_delphi_doc(ostream& out, t_function* tfunction) { + if (tfunction->has_doc() && xmldoc_) { + stringstream ps; + const vector<t_field*>& fields = tfunction->get_arglist()->get_members(); + vector<t_field*>::const_iterator p_iter; + for (p_iter = fields.begin(); p_iter != fields.end(); ++p_iter) { + t_field* p = *p_iter; + ps << "\n<param name=\"" << xmlattrib_encode(p->get_name()) << "\">"; + if (p->has_doc()) { + std::string str = p->get_doc(); + str.erase(std::remove(str.begin(), str.end(), '\n'), + str.end()); // remove the newlines that appear from the parser + ps << xmldoc_encode(str); + } + ps << "</param>"; + } + generate_docstring_comment(out, + "{$REGION 'XMLDoc'}", + "/// ", + "<summary><para>" + xmldoc_encode(tfunction->get_doc()) + + "</para></summary>" + ps.str(), + "{$ENDREGION}\n"); + } +} + +bool t_delphi_generator::find_keyword(std::map<std::string, int>& keyword_map, std::string name) { + std::string::size_type len = name.length(); + + if (len <= 0) { + return false; + } + + std::string::size_type nlast = name.find_last_of('_'); + + if (nlast >= 1) { + if (nlast == (len - 1)) { + string new_name(name, 0, nlast); + return find_keyword(keyword_map, new_name); + } + } + return (keyword_map[name] == 1); +} + +std::string t_delphi_generator::normalize_name(std::string name, + bool b_method, + bool b_exception_method) { + string tmp(name); + std::transform(tmp.begin(), tmp.end(), tmp.begin(), static_cast<int (*)(int)>(std::tolower)); + + bool b_found = false; + + if (find_keyword(delphi_keywords, tmp)) { + b_found = true; + } else if (b_method && find_keyword(delphi_reserved_method, tmp)) { + b_found = true; + } else if (b_exception_method && find_keyword(delphi_reserved_method_exception, tmp)) { + b_found = true; + } + + if (b_found) { + return name + "_"; + } else { + return name; + } +} + +void t_delphi_generator::create_keywords() { + delphi_keywords["and"] = 1; + delphi_keywords["end"] = 1; + delphi_keywords["interface"] = 1; + delphi_keywords["raise"] = 1; + delphi_keywords["uses"] = 1; + delphi_keywords["array"] = 1; + delphi_keywords["except"] = 1; + delphi_keywords["is"] = 1; + delphi_keywords["record"] = 1; + delphi_keywords["var"] = 1; + delphi_keywords["as"] = 1; + delphi_keywords["exports"] = 1; + delphi_keywords["label"] = 1; + delphi_keywords["repeat"] = 1; + delphi_keywords["while"] = 1; + delphi_keywords["asm"] = 1; + delphi_keywords["file"] = 1; + delphi_keywords["library"] = 1; + delphi_keywords["resourcestring"] = 1; + delphi_keywords["with"] = 1; + delphi_keywords["begin"] = 1; + delphi_keywords["finalization"] = 1; + delphi_keywords["mod"] = 1; + delphi_keywords["set"] = 1; + delphi_keywords["xor"] = 1; + delphi_keywords["case"] = 1; + delphi_keywords["finally"] = 1; + delphi_keywords["nil"] = 1; + delphi_keywords["shl"] = 1; + delphi_keywords["class"] = 1; + delphi_keywords["for"] = 1; + delphi_keywords["not"] = 1; + delphi_keywords["shr"] = 1; + delphi_keywords["const"] = 1; + delphi_keywords["function"] = 1; + delphi_keywords["object"] = 1; + delphi_keywords["string"] = 1; + delphi_keywords["constructor"] = 1; + delphi_keywords["goto"] = 1; + delphi_keywords["of"] = 1; + delphi_keywords["then"] = 1; + delphi_keywords["destructor"] = 1; + delphi_keywords["if"] = 1; + delphi_keywords["or"] = 1; + delphi_keywords["threadvar"] = 1; + delphi_keywords["dispinterface"] = 1; + delphi_keywords["implementation"] = 1; + delphi_keywords["out"] = 1; + delphi_keywords["to"] = 1; + delphi_keywords["div"] = 1; + delphi_keywords["in"] = 1; + delphi_keywords["packed"] = 1; + delphi_keywords["try"] = 1; + delphi_keywords["do"] = 1; + delphi_keywords["inherited"] = 1; + delphi_keywords["procedure"] = 1; + delphi_keywords["type"] = 1; + delphi_keywords["downto"] = 1; + delphi_keywords["initialization"] = 1; + delphi_keywords["program"] = 1; + delphi_keywords["unit"] = 1; + delphi_keywords["else"] = 1; + delphi_keywords["inline"] = 1; + delphi_keywords["property"] = 1; + delphi_keywords["until"] = 1; + delphi_keywords["private"] = 1; + delphi_keywords["protected"] = 1; + delphi_keywords["public"] = 1; + delphi_keywords["published"] = 1; + delphi_keywords["automated"] = 1; + delphi_keywords["at"] = 1; + delphi_keywords["on"] = 1; + + // reserved/predefined variables and types (lowercase!) + delphi_keywords["result"] = 1; + delphi_keywords["system"] = 1; + delphi_keywords["sysutils"] = 1; + delphi_keywords["thrift"] = 1; + delphi_keywords["tbytes"] = 1; + delphi_keywords["tobject"] = 1; + delphi_keywords["tclass"] = 1; + delphi_keywords["tinterfacedobject"] = 1; + delphi_keywords["ansistring"] = 1; + delphi_keywords["string"] = 1; + delphi_keywords["boolean"] = 1; + delphi_keywords["shortint"] = 1; + delphi_keywords["smallint"] = 1; + delphi_keywords["integer"] = 1; + delphi_keywords["int64"] = 1; + delphi_keywords["double"] = 1; + + delphi_reserved_method["create"] = 1; + delphi_reserved_method["free"] = 1; + delphi_reserved_method["initinstance"] = 1; + delphi_reserved_method["cleanupinstance"] = 1; + delphi_reserved_method["classtype"] = 1; + delphi_reserved_method["classname"] = 1; + delphi_reserved_method["classnameis"] = 1; + delphi_reserved_method["classparent"] = 1; + delphi_reserved_method["classinfo"] = 1; + delphi_reserved_method["instancesize"] = 1; + delphi_reserved_method["inheritsfrom"] = 1; + delphi_reserved_method["methodaddress"] = 1; + delphi_reserved_method["methodname"] = 1; + delphi_reserved_method["fieldaddress"] = 1; + delphi_reserved_method["getinterface"] = 1; + delphi_reserved_method["getinterfaceentry"] = 1; + delphi_reserved_method["getinterfacetable"] = 1; + delphi_reserved_method["unitname"] = 1; + delphi_reserved_method["equals"] = 1; + delphi_reserved_method["gethashcode"] = 1; + delphi_reserved_method["tostring"] = 1; + delphi_reserved_method["safecallexception"] = 1; + delphi_reserved_method["afterconstruction"] = 1; + delphi_reserved_method["beforedestruction"] = 1; + delphi_reserved_method["dispatch"] = 1; + delphi_reserved_method["defaulthandler"] = 1; + delphi_reserved_method["newinstance"] = 1; + delphi_reserved_method["freeinstance"] = 1; + delphi_reserved_method["destroy"] = 1; + delphi_reserved_method["read"] = 1; + delphi_reserved_method["write"] = 1; + + delphi_reserved_method_exception["setinnerexception"] = 1; + delphi_reserved_method_exception["setstackinfo"] = 1; + delphi_reserved_method_exception["getstacktrace"] = 1; + delphi_reserved_method_exception["raisingexception"] = 1; + delphi_reserved_method_exception["createfmt"] = 1; + delphi_reserved_method_exception["createres"] = 1; + delphi_reserved_method_exception["createresfmt"] = 1; + delphi_reserved_method_exception["createhelp"] = 1; + delphi_reserved_method_exception["createfmthelp"] = 1; + delphi_reserved_method_exception["createreshelp"] = 1; + delphi_reserved_method_exception["createresfmthelp"] = 1; + delphi_reserved_method_exception["getbaseexception"] = 1; + delphi_reserved_method_exception["baseexception"] = 1; + delphi_reserved_method_exception["helpcontext"] = 1; + delphi_reserved_method_exception["innerexception"] = 1; + delphi_reserved_method_exception["message"] = 1; + delphi_reserved_method_exception["stacktrace"] = 1; + delphi_reserved_method_exception["stackinfo"] = 1; + delphi_reserved_method_exception["getexceptionstackinfoproc"] = 1; + delphi_reserved_method_exception["getstackinfostringproc"] = 1; + delphi_reserved_method_exception["cleanupstackinfoproc"] = 1; + delphi_reserved_method_exception["raiseouterexception"] = 1; + delphi_reserved_method_exception["throwouterexception"] = 1; +} + +void t_delphi_generator::add_delphi_uses_list(string unitname) { + vector<std::string>::const_iterator s_iter; + bool found = false; + for (s_iter = uses_list.begin(); s_iter != uses_list.end(); ++s_iter) { + if ((*s_iter) == unitname) { + found = true; + break; + } + } + if (!found) { + uses_list.push_back(unitname); + } +} + +void t_delphi_generator::init_generator() { + indent_impl_ = 0; + namespace_name_ = program_->get_namespace("delphi"); + has_forward = false; + has_enum = false; + has_const = false; + create_keywords(); + + add_delphi_uses_list("Classes"); + add_delphi_uses_list("SysUtils"); + add_delphi_uses_list("Generics.Collections"); + if(async_) { + add_delphi_uses_list("System.Threading"); + } + + add_delphi_uses_list("Thrift"); + add_delphi_uses_list("Thrift.Utils"); + add_delphi_uses_list("Thrift.Collections"); + add_delphi_uses_list("Thrift.Protocol"); + add_delphi_uses_list("Thrift.Transport"); + if (register_types_) { + add_delphi_uses_list("Thrift.TypeRegistry"); + } + + init_known_types_list(); + + string unitname, nsname; + const vector<t_program*>& includes = program_->get_includes(); + for (auto include : includes) { + unitname = include->get_name(); + nsname = include->get_namespace("delphi"); + if ("" != nsname) { + unitname = normalize_name(nsname); + } + add_delphi_uses_list(unitname); + } + + MKDIR(get_out_dir().c_str()); +} + +void t_delphi_generator::close_generator() { + std::string unitname = program_name_; + if ("" != namespace_name_) { + unitname = namespace_name_; + } + + for (int i = 0; i < (int)unitname.size(); i++) { + if (unitname[i] == ' ') { + unitname.replace(i, 1, "_"); + } + } + + unitname = normalize_name(unitname); + + std::string f_name = get_out_dir() + "/" + unitname + ".pas"; + ofstream_with_content_based_conditional_update f_all; + + f_all.open(f_name); + + f_all << autogen_comment() << endl; + generate_delphi_doc(f_all, program_); + f_all << "unit " << unitname << ";" << endl << endl; + f_all << "interface" << endl << endl; + f_all << "uses" << endl; + + indent_up(); + + vector<std::string>::const_iterator s_iter; + for (s_iter = uses_list.begin(); s_iter != uses_list.end(); ++s_iter) { + if (s_iter != uses_list.begin()) { + f_all << ","; + f_all << endl; + } + indent(f_all) << *s_iter; + } + + f_all << ";" << endl << endl; + + indent_down(); + + string tmp_unit(unitname); + for (int i = 0; i < (int)tmp_unit.size(); i++) { + if (tmp_unit[i] == '.') { + tmp_unit.replace(i, 1, "_"); + } + } + + f_all << "const" << endl; + indent_up(); + indent(f_all) << "c" << tmp_unit + << "_Option_AnsiStr_Binary = " << (ansistr_binary_ ? "True" : "False") << ";" + << endl; + indent(f_all) << "c" << tmp_unit + << "_Option_Register_Types = " << (register_types_ ? "True" : "False") << ";" + << endl; + indent(f_all) << "c" << tmp_unit + << "_Option_ConstPrefix = " << (constprefix_ ? "True" : "False") << ";" << endl; + indent(f_all) << "c" << tmp_unit << "_Option_Events = " << (events_ ? "True" : "False") + << ";" << endl; + indent(f_all) << "c" << tmp_unit << "_Option_XmlDoc = " << (xmldoc_ ? "True" : "False") + << ";" << endl; + indent_down(); + + f_all << endl; + f_all << "type" << endl; + if (has_forward) { + f_all << s_forward_decr.str() << endl; + } + if (has_enum) { + indent(f_all) << endl; + indent(f_all) << "{$SCOPEDENUMS ON}" << endl << endl; + f_all << s_enum.str(); + indent(f_all) << "{$SCOPEDENUMS OFF}" << endl << endl; + } + f_all << s_struct.str(); + f_all << s_service.str(); + f_all << s_const.str(); + f_all << "implementation" << endl << endl; + f_all << s_struct_impl.str(); + f_all << s_service_impl.str(); + f_all << s_const_impl.str(); + + if (register_types_) { + f_all << endl; + f_all << "// Type factory methods and registration" << endl; + f_all << s_type_factory_funcs.str(); + f_all << "procedure RegisterTypeFactories;" << endl; + f_all << "begin" << endl; + f_all << s_type_factory_registration.str(); + f_all << "end;" << endl; + } + f_all << endl; + + string constants_class = make_constants_classname(); + + f_all << "initialization" << endl; + if (has_const) { + f_all << "{$IF CompilerVersion < 21.0} // D2010" << endl; + f_all << " " << constants_class.c_str() << "_Initialize;" << endl; + f_all << "{$IFEND}" << endl; + } + if (register_types_) { + f_all << " RegisterTypeFactories;" << endl; + } + f_all << endl; + + f_all << "finalization" << endl; + if (has_const) { + f_all << "{$IF CompilerVersion < 21.0} // D2010" << endl; + f_all << " " << constants_class.c_str() << "_Finalize;" << endl; + f_all << "{$IFEND}" << endl; + } + f_all << endl << endl; + + f_all << "end." << endl; + f_all.close(); + + if (!typedefs_pending.empty()) { + pwarning(0, "%d typedefs with unresolved type references left:\n", typedefs_pending.size()); + for (std::list<t_typedef*>::iterator iter = typedefs_pending.begin(); + typedefs_pending.end() != iter; + ++iter) { + pwarning(0, "- %s\n", (*iter)->get_symbolic().c_str()); + } + } +} + +void t_delphi_generator::delphi_type_usings(ostream& out) { + indent_up(); + indent(out) << "Classes, SysUtils, Generics.Collections, Thrift.Collections, Thrift.Protocol," + << endl; + indent(out) << "Thrift.Transport;" << endl << endl; + indent_down(); +} + +void t_delphi_generator::generate_forward_declaration(t_struct* tstruct) { + // Forward declare struct def + has_forward = true; + pverbose("forward declaration of %s\n", type_name(tstruct).c_str()); + + string what = tstruct->is_xception() ? "class" : "interface"; + + indent_up(); + indent(s_forward_decr) << type_name(tstruct, tstruct->is_xception(), true) << " = " << what << ";" + << endl; + indent_down(); + + add_defined_type(tstruct); +} + +void t_delphi_generator::generate_typedef(t_typedef* ttypedef) { + t_type* type = ttypedef->get_type(); + + // write now or save for later? + if (!is_fully_defined_type(type)) { + pverbose("typedef %s: unresolved dependencies found\n", type_name(ttypedef).c_str()); + typedefs_pending.push_back(ttypedef); + return; + } + + indent_up(); + generate_delphi_doc(s_struct, ttypedef); + indent(s_struct) << type_name(ttypedef) << " = "; + + // commented out: the benefit is not big enough to risk breaking existing code + // bool container = type->is_list() || type->is_map() || type->is_set(); + // if( ! container) + // s_struct << "type "; //the "type A = type B" syntax leads to E2574 with generics + + s_struct << type_name(ttypedef->get_type()) << ";" << endl << endl; + indent_down(); + + add_defined_type(ttypedef); +} + +bool t_delphi_generator::is_fully_defined_type(t_type* ttype) { + if ((NULL != ttype->get_program()) && (ttype->get_program() != program_)) { + t_scope* scope = ttype->get_program()->scope(); + if (NULL != scope->get_type(ttype->get_name())) { + // printf("type %s found in included scope %s\n", ttype->get_name().c_str(), + // ttype->get_program()->get_name().c_str()); + return true; + } + } + + if (ttype->is_typedef()) { + return (1 == types_known[type_name(ttype)]); + } + + if (ttype->is_base_type()) { + return (1 == types_known[base_type_name((t_base_type*)ttype)]); + } else if (ttype->is_enum()) { + return true; // enums are written first, before all other types + } else if (ttype->is_map()) { + t_map* tmap = (t_map*)ttype; + return is_fully_defined_type(tmap->get_key_type()) + && is_fully_defined_type(tmap->get_val_type()); + } else if (ttype->is_set()) { + t_set* tset = (t_set*)ttype; + return is_fully_defined_type(tset->get_elem_type()); + } else if (ttype->is_list()) { + t_list* tlist = (t_list*)ttype; + return is_fully_defined_type(tlist->get_elem_type()); + } + + return (1 == types_known[type_name(ttype)]); +} + +void t_delphi_generator::add_defined_type(t_type* ttype) { + // mark as known type + types_known[type_name(ttype)] = 1; + + // check all pending typedefs + std::list<t_typedef*>::iterator iter; + bool more = true; + while (more && (!typedefs_pending.empty())) { + more = false; + + for (iter = typedefs_pending.begin(); typedefs_pending.end() != iter; ++iter) { + t_typedef* ttypedef = (*iter); + if (is_fully_defined_type(ttypedef->get_type())) { + pverbose("typedef %s: all pending references are now resolved\n", + type_name(ttypedef).c_str()); + typedefs_pending.erase(iter); + generate_typedef(ttypedef); + more = true; + break; + } + } + } +} + +void t_delphi_generator::init_known_types_list() { + // known base types + types_known[type_name(g_type_string)] = 1; + types_known[type_name(g_type_binary)] = 1; + types_known[type_name(g_type_bool)] = 1; + types_known[type_name(g_type_i8)] = 1; + types_known[type_name(g_type_i16)] = 1; + types_known[type_name(g_type_i32)] = 1; + types_known[type_name(g_type_i64)] = 1; + types_known[type_name(g_type_double)] = 1; +} + +void t_delphi_generator::generate_enum(t_enum* tenum) { + has_enum = true; + indent_up(); + generate_delphi_doc(s_enum, tenum); + indent(s_enum) << type_name(tenum, true, true) << " = " + << "(" << endl; + indent_up(); + vector<t_enum_value*> constants = tenum->get_constants(); + if (constants.empty()) { + indent(s_enum) << "dummy = 0 // empty enums are not allowed"; + } else { + vector<t_enum_value*>::iterator c_iter; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + int value = (*c_iter)->get_value(); + if (c_iter != constants.begin()) { + s_enum << ","; + s_enum << endl; + } + generate_delphi_doc(s_enum, *c_iter); + indent(s_enum) << normalize_name((*c_iter)->get_name()) << " = " << value; + } + } + s_enum << endl; + indent_down(); + indent(s_enum) << ");" << endl << endl; + indent_down(); +} + +std::string t_delphi_generator::make_valid_delphi_identifier(std::string const& fromName) { + std::string str = fromName; + if (str.empty()) { + return str; + } + + // tests rely on this + assert(('A' < 'Z') && ('a' < 'z') && ('0' < '9')); + + // if the first letter is a number, we add an additional underscore in front of it + char c = str.at(0); + if (('0' <= c) && (c <= '9')) { + str = "_" + str; + } + + // following chars: letter, number or underscore + for (size_t i = 0; i < str.size(); ++i) { + c = str.at(i); + if ((('A' > c) || (c > 'Z')) && (('a' > c) || (c > 'z')) && (('0' > c) || (c > '9')) + && ('_' != c)) { + str.replace(i, 1, "_"); + } + } + + return str; +} + +std::string t_delphi_generator::make_constants_classname() { + if (constprefix_) { + return make_valid_delphi_identifier("T" + program_name_ + "Constants"); + } else { + return "TConstants"; // compatibility + } +} + +void t_delphi_generator::generate_consts(std::vector<t_const*> consts) { + if (consts.empty()) { + return; + } + + has_const = true; + string constants_class = make_constants_classname(); + + indent_up(); + indent(s_const) << constants_class.c_str() << " = class" << endl; + indent(s_const) << "private" << endl; + indent_up(); + vector<t_const*>::iterator c_iter; + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + if (const_needs_var((*c_iter)->get_type())) { + print_private_field(s_const, + normalize_name((*c_iter)->get_name()), + (*c_iter)->get_type(), + (*c_iter)->get_value()); + } + } + indent_down(); + indent(s_const) << "public" << endl; + indent_up(); + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + generate_delphi_doc(s_const, *c_iter); + print_const_prop(s_const, + normalize_name((*c_iter)->get_name()), + (*c_iter)->get_type(), + (*c_iter)->get_value()); + } + indent(s_const) << "{$IF CompilerVersion >= 21.0}" << endl; + indent(s_const) << "class constructor Create;" << endl; + indent(s_const) << "class destructor Destroy;" << endl; + indent(s_const) << "{$IFEND}" << endl; + indent_down(); + indent(s_const) << "end;" << endl << endl; + indent_down(); + + std::ostringstream vars, code; + + indent_up_impl(); + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + initialize_field(vars, + code, + "F" + prop_name((*c_iter)->get_name()), + (*c_iter)->get_type(), + (*c_iter)->get_value()); + } + indent_down_impl(); + + indent_impl(s_const_impl) << "{$IF CompilerVersion >= 21.0}" << endl; + indent_impl(s_const_impl) << "class constructor " << constants_class.c_str() << ".Create;" + << endl; + + if (!vars.str().empty()) { + indent_impl(s_const_impl) << "var" << endl; + s_const_impl << vars.str(); + } + indent_impl(s_const_impl) << "begin" << endl; + if (!code.str().empty()) { + s_const_impl << code.str(); + } + indent_impl(s_const_impl) << "end;" << endl << endl; + indent_impl(s_const_impl) << "class destructor " << constants_class.c_str() << ".Destroy;" + << endl; + indent_impl(s_const_impl) << "begin" << endl; + indent_up_impl(); + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + if (const_needs_var((*c_iter)->get_type())) { + finalize_field(s_const_impl, + normalize_name((*c_iter)->get_name()), + (*c_iter)->get_type(), + (*c_iter)->get_value()); + } + } + indent_impl(s_const_impl) << "inherited;" << endl; + indent_down_impl(); + indent_impl(s_const_impl) << "end;" << endl; + indent_impl(s_const_impl) << "{$ELSE}" << endl; + + vars.str(""); + code.str(""); + + indent_up_impl(); + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + if (const_needs_var((*c_iter)->get_type())) { + initialize_field(vars, + code, + constants_class + ".F" + prop_name((*c_iter)->get_name()), + (*c_iter)->get_type(), + (*c_iter)->get_value()); + } + } + indent_down_impl(); + + indent_impl(s_const_impl) << "procedure " << constants_class.c_str() << "_Initialize;" << endl; + if (!vars.str().empty()) { + indent_impl(s_const_impl) << "var" << endl; + s_const_impl << vars.str(); + } + indent_impl(s_const_impl) << "begin" << endl; + if (!code.str().empty()) { + s_const_impl << code.str(); + } + indent_impl(s_const_impl) << "end;" << endl << endl; + + indent_impl(s_const_impl) << "procedure " << constants_class.c_str() << "_Finalize;" << endl; + indent_impl(s_const_impl) << "begin" << endl; + indent_up_impl(); + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + finalize_field(s_const_impl, + normalize_name((*c_iter)->get_name()), + (*c_iter)->get_type(), + (*c_iter)->get_value(), + constants_class); + } + indent_down_impl(); + indent_impl(s_const_impl) << "end;" << endl; + indent_impl(s_const_impl) << "{$IFEND}" << endl << endl; +} + +void t_delphi_generator::print_const_def_value(std::ostream& vars, + std::ostream& out, + string name, + t_type* type, + t_const_value* value, + string cls_nm) { + + string cls_prefix; + + if (cls_nm == "") { + cls_prefix = ""; + } else { + cls_prefix = cls_nm + "."; + } + + if (type->is_struct() || type->is_xception()) { + const vector<t_field*>& fields = ((t_struct*)type)->get_members(); + vector<t_field*>::const_iterator f_iter; + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + t_type* field_type = NULL; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field_type = (*f_iter)->get_type(); + } + } + if (field_type == NULL) { + throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string(); + } + string val = render_const_value(vars, out, name, field_type, v_iter->second); + indent_impl(out) << cls_prefix << normalize_name(name) << "." + << prop_name(v_iter->first->get_string(), type->is_xception()) + << " := " << val << ";" << endl; + } + } else if (type->is_map()) { + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string key = render_const_value(vars, out, name, ktype, v_iter->first); + string val = render_const_value(vars, out, name, vtype, v_iter->second); + indent_impl(out) << cls_prefix << normalize_name(name) << "[" << key << "]" + << " := " << val << ";" << endl; + } + } else if (type->is_list() || type->is_set()) { + t_type* etype; + if (type->is_list()) { + etype = ((t_list*)type)->get_elem_type(); + } else { + etype = ((t_set*)type)->get_elem_type(); + } + + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string val = render_const_value(vars, out, name, etype, *v_iter); + indent_impl(out) << cls_prefix << normalize_name(name) << ".Add(" << val << ");" << endl; + } + } +} + +void t_delphi_generator::print_private_field(std::ostream& out, + string name, + t_type* type, + t_const_value* value) { + (void)value; + indent(out) << "class var F" << name << ": " << type_name(type) << ";" << endl; +} + +bool t_delphi_generator::const_needs_var(t_type* type) { + t_type* truetype = type; + while (truetype->is_typedef()) { + truetype = ((t_typedef*)truetype)->get_type(); + } + return (!truetype->is_base_type()); +} + +void t_delphi_generator::print_const_prop(std::ostream& out, + string name, + t_type* type, + t_const_value* value) { + (void)value; + if (const_needs_var(type)) { + indent(out) << "class property " << name << ": " << type_name(type) << " read F" << name << ";" + << endl; + } else { + std::ostringstream vars; // dummy + string v2 = render_const_value(vars, out, name, type, value); + indent(out) << "const " << name << " = " << v2 << ";" << endl; + } +} + +void t_delphi_generator::print_const_value(std::ostream& vars, + std::ostream& out, + string name, + t_type* type, + t_const_value* value) { + t_type* truetype = type; + while (truetype->is_typedef()) { + truetype = ((t_typedef*)truetype)->get_type(); + } + + if (truetype->is_base_type()) { + // already done + // string v2 = render_const_value( vars, out, name, type, value); + // indent_impl(out) << name << " := " << v2 << ";" << endl; + } else if (truetype->is_enum()) { + indent_impl(out) << name << " := " << type_name(type) << "." << value->get_identifier_name() + << ";" << endl; + } else { + string typname; + typname = type_name(truetype, true, false, type->is_xception(), type->is_xception()); + indent_impl(out) << name << " := " << typname << ".Create;" << endl; + print_const_def_value(vars, out, name, truetype, value); + } +} + +void t_delphi_generator::initialize_field(std::ostream& vars, + std::ostream& out, + string name, + t_type* type, + t_const_value* value) { + print_const_value(vars, out, name, type, value); +} + +void t_delphi_generator::finalize_field(std::ostream& out, + string name, + t_type* type, + t_const_value* value, + string cls_nm) { + (void)out; + (void)name; + (void)type; + (void)value; + (void)cls_nm; +} + +string t_delphi_generator::render_const_value(ostream& vars, + ostream& out, + string name, + t_type* type, + t_const_value* value) { + (void)name; + + t_type* truetype = type; + while (truetype->is_typedef()) { + truetype = ((t_typedef*)truetype)->get_type(); + } + + std::ostringstream render; + + if (truetype->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)truetype)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + render << "'" << get_escaped_string(value) << "'"; + break; + case t_base_type::TYPE_BOOL: + render << ((value->get_integer() > 0) ? "True" : "False"); + break; + case t_base_type::TYPE_I8: + render << "ShortInt( " << value->get_integer() << ")"; + break; + case t_base_type::TYPE_I16: + render << "SmallInt( " << value->get_integer() << ")"; + break; + case t_base_type::TYPE_I32: + render << "LongInt( " << value->get_integer() << ")"; + break; + case t_base_type::TYPE_I64: + render << "Int64( " << value->get_integer() << ")"; + break; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) { + render << value->get_integer() << ".0"; // make it a double constant by adding ".0" + } else { + render << value->get_double(); + } + break; + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + } else if (truetype->is_enum()) { + render << type_name(type, false) << "." << value->get_identifier_name(); + } else { + string t = tmp("tmp"); + vars << " " << t << " : " << type_name(type) << ";" << endl; + print_const_value(vars, out, t, type, value); + render << t; + } + + return render.str(); +} + +void t_delphi_generator::generate_struct(t_struct* tstruct) { + generate_delphi_struct(tstruct, false); +} + +void t_delphi_generator::generate_xception(t_struct* txception) { + generate_delphi_struct(txception, true); +} + +void t_delphi_generator::generate_delphi_struct(t_struct* tstruct, bool is_exception) { + indent_up(); + generate_delphi_struct_definition(s_struct, tstruct, is_exception); + indent_down(); + + add_defined_type(tstruct); + + generate_delphi_struct_impl(s_struct_impl, "", tstruct, is_exception); + if (register_types_) { + generate_delphi_struct_type_factory(s_type_factory_funcs, "", tstruct, is_exception); + generate_delphi_struct_type_factory_registration(s_type_factory_registration, + "", + tstruct, + is_exception); + } +} + +void t_delphi_generator::generate_delphi_struct_impl(ostream& out, + string cls_prefix, + t_struct* tstruct, + bool is_exception, + bool is_result, + bool is_x_factory) { + + if (is_exception && (!is_x_factory)) { + generate_delphi_struct_impl(out, cls_prefix, tstruct, is_exception, is_result, true); + } + + string cls_nm; + + string exception_factory_name; + + if (is_exception) { + exception_factory_name = normalize_clsnm(tstruct->get_name(), "", true) + "Factory"; + } + + if (is_exception) { + cls_nm = type_name(tstruct, true, (!is_x_factory), is_x_factory, true); + } else { + cls_nm = type_name(tstruct, true, false); + } + + std::ostringstream vars, code; + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + indent_up_impl(); + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* t = (*m_iter)->get_type(); + while (t->is_typedef()) { + t = ((t_typedef*)t)->get_type(); + } + if ((*m_iter)->get_value() != NULL) { + initialize_field(vars, + code, + "F" + prop_name((*m_iter)->get_name(), is_exception), + t, + (*m_iter)->get_value()); + if ((*m_iter)->get_req() != t_field::T_REQUIRED) { + indent_impl(code) << "F__isset_" << prop_name((*m_iter), is_exception) << " := True;" + << endl; + } + } + } + indent_down_impl(); + + indent_impl(out) << "constructor " << cls_prefix << cls_nm << "." + << "Create;" << endl; + + if (!vars.str().empty()) { + out << "var" << endl; + out << vars.str(); + } + + indent_impl(out) << "begin" << endl; + indent_up_impl(); + if (is_exception && (!is_x_factory)) { + indent_impl(out) << "inherited Create('');" << endl; + } else { + indent_impl(out) << "inherited;" << endl; + } + + if (!code.str().empty()) { + out << code.str(); + } + + indent_down_impl(); + indent_impl(out) << "end;" << endl << endl; + + if ((members.size() > 0) && is_exception && (!is_x_factory)) { + indent_impl(out) << "constructor " << cls_prefix << cls_nm << "." + << "Create(" << constructor_argument_list(tstruct, indent_impl()) << ");" + << endl; + indent_impl(out) << "begin" << endl; + indent_up_impl(); + indent_impl(out) << "Create;" << endl; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + string propname = prop_name((*m_iter)->get_name(), is_exception); + string param_name = constructor_param_name((*m_iter)->get_name()); + indent_impl(out) << propname << " := " << param_name << ";" << endl; + } + indent_impl(out) << "UpdateMessageProperty;" << endl; + indent_down_impl(); + indent_impl(out) << "end;" << endl << endl; + } + + indent_impl(out) << "destructor " << cls_prefix << cls_nm << "." + << "Destroy;" << endl; + indent_impl(out) << "begin" << endl; + indent_up_impl(); + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* t = (*m_iter)->get_type(); + while (t->is_typedef()) { + t = ((t_typedef*)t)->get_type(); + } + finalize_field(out, prop_name(*m_iter, is_exception), t, (*m_iter)->get_value()); + } + + indent_impl(out) << "inherited;" << endl; + indent_down_impl(); + indent_impl(out) << "end;" << endl << endl; + + if (is_exception && (!is_x_factory)) { + indent_impl(out) << "function " << cls_prefix << cls_nm << "." << exception_factory_name + << ": I" << exception_factory_name << ";" << endl; + indent_impl(out) << "begin" << endl; + indent_up_impl(); + indent_impl(out) << "if F" << exception_factory_name << " = nil" << endl; + indent_impl(out) << "then F" << exception_factory_name << " := T" << exception_factory_name << "Impl.Create;" << endl; + indent_impl(out) << endl; + indent_impl(out) << "result := F" << exception_factory_name << ";" << endl; + indent_down_impl(); + indent_impl(out) << "end;" << endl << endl; + } + + if (tstruct->is_union()) { + indent_impl(out) << "procedure " << cls_prefix << cls_nm << "." + << "ClearUnionValues;" << endl; + indent_impl(out) << "begin" << endl; + indent_up_impl(); + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* t = (*m_iter)->get_type(); + while (t->is_typedef()) { + t = ((t_typedef*)t)->get_type(); + } + + generate_delphi_clear_union_value(out, + cls_prefix, + cls_nm, + t, + *m_iter, + "F", + is_exception, + tstruct->is_union(), + is_x_factory, + exception_factory_name); + } + indent_down_impl(); + indent_impl(out) << "end;" << endl << endl; + } + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* t = (*m_iter)->get_type(); + while (t->is_typedef()) { + t = ((t_typedef*)t)->get_type(); + } + generate_delphi_property_reader_impl(out, cls_prefix, cls_nm, t, *m_iter, "F", is_exception); + generate_delphi_property_writer_impl(out, + cls_prefix, + cls_nm, + t, + *m_iter, + "F", + is_exception, + tstruct->is_union(), + is_x_factory, + exception_factory_name); + if ((*m_iter)->get_req() != t_field::T_REQUIRED) { + generate_delphi_isset_reader_impl(out, cls_prefix, cls_nm, t, *m_iter, "F", is_exception); + } + } + + if ((!is_exception) || is_x_factory) { + generate_delphi_struct_reader_impl(out, cls_prefix, tstruct, is_exception); + if (is_result) { + generate_delphi_struct_result_writer_impl(out, cls_prefix, tstruct, is_exception); + } else { + generate_delphi_struct_writer_impl(out, cls_prefix, tstruct, is_exception); + } + } + generate_delphi_struct_tostring_impl(out, cls_prefix, tstruct, is_exception, is_x_factory); + + if (is_exception && is_x_factory) { + generate_delphi_create_exception_impl(out, cls_prefix, tstruct, is_exception); + } +} + +void t_delphi_generator::print_delphi_struct_type_factory_func(ostream& out, t_struct* tstruct) { + string struct_intf_name = type_name(tstruct); + out << "Create_"; + out << struct_intf_name; + out << "_Impl"; +} + +void t_delphi_generator::generate_delphi_struct_type_factory(ostream& out, + string cls_prefix, + t_struct* tstruct, + bool is_exception, + bool is_result, + bool is_x_factory) { + (void)cls_prefix; + if (is_exception) + return; + if (is_result) + return; + if (is_x_factory) + return; + + string struct_intf_name = type_name(tstruct); + string cls_nm = type_name(tstruct, true, false); + + out << "function "; + print_delphi_struct_type_factory_func(out, tstruct); + out << ": "; + out << struct_intf_name; + out << ";" << endl; + out << "begin" << endl; + indent_up(); + indent(out) << "Result := " << cls_nm << ".Create;" << endl; + indent_down(); + out << "end;" << endl << endl; +} + +void t_delphi_generator::generate_delphi_struct_type_factory_registration(ostream& out, + string cls_prefix, + t_struct* tstruct, + bool is_exception, + bool is_result, + bool is_x_factory) { + (void)cls_prefix; + if (is_exception) + return; + if (is_result) + return; + if (is_x_factory) + return; + + string struct_intf_name = type_name(tstruct); + + indent(out) << " TypeRegistry.RegisterTypeFactory<" << struct_intf_name << ">("; + print_delphi_struct_type_factory_func(out, tstruct); + out << ");"; + out << endl; +} + +void t_delphi_generator::generate_delphi_struct_definition(ostream& out, + t_struct* tstruct, + bool is_exception, + bool in_class, + bool is_result, + bool is_x_factory) { + bool is_final = (tstruct->annotations_.find("final") != tstruct->annotations_.end()); + string struct_intf_name; + string struct_name; + string isset_name; + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + string exception_factory_name = normalize_clsnm(tstruct->get_name(), "", true) + "Factory"; + + if (is_exception) { + struct_intf_name = type_name(tstruct, false, false, true); + } else { + struct_intf_name = type_name(tstruct); + } + + if (is_exception) { + struct_name = type_name(tstruct, true, (!is_x_factory), is_x_factory); + } else { + struct_name = type_name(tstruct, true); + } + + if ((!is_exception) || is_x_factory) { + + generate_delphi_doc(out, tstruct); + indent(out) << struct_intf_name << " = interface(IBase)" << endl; + indent_up(); + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + generate_delphi_property_reader_definition(out, *m_iter, is_exception); + generate_delphi_property_writer_definition(out, *m_iter, is_exception); + } + + if (is_x_factory) { + out << endl; + indent(out) << "// Create Exception Object" << endl; + indent(out) << "function CreateException: " << type_name(tstruct, true, true) << ";" << endl; + } + + if (members.size() > 0) { + out << endl; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + generate_property(out, *m_iter, true, is_exception); + } + } + + if (members.size() > 0) { + out << endl; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if ((*m_iter)->get_req() != t_field::T_REQUIRED) { + generate_delphi_isset_reader_definition(out, *m_iter, is_exception); + } + } + } + + if (members.size() > 0) { + out << endl; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if ((*m_iter)->get_req() != t_field::T_REQUIRED) { + isset_name = "__isset_" + prop_name(*m_iter, is_exception); + indent(out) << "property " << isset_name << ": System.Boolean read Get" << isset_name << ";" + << endl; + } + } + } + + indent_down(); + indent(out) << "end;" << endl << endl; + } + + generate_delphi_doc(out, tstruct); + indent(out) << struct_name << " = "; + if (is_final) { + out << "sealed "; + } + out << "class("; + if (is_exception && (!is_x_factory)) { + out << "TException"; + } else { + out << "TInterfacedObject, IBase, ISupportsToString, " << struct_intf_name; + } + out << ")" << endl; + + if (is_exception && (!is_x_factory)) { + indent(out) << "public" << endl; + indent_up(); + indent(out) << "type" << endl; + indent_up(); + generate_delphi_struct_definition(out, tstruct, is_exception, in_class, is_result, true); + indent_down(); + indent_down(); + } + + indent(out) << "private" << endl; + indent_up(); + + if (is_exception && (!is_x_factory)) { + indent(out) << "F" << exception_factory_name << " :" << struct_intf_name << ";" << endl << endl; + } + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + indent(out) << declare_field(*m_iter, false, "F", is_exception) << endl; + } + + if (members.size() > 0) { + indent(out) << endl; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if ((*m_iter)->get_req() != t_field::T_REQUIRED) { + isset_name = "F__isset_" + prop_name(*m_iter, is_exception); + indent(out) << isset_name << ": System.Boolean;" << endl; + } + } + } + + indent(out) << endl; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + generate_delphi_property_reader_definition(out, *m_iter, is_exception); + generate_delphi_property_writer_definition(out, *m_iter, is_exception); + } + + if (tstruct->is_union()) { + out << endl; + indent(out) << "// Clear values(for union's property setter)" << endl; + indent(out) << "procedure ClearUnionValues;" << endl; + } + + if (members.size() > 0) { + out << endl; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if ((*m_iter)->get_req() != t_field::T_REQUIRED) { + isset_name = "__isset_" + prop_name(*m_iter, is_exception); + indent(out) << "function Get" << isset_name << ": System.Boolean;" << endl; + } + } + } + + indent_down(); + + indent(out) << "public" << endl; + indent_up(); + + if ((members.size() > 0) && is_exception && (!is_x_factory)) { + indent(out) << "constructor Create; overload;" << endl; + indent(out) << "constructor Create(" << constructor_argument_list(tstruct, indent()) + << "); overload;" << endl; + } else { + indent(out) << "constructor Create;" << endl; + } + + indent(out) << "destructor Destroy; override;" << endl; + + out << endl; + indent(out) << "function ToString: string; override;" << endl; + + if (is_exception && (!is_x_factory)) { + out << endl; + indent(out) << "// Exception Factory" << endl; + indent(out) << "function " << exception_factory_name << ": " << struct_intf_name << ";" << endl; + } + + if ((!is_exception) || is_x_factory) { + out << endl; + indent(out) << "// IBase" << endl; + indent(out) << "procedure Read( const iprot: IProtocol);" << endl; + indent(out) << "procedure Write( const oprot: IProtocol);" << endl; + } + + if (is_exception && is_x_factory) { + out << endl; + indent(out) << "// Create Exception Object" << endl; + indent(out) << "function CreateException: " << type_name(tstruct, true, true) << ";" << endl; + } + + if (members.size() > 0) { + out << endl; + indent(out) << "// Properties" << endl; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + generate_property(out, *m_iter, true, is_exception); + } + } + + if (members.size() > 0) { + out << endl; + indent(out) << "// isset" << endl; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if ((*m_iter)->get_req() != t_field::T_REQUIRED) { + isset_name = "__isset_" + prop_name(*m_iter, is_exception); + indent(out) << "property " << isset_name << ": System.Boolean read Get" << isset_name << ";" + << endl; + } + } + } + + indent_down(); + indent(out) << "end;" << endl << endl; +} + +void t_delphi_generator::generate_service(t_service* tservice) { + indent_up(); + generate_delphi_doc(s_service, tservice); + indent(s_service) << normalize_clsnm(service_name_, "T") << " = class" << endl; + indent(s_service) << "public" << endl; + indent_up(); + indent(s_service) << "type" << endl; + generate_service_interface(tservice); + generate_service_client(tservice); + generate_service_server(tservice); + generate_service_helpers(tservice); + indent_down(); + indent_down(); + indent(s_service) << "end;" << endl; + indent(s_service) << endl; + indent_down(); +} + +void t_delphi_generator::generate_service_interface(t_service* tservice) { + generate_service_interface(tservice,false); + if(async_) { + generate_service_interface(tservice,true); + } +} + + +void t_delphi_generator::generate_service_interface(t_service* tservice, bool for_async) { + string extends = ""; + string extends_iface = ""; + string iface_name = for_async ? "IAsync" : "Iface"; + + indent_up(); + + generate_delphi_doc(s_service, tservice); + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends(), true, true); + extends_iface = extends + "." + iface_name; + generate_delphi_doc(s_service, tservice); + indent(s_service) << iface_name << " = interface(" << extends_iface << ")" << endl; + } else { + indent(s_service) << iface_name << " = interface" << endl; + } + + indent_up(); + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_delphi_doc(s_service, *f_iter); + indent(s_service) << function_signature(*f_iter, for_async) << endl; + } + indent_down(); + indent(s_service) << "end;" << endl << endl; + + indent_down(); +} + +void t_delphi_generator::generate_service_helpers(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* ts = (*f_iter)->get_arglist(); + generate_delphi_struct_definition(s_service, ts, false, true); + generate_delphi_struct_impl(s_service_impl, + normalize_clsnm(service_name_, "T") + ".", + ts, + false); + generate_function_helpers(*f_iter); + } +} + +void t_delphi_generator::generate_service_client(t_service* tservice) { + indent_up(); + string extends = ""; + string extends_client = "TInterfacedObject"; + string implements = async_ ? "Iface, IAsync" : "Iface"; + + generate_delphi_doc(s_service, tservice); + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends(), true, true); + extends_client = extends + ".TClient"; + } + indent(s_service) << "TClient = class( " << extends_client << ", " << implements << ")" << endl; + + indent(s_service) << "public" << endl; + indent_up(); + + indent(s_service) << "constructor Create( prot: IProtocol); overload;" << endl; + + indent_impl(s_service_impl) << "constructor " << normalize_clsnm(service_name_, "T") + << ".TClient.Create( prot: IProtocol);" << endl; + indent_impl(s_service_impl) << "begin" << endl; + indent_up_impl(); + indent_impl(s_service_impl) << "Create( prot, prot );" << endl; + indent_down_impl(); + indent_impl(s_service_impl) << "end;" << endl << endl; + + indent(s_service) + << "constructor Create( const iprot: IProtocol; const oprot: IProtocol); overload;" << endl; + + indent_impl(s_service_impl) << "constructor " << normalize_clsnm(service_name_, "T") + << ".TClient.Create( const iprot: IProtocol; const oprot: IProtocol);" + << endl; + indent_impl(s_service_impl) << "begin" << endl; + indent_up_impl(); + indent_impl(s_service_impl) << "inherited Create;" << endl; + indent_impl(s_service_impl) << "iprot_ := iprot;" << endl; + indent_impl(s_service_impl) << "oprot_ := oprot;" << endl; + indent_down_impl(); + indent_impl(s_service_impl) << "end;" << endl << endl; + + indent_down(); + + if (extends.empty()) { + indent(s_service) << "protected" << endl; + indent_up(); + indent(s_service) << "iprot_: IProtocol;" << endl; + indent(s_service) << "oprot_: IProtocol;" << endl; + indent(s_service) << "seqid_: System.Integer;" << endl; + indent_down(); + + indent(s_service) << "public" << endl; + indent_up(); + indent(s_service) << "property InputProtocol: IProtocol read iprot_;" << endl; + indent(s_service) << "property OutputProtocol: IProtocol read oprot_;" << endl; + indent_down(); + } + + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator f_iter; + + indent(s_service) << "protected" << endl; + indent_up(); + + indent(s_service) << "// Iface" << endl; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + string funname = (*f_iter)->get_name(); + generate_delphi_doc(s_service, *f_iter); + indent(s_service) << function_signature(*f_iter, false) << endl; + } + + if( async_) { + indent(s_service) << endl; + indent(s_service) << "// IAsync" << endl; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + string funname = (*f_iter)->get_name(); + generate_delphi_doc(s_service, *f_iter); + indent(s_service) << function_signature(*f_iter, true) << endl; + } + } + + indent_down(); + + indent(s_service) << "public" << endl; + indent_up(); + + string full_cls = normalize_clsnm(service_name_, "T") + ".TClient"; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + string funname = (*f_iter)->get_name(); + + vector<t_field*>::const_iterator fld_iter; + t_struct* arg_struct = (*f_iter)->get_arglist(); + const vector<t_field*>& fields = arg_struct->get_members(); + + // one for sync only, two for async+sync + int mode = async_ ? 1 : 0; + while( mode >= 0) { + bool for_async = (mode != 0); + mode--; + + indent_impl(s_service_impl) << function_signature(*f_iter, for_async, full_cls) << endl; + indent_impl(s_service_impl) << "begin" << endl; + indent_up_impl(); + + t_type* ttype = (*f_iter)->get_returntype(); + if( for_async) { + if (is_void(ttype)) { + // Delphi forces us to specify a type with IFuture<T>, so we use Integer=0 for void methods + indent_impl(s_service_impl) << "result := TTask.Future<System.Integer>(function: System.Integer" << endl; + } else { + string rettype = type_name(ttype, false, true, false, true); + indent_impl(s_service_impl) << "result := TTask.Future<" << rettype << ">(function: " << rettype << endl; + } + indent_impl(s_service_impl) << "begin" << endl; + indent_up_impl(); + } + + indent_impl(s_service_impl) << "send_" << funname << "("; + + bool first = true; + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + if (first) { + first = false; + } else { + s_service_impl << ", "; + } + s_service_impl << normalize_name((*fld_iter)->get_name()); + } + s_service_impl << ");" << endl; + + if (!(*f_iter)->is_oneway()) { + s_service_impl << indent_impl(); + if (!(*f_iter)->get_returntype()->is_void()) { + s_service_impl << "Result := "; + } + s_service_impl << "recv_" << funname << "();" << endl; + } + + if( for_async) { + if (is_void(ttype)) { + indent_impl(s_service_impl) << "Result := 0;" << endl; // no IFuture<void> in Delphi + } + indent_down_impl(); + indent_impl(s_service_impl) << "end);" << endl; + } + + indent_down_impl(); + indent_impl(s_service_impl) << "end;" << endl << endl; + } + + t_function send_function(g_type_void, + string("send_") + (*f_iter)->get_name(), + (*f_iter)->get_arglist()); + + string argsname = (*f_iter)->get_name() + "_args"; + string args_clsnm = normalize_clsnm(argsname, "T"); + string args_intfnm = normalize_clsnm(argsname, "I"); + + string argsvar = tmp("_args"); + string msgvar = tmp("_msg"); + + indent(s_service) << function_signature(&send_function, false) << endl; + indent_impl(s_service_impl) << function_signature(&send_function, false, full_cls) << endl; + indent_impl(s_service_impl) << "var" << endl; + indent_up_impl(); + indent_impl(s_service_impl) << argsvar << " : " << args_intfnm << ";" << endl; + indent_impl(s_service_impl) << msgvar << " : Thrift.Protocol.TThriftMessage;" << endl; + indent_down_impl(); + indent_impl(s_service_impl) << "begin" << endl; + indent_up_impl(); + + indent_impl(s_service_impl) << "seqid_ := seqid_ + 1;" << endl; + indent_impl(s_service_impl) << "Thrift.Protocol.Init( " << msgvar << ", '" << funname + << "', " << ((*f_iter)->is_oneway() ? "TMessageType.Oneway" + : "TMessageType.Call") + << ", seqid_);" << endl; + + indent_impl(s_service_impl) << "oprot_.WriteMessageBegin( " << msgvar << " );" << endl; + indent_impl(s_service_impl) << argsvar << " := " << args_clsnm << "Impl.Create();" << endl; + + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + indent_impl(s_service_impl) << argsvar << "." << prop_name(*fld_iter) + << " := " << normalize_name((*fld_iter)->get_name()) << ";" + << endl; + } + indent_impl(s_service_impl) << argsvar << ".Write(oprot_);" << endl; + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + indent_impl(s_service_impl) << argsvar << "." << prop_name(*fld_iter) + << " := " << empty_value((*fld_iter)->get_type()) << ";" << endl; + } + + indent_impl(s_service_impl) << "oprot_.WriteMessageEnd();" << endl; + indent_impl(s_service_impl) << "oprot_.Transport.Flush();" << endl; + + indent_down_impl(); + indent_impl(s_service_impl) << "end;" << endl << endl; + + if (!(*f_iter)->is_oneway()) { + string org_resultname = (*f_iter)->get_name() + "_result"; + string result_clsnm = normalize_clsnm(org_resultname, "T"); + string result_intfnm = normalize_clsnm(org_resultname, "I"); + + t_struct noargs(program_); + t_function recv_function((*f_iter)->get_returntype(), + string("recv_") + (*f_iter)->get_name(), + &noargs, + (*f_iter)->get_xceptions()); + + t_struct* xs = (*f_iter)->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + + string exceptvar = tmp("_ex"); + string appexvar = tmp("_ax"); + string retvar = tmp("_ret"); + + indent(s_service) << function_signature(&recv_function, false) << endl; + indent_impl(s_service_impl) << function_signature(&recv_function, false, full_cls) << endl; + indent_impl(s_service_impl) << "var" << endl; + indent_up_impl(); + indent_impl(s_service_impl) << msgvar << " : Thrift.Protocol.TThriftMessage;" << endl; + if (xceptions.size() > 0) { + indent_impl(s_service_impl) << exceptvar << " : Exception;" << endl; + } + indent_impl(s_service_impl) << appexvar << " : TApplicationException;" << endl; + indent_impl(s_service_impl) << retvar << " : " << result_intfnm << ";" << endl; + + indent_down_impl(); + indent_impl(s_service_impl) << "begin" << endl; + indent_up_impl(); + indent_impl(s_service_impl) << msgvar << " := iprot_.ReadMessageBegin();" << endl; + indent_impl(s_service_impl) << "if (" << msgvar << ".Type_ = TMessageType.Exception) then" + << endl; + indent_impl(s_service_impl) << "begin" << endl; + indent_up_impl(); + indent_impl(s_service_impl) << appexvar << " := TApplicationException.Read(iprot_);" << endl; + indent_impl(s_service_impl) << "iprot_.ReadMessageEnd();" << endl; + indent_impl(s_service_impl) << "raise " << appexvar << ";" << endl; + indent_down_impl(); + indent_impl(s_service_impl) << "end;" << endl; + + indent_impl(s_service_impl) << retvar << " := " << result_clsnm << "Impl.Create();" << endl; + indent_impl(s_service_impl) << retvar << ".Read(iprot_);" << endl; + indent_impl(s_service_impl) << "iprot_.ReadMessageEnd();" << endl; + + if (!(*f_iter)->get_returntype()->is_void()) { + indent_impl(s_service_impl) << "if (" << retvar << ".__isset_success) then" << endl; + indent_impl(s_service_impl) << "begin" << endl; + indent_up_impl(); + indent_impl(s_service_impl) << "Result := " << retvar << ".Success;" << endl; + t_type* type = (*f_iter)->get_returntype(); + if (type->is_struct() || type->is_xception() || type->is_map() || type->is_list() + || type->is_set()) { + indent_impl(s_service_impl) << retvar << ".Success := nil;" << endl; + } + indent_impl(s_service_impl) << "Exit;" << endl; + indent_down_impl(); + indent_impl(s_service_impl) << "end;" << endl; + } + + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + indent_impl(s_service_impl) << "if (" << retvar << ".__isset_" << prop_name(*x_iter) + << ") then" << endl; + indent_impl(s_service_impl) << "begin" << endl; + indent_up_impl(); + indent_impl(s_service_impl) << exceptvar << " := " << retvar << "." << prop_name(*x_iter) + << ".CreateException;" << endl; + indent_impl(s_service_impl) << "raise " << exceptvar << ";" << endl; + indent_down_impl(); + indent_impl(s_service_impl) << "end;" << endl; + } + + if (!(*f_iter)->get_returntype()->is_void()) { + indent_impl(s_service_impl) + << "raise TApplicationExceptionMissingResult.Create('" + << (*f_iter)->get_name() << " failed: unknown result');" << endl; + } + + indent_down_impl(); + indent_impl(s_service_impl) << "end;" << endl << endl; + } + } + + indent_down(); + indent(s_service) << "end;" << endl << endl; +} + +void t_delphi_generator::generate_service_server(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + string extends = ""; + string extends_processor = ""; + + string full_cls = normalize_clsnm(service_name_, "T") + ".TProcessorImpl"; + + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends(), true, true); + extends_processor = extends + ".TProcessorImpl"; + indent(s_service) << "TProcessorImpl = class(" << extends_processor << ", IProcessor)" << endl; + } else { + indent(s_service) << "TProcessorImpl = class( TInterfacedObject, IProcessor)" << endl; + } + + indent(s_service) << "public" << endl; + indent_up(); + indent(s_service) << "constructor Create( iface_: Iface );" << endl; + indent(s_service) << "destructor Destroy; override;" << endl; + indent_down(); + + indent_impl(s_service_impl) << "constructor " << full_cls << ".Create( iface_: Iface );" << endl; + indent_impl(s_service_impl) << "begin" << endl; + indent_up_impl(); + if (tservice->get_extends() != NULL) { + indent_impl(s_service_impl) << "inherited Create( iface_);" << endl; + } else { + indent_impl(s_service_impl) << "inherited Create;" << endl; + } + indent_impl(s_service_impl) << "Self.iface_ := iface_;" << endl; + if (tservice->get_extends() != NULL) { + indent_impl(s_service_impl) << "ASSERT( processMap_ <> nil); // inherited" << endl; + } else { + indent_impl(s_service_impl) + << "processMap_ := TThriftDictionaryImpl<string, TProcessFunction>.Create;" << endl; + } + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + indent_impl(s_service_impl) << "processMap_.AddOrSetValue( '" << (*f_iter)->get_name() << "', " + << (*f_iter)->get_name() << "_Process);" << endl; + } + indent_down_impl(); + indent_impl(s_service_impl) << "end;" << endl << endl; + + indent_impl(s_service_impl) << "destructor " << full_cls << ".Destroy;" << endl; + indent_impl(s_service_impl) << "begin" << endl; + indent_up_impl(); + indent_impl(s_service_impl) << "inherited;" << endl; + indent_down_impl(); + indent_impl(s_service_impl) << "end;" << endl << endl; + + indent(s_service) << "private" << endl; + indent_up(); + indent(s_service) << "iface_: Iface;" << endl; + indent_down(); + + if (tservice->get_extends() == NULL) { + indent(s_service) << "protected" << endl; + indent_up(); + indent(s_service) << "type" << endl; + indent_up(); + indent(s_service) << "TProcessFunction = reference to procedure( seqid: System.Integer; const iprot: " + "IProtocol; const oprot: IProtocol" + << (events_ ? "; const events : IRequestEvents" : "") << ");" << endl; + indent_down(); + indent_down(); + indent(s_service) << "protected" << endl; + indent_up(); + indent(s_service) << "processMap_: IThriftDictionary<string, TProcessFunction>;" << endl; + indent_down(); + } + + indent(s_service) << "public" << endl; + indent_up(); + if (extends.empty()) { + indent(s_service) << "function Process( const iprot: IProtocol; const oprot: IProtocol; const " + "events : IProcessorEvents): System.Boolean;" << endl; + } else { + indent(s_service) << "function Process( const iprot: IProtocol; const oprot: IProtocol; const " + "events : IProcessorEvents): System.Boolean; reintroduce;" << endl; + } + + indent_impl(s_service_impl) << "function " << full_cls << ".Process( const iprot: IProtocol; " + "const oprot: IProtocol; const events " + ": IProcessorEvents): System.Boolean;" << endl; + ; + indent_impl(s_service_impl) << "var" << endl; + indent_up_impl(); + indent_impl(s_service_impl) << "msg : Thrift.Protocol.TThriftMessage;" << endl; + indent_impl(s_service_impl) << "fn : TProcessFunction;" << endl; + indent_impl(s_service_impl) << "x : TApplicationException;" << endl; + if (events_) { + indent_impl(s_service_impl) << "context : IRequestEvents;" << endl; + } + indent_down_impl(); + indent_impl(s_service_impl) << "begin" << endl; + indent_up_impl(); + indent_impl(s_service_impl) << "try" << endl; + indent_up_impl(); + indent_impl(s_service_impl) << "msg := iprot.ReadMessageBegin();" << endl; + indent_impl(s_service_impl) << "fn := nil;" << endl; + indent_impl(s_service_impl) << "if not processMap_.TryGetValue(msg.Name, fn)" << endl; + indent_impl(s_service_impl) << "or not Assigned(fn) then" << endl; + indent_impl(s_service_impl) << "begin" << endl; + indent_up_impl(); + indent_impl(s_service_impl) << "TProtocolUtil.Skip(iprot, TType.Struct);" << endl; + indent_impl(s_service_impl) << "iprot.ReadMessageEnd();" << endl; + indent_impl(s_service_impl) << "x := " + "TApplicationExceptionUnknownMethod.Create(" + "'Invalid method name: ''' + msg.Name + '''');" << endl; + indent_impl(s_service_impl) + << "Thrift.Protocol.Init( msg, msg.Name, TMessageType.Exception, msg.SeqID);" + << endl; + indent_impl(s_service_impl) << "oprot.WriteMessageBegin( msg);" << endl; + indent_impl(s_service_impl) << "x.Write(oprot);" << endl; + indent_impl(s_service_impl) << "oprot.WriteMessageEnd();" << endl; + indent_impl(s_service_impl) << "oprot.Transport.Flush();" << endl; + indent_impl(s_service_impl) << "Result := True;" << endl; + indent_impl(s_service_impl) << "Exit;" << endl; + indent_down_impl(); + indent_impl(s_service_impl) << "end;" << endl; + if (events_) { + indent_impl(s_service_impl) << "if events <> nil" << endl; + indent_impl(s_service_impl) << "then context := events.CreateRequestContext(msg.Name)" << endl; + indent_impl(s_service_impl) << "else context := nil;" << endl; + indent_impl(s_service_impl) << "try" << endl; + indent_up_impl(); + indent_impl(s_service_impl) << "fn(msg.SeqID, iprot, oprot, context);" << endl; + indent_down_impl(); + indent_impl(s_service_impl) << "finally" << endl; + indent_up_impl(); + indent_impl(s_service_impl) << "if context <> nil then begin" << endl; + indent_up_impl(); + indent_impl(s_service_impl) << "context.CleanupContext;" << endl; + indent_impl(s_service_impl) << "context := nil;" << endl; + indent_down_impl(); + indent_impl(s_service_impl) << "end;" << endl; + indent_down_impl(); + indent_impl(s_service_impl) << "end;" << endl; + } else { + indent_impl(s_service_impl) << "fn(msg.SeqID, iprot, oprot);" << endl; + } + indent_down_impl(); + indent_impl(s_service_impl) << "except" << endl; + indent_up_impl(); + indent_impl(s_service_impl) << "on TTransportExceptionTimedOut do begin" << endl; + indent_up_impl(); + indent_impl(s_service_impl) << "Result := True;" << endl; + indent_impl(s_service_impl) << "Exit;" << endl; + indent_down_impl(); + indent_impl(s_service_impl) << "end;" << endl; + indent_impl(s_service_impl) << "else begin" << endl; + indent_up_impl(); + indent_impl(s_service_impl) << "Result := False;" << endl; + indent_impl(s_service_impl) << "Exit;" << endl; + indent_down_impl(); + indent_impl(s_service_impl) << "end;" << endl; + indent_down_impl(); + indent_impl(s_service_impl) << "end;" << endl; + indent_impl(s_service_impl) << "Result := True;" << endl; + indent_down_impl(); + indent_impl(s_service_impl) << "end;" << endl << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_process_function(tservice, *f_iter); + } + + indent_down(); + indent(s_service) << "end;" << endl << endl; +} + +void t_delphi_generator::generate_function_helpers(t_function* tfunction) { + if (tfunction->is_oneway()) { + return; + } + + t_struct result(program_, tfunction->get_name() + "_result"); + t_field success(tfunction->get_returntype(), "Success", 0); + if (!tfunction->get_returntype()->is_void()) { + result.append(&success); + } + + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& fields = xs->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + result.append(*f_iter); + } + + generate_delphi_struct_definition(s_service, &result, false, true, true); + generate_delphi_struct_impl(s_service_impl, + normalize_clsnm(service_name_, "T") + ".", + &result, + false); +} + +void t_delphi_generator::generate_process_function(t_service* tservice, t_function* tfunction) { + (void)tservice; + string funcname = tfunction->get_name(); + string full_cls = normalize_clsnm(service_name_, "T") + ".TProcessorImpl"; + + string org_argsname = funcname + "_args"; + string args_clsnm = normalize_clsnm(org_argsname, "T"); + string args_intfnm = normalize_clsnm(org_argsname, "I"); + + string org_resultname = funcname + "_result"; + string result_clsnm = normalize_clsnm(org_resultname, "T"); + string result_intfnm = normalize_clsnm(org_resultname, "I"); + + indent(s_service) << "procedure " << funcname + << "_Process( seqid: System.Integer; const iprot: IProtocol; const oprot: IProtocol" + << (events_ ? "; const events : IRequestEvents" : "") << ");" << endl; + + if (tfunction->is_oneway()) { + indent_impl(s_service_impl) << "// one way processor" << endl; + } else { + indent_impl(s_service_impl) << "// both way processor" << endl; + } + + indent_impl(s_service_impl) + << "procedure " << full_cls << "." << funcname + << "_Process( seqid: System.Integer; const iprot: IProtocol; const oprot: IProtocol" + << (events_ ? "; const events : IRequestEvents" : "") << ");" << endl; + indent_impl(s_service_impl) << "var" << endl; + indent_up_impl(); + indent_impl(s_service_impl) << "args: " << args_intfnm << ";" << endl; + if (!tfunction->is_oneway()) { + indent_impl(s_service_impl) << "msg: Thrift.Protocol.TThriftMessage;" << endl; + indent_impl(s_service_impl) << "ret: " << result_intfnm << ";" << endl; + indent_impl(s_service_impl) << "appx : TApplicationException;" << endl; + } + + indent_down_impl(); + indent_impl(s_service_impl) << "begin" << endl; + indent_up_impl(); + + if (events_) { + indent_impl(s_service_impl) << "if events <> nil then events.PreRead;" << endl; + } + indent_impl(s_service_impl) << "args := " << args_clsnm << "Impl.Create;" << endl; + indent_impl(s_service_impl) << "args.Read(iprot);" << endl; + indent_impl(s_service_impl) << "iprot.ReadMessageEnd();" << endl; + if (events_) { + indent_impl(s_service_impl) << "if events <> nil then events.PostRead;" << endl; + } + + t_struct* xs = tfunction->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + + if (!tfunction->is_oneway()) { + indent_impl(s_service_impl) << "ret := " << result_clsnm << "Impl.Create;" << endl; + } + + indent_impl(s_service_impl) << "try" << endl; + indent_up_impl(); + + t_struct* arg_struct = tfunction->get_arglist(); + const std::vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + + s_service_impl << indent_impl(); + if (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void()) { + s_service_impl << "ret.Success := "; + } + s_service_impl << "iface_." << normalize_name(tfunction->get_name(), true) << "("; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + s_service_impl << ", "; + } + s_service_impl << "args." << prop_name(*f_iter); + } + s_service_impl << ");" << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + indent_impl(s_service_impl) << "args." << prop_name(*f_iter) + << " := " << empty_value((*f_iter)->get_type()) << ";" << endl; + } + + indent_down_impl(); + indent_impl(s_service_impl) << "except" << endl; + indent_up_impl(); + + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + indent_impl(s_service_impl) << "on E: " << type_name((*x_iter)->get_type(), true, true) + << " do begin" << endl; + indent_up_impl(); + if (!tfunction->is_oneway()) { + string factory_name = normalize_clsnm((*x_iter)->get_type()->get_name(), "", true) + + "Factory"; + indent_impl(s_service_impl) << "ret." << prop_name(*x_iter) << " := E." << factory_name << ";" + << endl; + } + indent_down_impl(); + indent_impl(s_service_impl) << "end;" << endl; + } + + indent_impl(s_service_impl) << "on E: Exception do begin" << endl; + indent_up_impl(); + if(events_) { + indent_impl(s_service_impl) << "if events <> nil then events.UnhandledError(E);" << endl; + } + if (!tfunction->is_oneway()) { + indent_impl(s_service_impl) << "appx := TApplicationExceptionInternalError.Create(E.Message);" + << endl; + indent_impl(s_service_impl) << "try" << endl; + indent_up_impl(); + if(events_) { + indent_impl(s_service_impl) << "if events <> nil then events.PreWrite;" << endl; + } + indent_impl(s_service_impl) << "Thrift.Protocol.Init( msg, '" + << tfunction->get_name() << "', TMessageType.Exception, seqid);" + << endl; + indent_impl(s_service_impl) << "oprot.WriteMessageBegin( msg);" << endl; + indent_impl(s_service_impl) << "appx.Write(oprot);" << endl; + indent_impl(s_service_impl) << "oprot.WriteMessageEnd();" << endl; + indent_impl(s_service_impl) << "oprot.Transport.Flush();" << endl; + if(events_) { + indent_impl(s_service_impl) << "if events <> nil then events.PostWrite;" << endl; + } + indent_impl(s_service_impl) << "Exit;" << endl; + indent_down_impl(); + indent_impl(s_service_impl) << "finally" << endl; + indent_up_impl(); + indent_impl(s_service_impl) << "appx.Free;" << endl; + indent_down_impl(); + indent_impl(s_service_impl) << "end;" << endl; + } + indent_down_impl(); + indent_impl(s_service_impl) << "end;" << endl; + + indent_down_impl(); + indent_impl(s_service_impl) << "end;" << endl; + + if (!tfunction->is_oneway()) { + if (events_) { + indent_impl(s_service_impl) << "if events <> nil then events.PreWrite;" << endl; + } + indent_impl(s_service_impl) << "Thrift.Protocol.Init( msg, '" + << tfunction->get_name() << "', TMessageType.Reply, seqid); " + << endl; + indent_impl(s_service_impl) << "oprot.WriteMessageBegin( msg); " << endl; + indent_impl(s_service_impl) << "ret.Write(oprot);" << endl; + indent_impl(s_service_impl) << "oprot.WriteMessageEnd();" << endl; + indent_impl(s_service_impl) << "oprot.Transport.Flush();" << endl; + if (events_) { + indent_impl(s_service_impl) << "if events <> nil then events.PostWrite;" << endl; + } + } else if (events_) { + indent_impl(s_service_impl) << "if events <> nil then events.OnewayComplete;" << endl; + } + + indent_down_impl(); + indent_impl(s_service_impl) << "end;" << endl << endl; +} + +void t_delphi_generator::generate_deserialize_field(ostream& out, + bool is_xception, + t_field* tfield, + string prefix, + ostream& local_vars) { + t_type* type = tfield->get_type(); + while (type->is_typedef()) { + type = ((t_typedef*)type)->get_type(); + } + + if (type->is_void()) { + throw "CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name(); + } + + string name = prefix + prop_name(tfield, is_xception); + + if (type->is_struct() || type->is_xception()) { + generate_deserialize_struct(out, (t_struct*)type, name, ""); + } else if (type->is_container()) { + generate_deserialize_container(out, is_xception, type, name, local_vars); + } else if (type->is_base_type() || type->is_enum()) { + indent_impl(out) << name << " := "; + + if (type->is_enum()) { + out << type_name(type, false) << "("; + } + + out << "iprot."; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + if (ansistr_binary_) { + out << "ReadAnsiString();"; + } else { + out << "ReadBinary();"; + } + } else { + out << "ReadString();"; + } + break; + case t_base_type::TYPE_BOOL: + out << "ReadBool();"; + break; + case t_base_type::TYPE_I8: + out << "ReadByte();"; + break; + case t_base_type::TYPE_I16: + out << "ReadI16();"; + break; + case t_base_type::TYPE_I32: + out << "ReadI32();"; + break; + case t_base_type::TYPE_I64: + out << "ReadI64();"; + break; + case t_base_type::TYPE_DOUBLE: + out << "ReadDouble();"; + break; + default: + throw "compiler error: no Delphi name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "ReadI32()"; + out << ");"; + } + out << endl; + } else { + printf("DO NOT KNOW HOW TO DESERIALIZE FIELD '%s' TYPE '%s'\n", + tfield->get_name().c_str(), + type_name(type).c_str()); + } +} + +void t_delphi_generator::generate_deserialize_struct(ostream& out, + t_struct* tstruct, + string name, + string prefix) { + string typ_name; + + if (tstruct->is_xception()) { + typ_name = type_name(tstruct, true, false, true, true); + } else { + typ_name = type_name(tstruct, true, false); + } + + indent_impl(out) << prefix << name << " := " << typ_name << ".Create;" << endl; + indent_impl(out) << prefix << name << ".Read(iprot);" << endl; +} + +void t_delphi_generator::generate_deserialize_container(ostream& out, + bool is_xception, + t_type* ttype, + string name, + std::ostream& local_vars) { + + string obj; + string counter; + string local_var; + + if (ttype->is_map()) { + obj = tmp("_map"); + } else if (ttype->is_set()) { + obj = tmp("_set"); + } else if (ttype->is_list()) { + obj = tmp("_list"); + } + + if (ttype->is_map()) { + local_var = obj + ": TThriftMap;"; + } else if (ttype->is_set()) { + local_var = obj + ": TThriftSet;"; + } else if (ttype->is_list()) { + local_var = obj + ": TThriftList;"; + } + local_vars << " " << local_var << endl; + counter = tmp("_i"); + local_var = counter + ": System.Integer;"; + local_vars << " " << local_var << endl; + + indent_impl(out) << name << " := " << type_name(ttype, true) << ".Create;" << endl; + + if (ttype->is_map()) { + indent_impl(out) << obj << " := iprot.ReadMapBegin();" << endl; + } else if (ttype->is_set()) { + indent_impl(out) << obj << " := iprot.ReadSetBegin();" << endl; + } else if (ttype->is_list()) { + indent_impl(out) << obj << " := iprot.ReadListBegin();" << endl; + } + + indent_impl(out) << "for " << counter << " := 0 to " << obj << ".Count - 1 do" << endl; + indent_impl(out) << "begin" << endl; + indent_up_impl(); + if (ttype->is_map()) { + generate_deserialize_map_element(out, is_xception, (t_map*)ttype, name, local_vars); + } else if (ttype->is_set()) { + generate_deserialize_set_element(out, is_xception, (t_set*)ttype, name, local_vars); + } else if (ttype->is_list()) { + generate_deserialize_list_element(out, is_xception, (t_list*)ttype, name, local_vars); + } + indent_down_impl(); + indent_impl(out) << "end;" << endl; + + if (ttype->is_map()) { + indent_impl(out) << "iprot.ReadMapEnd();" << endl; + } else if (ttype->is_set()) { + indent_impl(out) << "iprot.ReadSetEnd();" << endl; + } else if (ttype->is_list()) { + indent_impl(out) << "iprot.ReadListEnd();" << endl; + } +} + +void t_delphi_generator::generate_deserialize_map_element(ostream& out, + bool is_xception, + t_map* tmap, + string prefix, + ostream& local_vars) { + + string key = tmp("_key"); + string val = tmp("_val"); + string local_var; + + t_field fkey(tmap->get_key_type(), key); + t_field fval(tmap->get_val_type(), val); + + local_vars << " " << declare_field(&fkey) << endl; + local_vars << " " << declare_field(&fval) << endl; + + generate_deserialize_field(out, is_xception, &fkey, "", local_vars); + generate_deserialize_field(out, is_xception, &fval, "", local_vars); + + indent_impl(out) << prefix << ".AddOrSetValue( " << key << ", " << val << ");" << endl; +} + +void t_delphi_generator::generate_deserialize_set_element(ostream& out, + bool is_xception, + t_set* tset, + string prefix, + ostream& local_vars) { + string elem = tmp("_elem"); + t_field felem(tset->get_elem_type(), elem); + local_vars << " " << declare_field(&felem) << endl; + generate_deserialize_field(out, is_xception, &felem, "", local_vars); + indent_impl(out) << prefix << ".Add(" << elem << ");" << endl; +} + +void t_delphi_generator::generate_deserialize_list_element(ostream& out, + bool is_xception, + t_list* tlist, + string prefix, + ostream& local_vars) { + string elem = tmp("_elem"); + t_field felem(tlist->get_elem_type(), elem); + local_vars << " " << declare_field(&felem) << endl; + generate_deserialize_field(out, is_xception, &felem, "", local_vars); + indent_impl(out) << prefix << ".Add(" << elem << ");" << endl; +} + +void t_delphi_generator::generate_serialize_field(ostream& out, + bool is_xception, + t_field* tfield, + string prefix, + ostream& local_vars) { + (void)local_vars; + + t_type* type = tfield->get_type(); + while (type->is_typedef()) { + type = ((t_typedef*)type)->get_type(); + } + + string name = prefix + prop_name(tfield, is_xception); + + if (type->is_void()) { + throw "CANNOT GENERATE SERIALIZE CODE FOR void TYPE: " + name; + } + + if (type->is_struct() || type->is_xception()) { + generate_serialize_struct(out, (t_struct*)type, name, local_vars); + } else if (type->is_container()) { + generate_serialize_container(out, is_xception, type, name, local_vars); + } else if (type->is_base_type() || type->is_enum()) { + + indent_impl(out) << "oprot."; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + if (ansistr_binary_) { + out << "WriteAnsiString("; + } else { + out << "WriteBinary("; + } + } else { + out << "WriteString("; + } + out << name << ");"; + break; + case t_base_type::TYPE_BOOL: + out << "WriteBool(" << name << ");"; + break; + case t_base_type::TYPE_I8: + out << "WriteByte(" << name << ");"; + break; + case t_base_type::TYPE_I16: + out << "WriteI16(" << name << ");"; + break; + case t_base_type::TYPE_I32: + out << "WriteI32(" << name << ");"; + break; + case t_base_type::TYPE_I64: + out << "WriteI64(" << name << ");"; + break; + case t_base_type::TYPE_DOUBLE: + out << "WriteDouble(" << name << ");"; + break; + default: + throw "compiler error: no Delphi name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "WriteI32(System.Integer(" << name << "));"; + } + out << endl; + } else { + printf("DO NOT KNOW HOW TO SERIALIZE '%s%s' TYPE '%s'\n", + prefix.c_str(), + tfield->get_name().c_str(), + type_name(type).c_str()); + } +} + +void t_delphi_generator::generate_serialize_struct(ostream& out, + t_struct* tstruct, + string prefix, + ostream& local_vars) { + (void)local_vars; + (void)tstruct; + out << indent_impl() << prefix << ".Write(oprot);" << endl; +} + +void t_delphi_generator::generate_serialize_container(ostream& out, + bool is_xception, + t_type* ttype, + string prefix, + ostream& local_vars) { + string obj; + if (ttype->is_map()) { + obj = tmp("map"); + local_vars << " " << obj << " : TThriftMap;" << endl; + indent_impl(out) << "Thrift.Protocol.Init( " << obj << ", " + << type_to_enum(((t_map*)ttype)->get_key_type()) << ", " + << type_to_enum(((t_map*)ttype)->get_val_type()) << ", " << prefix + << ".Count);" << endl; + indent_impl(out) << "oprot.WriteMapBegin( " << obj << ");" << endl; + } else if (ttype->is_set()) { + obj = tmp("set_"); + local_vars << " " << obj << " : TThriftSet;" << endl; + indent_impl(out) << "Thrift.Protocol.Init( " << obj << ", " + << type_to_enum(((t_set*)ttype)->get_elem_type()) << ", " << prefix + << ".Count);" << endl; + indent_impl(out) << "oprot.WriteSetBegin( " << obj << ");" << endl; + } else if (ttype->is_list()) { + obj = tmp("list_"); + local_vars << " " << obj << " : TThriftList;" << endl; + indent_impl(out) << "Thrift.Protocol.Init( " << obj << ", " + << type_to_enum(((t_list*)ttype)->get_elem_type()) << ", " << prefix + << ".Count);" << endl; + indent_impl(out) << "oprot.WriteListBegin( " << obj << ");" << endl; + } + + string iter = tmp("_iter"); + if (ttype->is_map()) { + local_vars << " " << iter << ": " << type_name(((t_map*)ttype)->get_key_type()) << ";" << endl; + indent_impl(out) << "for " << iter << " in " << prefix << ".Keys do" << endl; + indent_impl(out) << "begin" << endl; + indent_up_impl(); + } else if (ttype->is_set()) { + local_vars << " " << iter << ": " << type_name(((t_set*)ttype)->get_elem_type()) << ";" + << endl; + indent_impl(out) << "for " << iter << " in " << prefix << " do" << endl; + indent_impl(out) << "begin" << endl; + indent_up_impl(); + } else if (ttype->is_list()) { + local_vars << " " << iter << ": " << type_name(((t_list*)ttype)->get_elem_type()) << ";" + << endl; + indent_impl(out) << "for " << iter << " in " << prefix << " do" << endl; + indent_impl(out) << "begin" << endl; + indent_up_impl(); + } + + if (ttype->is_map()) { + generate_serialize_map_element(out, is_xception, (t_map*)ttype, iter, prefix, local_vars); + } else if (ttype->is_set()) { + generate_serialize_set_element(out, is_xception, (t_set*)ttype, iter, local_vars); + } else if (ttype->is_list()) { + generate_serialize_list_element(out, is_xception, (t_list*)ttype, iter, local_vars); + } + + indent_down_impl(); + indent_impl(out) << "end;" << endl; + + if (ttype->is_map()) { + indent_impl(out) << "oprot.WriteMapEnd();" << endl; + } else if (ttype->is_set()) { + indent_impl(out) << "oprot.WriteSetEnd();" << endl; + } else if (ttype->is_list()) { + indent_impl(out) << "oprot.WriteListEnd();" << endl; + } +} + +void t_delphi_generator::generate_serialize_map_element(ostream& out, + bool is_xception, + t_map* tmap, + string iter, + string map, + ostream& local_vars) { + t_field kfield(tmap->get_key_type(), iter); + generate_serialize_field(out, is_xception, &kfield, "", local_vars); + t_field vfield(tmap->get_val_type(), map + "[" + iter + "]"); + generate_serialize_field(out, is_xception, &vfield, "", local_vars); +} + +void t_delphi_generator::generate_serialize_set_element(ostream& out, + bool is_xception, + t_set* tset, + string iter, + ostream& local_vars) { + t_field efield(tset->get_elem_type(), iter); + generate_serialize_field(out, is_xception, &efield, "", local_vars); +} + +void t_delphi_generator::generate_serialize_list_element(ostream& out, + bool is_xception, + t_list* tlist, + string iter, + ostream& local_vars) { + t_field efield(tlist->get_elem_type(), iter); + generate_serialize_field(out, is_xception, &efield, "", local_vars); +} + +void t_delphi_generator::generate_property(ostream& out, + t_field* tfield, + bool isPublic, + bool is_xception) { + generate_delphi_property(out, is_xception, tfield, isPublic, "Get"); +} + +void t_delphi_generator::generate_delphi_property(ostream& out, + bool struct_is_xception, + t_field* tfield, + bool isPublic, + std::string fieldPrefix) { + (void)isPublic; + + t_type* ftype = tfield->get_type(); + bool is_xception = ftype->is_xception(); + generate_delphi_doc(out, tfield); + indent(out) << "property " << prop_name(tfield, struct_is_xception) << ": " + << type_name(ftype, false, true, is_xception, true) << " read " + << fieldPrefix + prop_name(tfield, struct_is_xception) << " write Set" + << prop_name(tfield, struct_is_xception) << ";" << endl; +} + +std::string t_delphi_generator::prop_name(t_field* tfield, bool is_xception) { + return prop_name(tfield->get_name(), is_xception); +} + +std::string t_delphi_generator::prop_name(string name, bool is_xception) { + string ret = name; + ret[0] = toupper(ret[0]); + return normalize_name(ret, true, is_xception); +} + +std::string t_delphi_generator::constructor_param_name(string name) { + string ret = name; + ret[0] = toupper(ret[0]); + ret = "A" + ret; + return normalize_name(ret, false, false); +} + +string t_delphi_generator::normalize_clsnm(string clsnm, string prefix, bool b_no_check_keyword) { + if (clsnm.size() > 0) { + clsnm[0] = toupper(clsnm[0]); + } + if (b_no_check_keyword) { + return prefix + clsnm; + } else { + return normalize_name(prefix + clsnm); + } +} + +string t_delphi_generator::type_name(t_type* ttype, + bool b_cls, + bool b_no_postfix, + bool b_exception_factory, + bool b_full_exception_factory) { + + if (ttype->is_typedef()) { + t_typedef* tdef = (t_typedef*)ttype; + if (tdef->is_forward_typedef()) { // forward types according to THRIFT-2421 + if (tdef->get_type() != NULL) { + return type_name(tdef->get_type(), + b_cls, + b_no_postfix, + b_exception_factory, + b_full_exception_factory); + } else { + throw "unresolved forward declaration: " + tdef->get_symbolic(); + } + } else { + return normalize_name("T" + tdef->get_symbolic()); + } + } + + string typ_nm; + + string s_factory; + + if (ttype->is_base_type()) { + return base_type_name((t_base_type*)ttype); + } else if (ttype->is_enum()) { + b_cls = true; + b_no_postfix = true; + } else if (ttype->is_map()) { + t_map* tmap = (t_map*)ttype; + if (b_cls) { + typ_nm = "TThriftDictionaryImpl"; + } else { + typ_nm = "IThriftDictionary"; + } + return typ_nm + "<" + type_name(tmap->get_key_type()) + ", " + type_name(tmap->get_val_type()) + + ">"; + } else if (ttype->is_set()) { + t_set* tset = (t_set*)ttype; + if (b_cls) { + typ_nm = "THashSetImpl"; + } else { + typ_nm = "IHashSet"; + } + return typ_nm + "<" + type_name(tset->get_elem_type()) + ">"; + } else if (ttype->is_list()) { + t_list* tlist = (t_list*)ttype; + if (b_cls) { + typ_nm = "TThriftListImpl"; + } else { + typ_nm = "IThriftList"; + } + return typ_nm + "<" + type_name(tlist->get_elem_type()) + ">"; + } + + string type_prefix; + + if (b_cls) { + type_prefix = "T"; + } else { + type_prefix = "I"; + } + + string nm = normalize_clsnm(ttype->get_name(), type_prefix); + + if (b_exception_factory) { + nm = nm + "Factory"; + } + + if (b_cls) { + if (!b_no_postfix) { + nm = nm + "Impl"; + } + } + + if (b_exception_factory && b_full_exception_factory) { + return type_name(ttype, true, true, false, false) + "." + nm; + } + + return nm; +} + +// returns "const " for some argument types +string t_delphi_generator::input_arg_prefix(t_type* ttype) { + + // base types + if (ttype->is_base_type()) { + switch (((t_base_type*)ttype)->get_base()) { + + // these should be const'ed for optimal performamce + case t_base_type::TYPE_STRING: // refcounted pointer + case t_base_type::TYPE_I64: // larger than 32 bit + case t_base_type::TYPE_DOUBLE: // larger than 32 bit + return "const "; + + // all others don't need to be + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_BOOL: + case t_base_type::TYPE_VOID: + return ""; + + // we better always report any unknown types + default: + throw "compiler error: no input_arg_prefix() for base type " + + t_base_type::t_base_name(((t_base_type*)ttype)->get_base()); + } + + // enums + } else if (ttype->is_enum()) { + return ""; // usually <= 32 bit + + // containers + } else if (ttype->is_map()) { + return "const "; // refcounted pointer + + } else if (ttype->is_set()) { + return "const "; // refcounted pointer + + } else if (ttype->is_list()) { + return "const "; // refcounted pointer + } + + // any other type, either TSomething or ISomething + return "const "; // possibly refcounted pointer +} + +string t_delphi_generator::base_type_name(t_base_type* tbase) { + switch (tbase->get_base()) { + case t_base_type::TYPE_VOID: + // no "void" in Delphi language + return ""; + case t_base_type::TYPE_STRING: + if (tbase->is_binary()) { + if (ansistr_binary_) { + return "System.AnsiString"; + } else { + return "SysUtils.TBytes"; + } + } else { + return "System.string"; + } + case t_base_type::TYPE_BOOL: + return "System.Boolean"; + case t_base_type::TYPE_I8: + return "System.ShortInt"; + case t_base_type::TYPE_I16: + return "System.SmallInt"; + case t_base_type::TYPE_I32: + return "System.Integer"; + case t_base_type::TYPE_I64: + return "System.Int64"; + case t_base_type::TYPE_DOUBLE: + return "System.Double"; + default: + throw "compiler error: no Delphi name for base type " + + t_base_type::t_base_name(tbase->get_base()); + } +} + +string t_delphi_generator::declare_field(t_field* tfield, + bool init, + std::string prefix, + bool is_xception_class) { + (void)init; + + t_type* ftype = tfield->get_type(); + bool is_xception = ftype->is_xception(); + + string result = prefix + prop_name(tfield, is_xception_class) + ": " + + type_name(ftype, false, true, is_xception, true) + ";"; + return result; +} + +string t_delphi_generator::function_signature(t_function* tfunction, + bool for_async, + std::string full_cls, + bool is_xception) { + t_type* ttype = tfunction->get_returntype(); + string prefix; + if (full_cls == "") { + prefix = ""; + } else { + prefix = full_cls + "."; + } + + if( for_async) { + if (is_void(ttype)) { + return "function " + prefix + normalize_name(tfunction->get_name(), true, is_xception) + "Async(" + + argument_list(tfunction->get_arglist()) + "): IFuture<Integer>;"; // no IFuture<void> in Delphi + } else { + return "function " + prefix + normalize_name(tfunction->get_name(), true, is_xception) + "Async(" + + argument_list(tfunction->get_arglist()) + "): IFuture<" + + type_name(ttype, false, true, is_xception, true) + ">;"; + } + } else { + if (is_void(ttype)) { + return "procedure " + prefix + normalize_name(tfunction->get_name(), true, is_xception) + "(" + + argument_list(tfunction->get_arglist()) + ");"; + } else { + return "function " + prefix + normalize_name(tfunction->get_name(), true, is_xception) + "(" + + argument_list(tfunction->get_arglist()) + "): " + + type_name(ttype, false, true, is_xception, true) + ";"; + } + } +} + +string t_delphi_generator::argument_list(t_struct* tstruct) { + string result = ""; + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + t_type* tt; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + result += "; "; + } + + tt = (*f_iter)->get_type(); + result += input_arg_prefix(tt); // const? + result += normalize_name((*f_iter)->get_name()) + ": " + + type_name(tt, false, true, tt->is_xception(), true); + } + return result; +} + +string t_delphi_generator::constructor_argument_list(t_struct* tstruct, string current_indent) { + ostringstream result; + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + t_type* tt; + string line = ""; + string newline_indent = current_indent + " "; + + bool firstline = true; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + line += ";"; + } + + if (line.size() > 80) { + if (firstline) { + result << endl << newline_indent; + firstline = false; + } + result << line << endl; + line = newline_indent; + } else if (line.size() > 0) { + line += " "; + } + + tt = (*f_iter)->get_type(); + line += input_arg_prefix(tt); // const? + line += constructor_param_name((*f_iter)->get_name()) + ": " + + type_name(tt, false, true, tt->is_xception(), true); + } + + if (line.size() > 0) { + result << line; + } + + string result_str; + + if (firstline) { + result_str = " " + result.str(); + } else { + result_str = result.str(); + } + + return result_str; +} + +string t_delphi_generator::type_to_enum(t_type* type) { + while (type->is_typedef()) { + type = ((t_typedef*)type)->get_type(); + } + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + return "TType.String_"; + case t_base_type::TYPE_BOOL: + return "TType.Bool_"; + case t_base_type::TYPE_I8: + return "TType.Byte_"; + case t_base_type::TYPE_I16: + return "TType.I16"; + case t_base_type::TYPE_I32: + return "TType.I32"; + case t_base_type::TYPE_I64: + return "TType.I64"; + case t_base_type::TYPE_DOUBLE: + return "TType.Double_"; + } + } else if (type->is_enum()) { + return "TType.I32"; + } else if (type->is_struct() || type->is_xception()) { + return "TType.Struct"; + } else if (type->is_map()) { + return "TType.Map"; + } else if (type->is_set()) { + return "TType.Set_"; + } else if (type->is_list()) { + return "TType.List"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +string t_delphi_generator::empty_value(t_type* type) { + while (type->is_typedef()) { + type = ((t_typedef*)type)->get_type(); + } + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + return "0"; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + if (ansistr_binary_) { + return "''"; + } else { + return "nil"; + } + } else { + return "''"; + } + case t_base_type::TYPE_BOOL: + return "False"; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + return "0"; + case t_base_type::TYPE_DOUBLE: + return "0.0"; + } + } else if (type->is_enum()) { + return "T" + type->get_name() + "(0)"; + } else if (type->is_struct() || type->is_xception()) { + return "nil"; + } else if (type->is_map()) { + return "nil"; + } else if (type->is_set()) { + return "nil"; + } else if (type->is_list()) { + return "nil"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +void t_delphi_generator::generate_delphi_property_writer_definition(ostream& out, + t_field* tfield, + bool is_xception_class) { + t_type* ftype = tfield->get_type(); + bool is_xception = ftype->is_xception(); + + indent(out) << "procedure Set" << prop_name(tfield, is_xception_class) + << "( const Value: " << type_name(ftype, false, true, is_xception, true) << ");" + << endl; +} + +void t_delphi_generator::generate_delphi_property_reader_definition(ostream& out, + t_field* tfield, + bool is_xception_class) { + t_type* ftype = tfield->get_type(); + bool is_xception = ftype->is_xception(); + + indent(out) << "function Get" << prop_name(tfield, is_xception_class) << ": " + << type_name(ftype, false, true, is_xception, true) << ";" << endl; +} + +void t_delphi_generator::generate_delphi_isset_reader_definition(ostream& out, + t_field* tfield, + bool is_xception) { + indent(out) << "function Get__isset_" << prop_name(tfield, is_xception) << ": System.Boolean;" << endl; +} + +void t_delphi_generator::generate_delphi_clear_union_value(ostream& out, + std::string cls_prefix, + std::string name, + t_type* type, + t_field* tfield, + std::string fieldPrefix, + bool is_xception_class, + bool is_union, + bool is_xception_factory, + std::string xception_factory_name) { + (void)cls_prefix; + (void)name; + (void)type; + (void)is_union; + (void)is_xception_factory; + (void)xception_factory_name; + + t_type* ftype = tfield->get_type(); + bool is_xception = ftype->is_xception(); + + indent_impl(out) << "if F__isset_" << prop_name(tfield, is_xception_class) << " then begin" + << endl; + indent_up_impl(); + indent_impl(out) << "F__isset_" << prop_name(tfield, is_xception_class) << " := False;" << endl; + indent_impl(out) << fieldPrefix << prop_name(tfield, is_xception_class) << " := " + << "Default( " << type_name(ftype, false, true, is_xception, true) << ");" + << endl; + indent_down_impl(); + indent_impl(out) << "end;" << endl; +} + +void t_delphi_generator::generate_delphi_property_writer_impl(ostream& out, + std::string cls_prefix, + std::string name, + t_type* type, + t_field* tfield, + std::string fieldPrefix, + bool is_xception_class, + bool is_union, + bool is_xception_factory, + std::string xception_factory_name) { + (void)type; + + t_type* ftype = tfield->get_type(); + bool is_xception = ftype->is_xception(); + + indent_impl(out) << "procedure " << cls_prefix << name << "." + << "Set" << prop_name(tfield, is_xception_class) + << "( const Value: " << type_name(ftype, false, true, is_xception, true) << ");" + << endl; + indent_impl(out) << "begin" << endl; + indent_up_impl(); + if (is_union) { + indent_impl(out) << "ClearUnionValues;" << endl; + } + if (tfield->get_req() != t_field::T_REQUIRED) { + indent_impl(out) << "F__isset_" << prop_name(tfield, is_xception_class) << " := True;" << endl; + } + indent_impl(out) << fieldPrefix << prop_name(tfield, is_xception_class) << " := Value;" << endl; + + if (is_xception_class && (!is_xception_factory)) { + indent_impl(out) << xception_factory_name << "." << prop_name(tfield, is_xception_class) + << " := Value;" << endl; + } + + indent_down_impl(); + indent_impl(out) << "end;" << endl << endl; +} + +void t_delphi_generator::generate_delphi_property_reader_impl(ostream& out, + std::string cls_prefix, + std::string name, + t_type* type, + t_field* tfield, + std::string fieldPrefix, + bool is_xception_class) { + (void)type; + + t_type* ftype = tfield->get_type(); + bool is_xception = ftype->is_xception(); + + indent_impl(out) << "function " << cls_prefix << name << "." + << "Get" << prop_name(tfield, is_xception_class) << ": " + << type_name(ftype, false, true, is_xception, true) << ";" << endl; + indent_impl(out) << "begin" << endl; + indent_up_impl(); + indent_impl(out) << "Result := " << fieldPrefix << prop_name(tfield, is_xception_class) << ";" + << endl; + indent_down_impl(); + indent_impl(out) << "end;" << endl << endl; +} + +void t_delphi_generator::generate_delphi_isset_reader_impl(ostream& out, + std::string cls_prefix, + std::string name, + t_type* type, + t_field* tfield, + std::string fieldPrefix, + bool is_xception) { + (void)type; + + string isset_name = "__isset_" + prop_name(tfield, is_xception); + indent_impl(out) << "function " << cls_prefix << name << "." + << "Get" << isset_name << ": System.Boolean;" << endl; + indent_impl(out) << "begin" << endl; + indent_up_impl(); + indent_impl(out) << "Result := " << fieldPrefix << isset_name << ";" << endl; + indent_down_impl(); + indent_impl(out) << "end;" << endl << endl; +} + +void t_delphi_generator::generate_delphi_create_exception_impl(ostream& out, + string cls_prefix, + t_struct* tstruct, + bool is_exception) { + (void)cls_prefix; + + string exception_cls_nm = type_name(tstruct, true, true); + string cls_nm = type_name(tstruct, true, false, is_exception, is_exception); + + indent_impl(out) << "function " << cls_nm << ".CreateException: " << exception_cls_nm << ";" + << endl; + + indent_impl(out) << "begin" << endl; + indent_up_impl(); + + indent_impl(out) << "Result := " << exception_cls_nm << ".Create;" << endl; + string factory_name = normalize_clsnm(tstruct->get_name(), "", true) + "Factory"; + indent_impl(out) << "Result.F" << factory_name << " := Self;" << endl; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + string propname; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + propname = prop_name(*f_iter, is_exception); + if ((*f_iter)->get_req() != t_field::T_REQUIRED) { + indent_impl(out) << "if __isset_" << propname << " then begin" << endl; + indent_up_impl(); + } + indent_impl(out) << "Result." << propname << " := " << propname << ";" << endl; + if ((*f_iter)->get_req() != t_field::T_REQUIRED) { + indent_down_impl(); + indent_impl(out) << "end;" << endl; + } + } + + indent_impl(out) << "Result.UpdateMessageProperty;" << endl; + + indent_down_impl(); + indent_impl(out) << "end;" << endl << endl; +} + +void t_delphi_generator::generate_delphi_struct_reader_impl(ostream& out, + string cls_prefix, + t_struct* tstruct, + bool is_exception) { + + ostringstream local_vars; + ostringstream code_block; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + indent_impl(code_block) << "begin" << endl; + indent_up_impl(); + + indent_impl(local_vars) << "tracker : IProtocolRecursionTracker;" << endl; + indent_impl(code_block) << "tracker := iprot.NextRecursionLevel;" << endl; + + // local bools for required fields + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_REQUIRED) { + indent_impl(local_vars) << "_req_isset_" << prop_name(*f_iter, is_exception) << " : System.Boolean;" + << endl; + indent_impl(code_block) << "_req_isset_" << prop_name(*f_iter, is_exception) << " := FALSE;" + << endl; + } + } + + indent_impl(code_block) << "struc := iprot.ReadStructBegin;" << endl; + + indent_impl(code_block) << "try" << endl; + indent_up_impl(); + + indent_impl(code_block) << "while (true) do" << endl; + indent_impl(code_block) << "begin" << endl; + indent_up_impl(); + + indent_impl(code_block) << "field_ := iprot.ReadFieldBegin();" << endl; + + indent_impl(code_block) << "if (field_.Type_ = TType.Stop) then" << endl; + indent_impl(code_block) << "begin" << endl; + indent_up_impl(); + indent_impl(code_block) << "Break;" << endl; + indent_down_impl(); + indent_impl(code_block) << "end;" << endl; + + bool first = true; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + + if (first) { + indent_impl(code_block) << "case field_.ID of" << endl; + indent_up_impl(); + } + + first = false; + if (f_iter != fields.begin()) { + code_block << ";" << endl; + } + indent_impl(code_block) << (*f_iter)->get_key() << ": begin" << endl; + indent_up_impl(); + indent_impl(code_block) << "if (field_.Type_ = " << type_to_enum((*f_iter)->get_type()) + << ") then begin" << endl; + indent_up_impl(); + + generate_deserialize_field(code_block, is_exception, *f_iter, "Self.", local_vars); + + // required field? + if ((*f_iter)->get_req() == t_field::T_REQUIRED) { + indent_impl(code_block) << "_req_isset_" << prop_name(*f_iter, is_exception) << " := TRUE;" + << endl; + } + + indent_down_impl(); + + indent_impl(code_block) << "end else begin" << endl; + indent_up_impl(); + indent_impl(code_block) << "TProtocolUtil.Skip(iprot, field_.Type_);" << endl; + indent_down_impl(); + indent_impl(code_block) << "end;" << endl; + indent_down_impl(); + indent_impl(code_block) << "end"; + } + + if (!first) { + code_block << endl; + indent_impl(code_block) << "else begin" << endl; + indent_up_impl(); + } + + indent_impl(code_block) << "TProtocolUtil.Skip(iprot, field_.Type_);" << endl; + + if (!first) { + indent_down_impl(); + indent_impl(code_block) << "end;" << endl; + indent_down_impl(); + indent_impl(code_block) << "end;" << endl; + } + + indent_impl(code_block) << "iprot.ReadFieldEnd;" << endl; + + indent_down_impl(); + + indent_impl(code_block) << "end;" << endl; + indent_down_impl(); + + indent_impl(code_block) << "finally" << endl; + indent_up_impl(); + indent_impl(code_block) << "iprot.ReadStructEnd;" << endl; + indent_down_impl(); + indent_impl(code_block) << "end;" << endl; + + // all required fields have been read? + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_REQUIRED) { + indent_impl(code_block) << "if not _req_isset_" << prop_name(*f_iter, is_exception) << endl; + indent_impl(code_block) + << "then raise TProtocolExceptionInvalidData.Create(" + << "'required field " << prop_name(*f_iter, is_exception) << " not set');" + << endl; + } + } + + indent_down_impl(); + indent_impl(code_block) << "end;" << endl << endl; + + string cls_nm; + + cls_nm = type_name(tstruct, true, false, is_exception, is_exception); + + indent_impl(out) << "procedure " << cls_prefix << cls_nm << ".Read( const iprot: IProtocol);" + << endl; + indent_impl(out) << "var" << endl; + indent_up_impl(); + indent_impl(out) << "field_ : TThriftField;" << endl; + indent_impl(out) << "struc : TThriftStruct;" << endl; + indent_down_impl(); + out << local_vars.str() << endl; + out << code_block.str(); +} + +void t_delphi_generator::generate_delphi_struct_result_writer_impl(ostream& out, + string cls_prefix, + t_struct* tstruct, + bool is_exception) { + + ostringstream local_vars; + ostringstream code_block; + + string name = tstruct->get_name(); + const vector<t_field*>& fields = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator f_iter; + + indent_impl(code_block) << "begin" << endl; + indent_up_impl(); + + indent_impl(local_vars) << "tracker : IProtocolRecursionTracker;" << endl; + indent_impl(code_block) << "tracker := oprot.NextRecursionLevel;" << endl; + + indent_impl(code_block) << "Thrift.Protocol.Init( struc, '" << name << "');" << endl; + indent_impl(code_block) << "oprot.WriteStructBegin(struc);" << endl; + + if (fields.size() > 0) { + indent_impl(code_block) << "Thrift.Protocol.Init( field_);" << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + indent_impl(code_block) << "if (__isset_" << prop_name(*f_iter, is_exception) << ") then" + << endl; + indent_impl(code_block) << "begin" << endl; + indent_up_impl(); + indent_impl(code_block) << "field_.Name := '" << (*f_iter)->get_name() << "';" << endl; + indent_impl(code_block) << "field_.Type_ := " << type_to_enum((*f_iter)->get_type()) << ";" + << endl; + indent_impl(code_block) << "field_.ID := " << (*f_iter)->get_key() << ";" << endl; + indent_impl(code_block) << "oprot.WriteFieldBegin(field_);" << endl; + generate_serialize_field(code_block, is_exception, *f_iter, "Self.", local_vars); + indent_impl(code_block) << "oprot.WriteFieldEnd();" << endl; + indent_down_impl(); + } + } + + indent_impl(code_block) << "oprot.WriteFieldStop();" << endl; + indent_impl(code_block) << "oprot.WriteStructEnd();" << endl; + + indent_down_impl(); + indent_impl(code_block) << "end;" << endl << endl; + + string cls_nm; + + cls_nm = type_name(tstruct, true, false, is_exception, is_exception); + + indent_impl(out) << "procedure " << cls_prefix << cls_nm << ".Write( const oprot: IProtocol);" + << endl; + indent_impl(out) << "var" << endl; + indent_up_impl(); + indent_impl(out) << "struc : TThriftStruct;" << endl; + + if (fields.size() > 0) { + indent_impl(out) << "field_ : TThriftField;" << endl; + } + + out << local_vars.str(); + indent_down_impl(); + out << code_block.str(); +} + +void t_delphi_generator::generate_delphi_struct_writer_impl(ostream& out, + string cls_prefix, + t_struct* tstruct, + bool is_exception) { + + ostringstream local_vars; + ostringstream code_block; + + string name = tstruct->get_name(); + const vector<t_field*>& fields = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator f_iter; + + indent_impl(code_block) << "begin" << endl; + indent_up_impl(); + + indent_impl(local_vars) << "tracker : IProtocolRecursionTracker;" << endl; + indent_impl(code_block) << "tracker := oprot.NextRecursionLevel;" << endl; + + indent_impl(code_block) << "Thrift.Protocol.Init( struc, '" << name << "');" << endl; + indent_impl(code_block) << "oprot.WriteStructBegin(struc);" << endl; + + if (fields.size() > 0) { + indent_impl(code_block) << "Thrift.Protocol.Init( field_);" << endl; + } + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + string fieldname = prop_name((*f_iter), is_exception); + bool null_allowed = type_can_be_null((*f_iter)->get_type()); + bool is_required = ((*f_iter)->get_req() == t_field::T_REQUIRED); + bool has_isset = (!is_required); + if (is_required && null_allowed) { + null_allowed = false; + indent_impl(code_block) << "if (Self." << fieldname << " = nil)" << endl; + indent_impl(code_block) << "then raise TProtocolExceptionInvalidData.Create(" + << "'required field " << fieldname << " not set');" + << endl; + } + if (null_allowed) { + indent_impl(code_block) << "if (Self." << fieldname << " <> nil)"; + if (has_isset) { + code_block << " and __isset_" << fieldname; + } + code_block << " then begin" << endl; + indent_up_impl(); + } else { + if (has_isset) { + indent_impl(code_block) << "if (__isset_" << fieldname << ") then begin" << endl; + indent_up_impl(); + } + } + indent_impl(code_block) << "field_.Name := '" << (*f_iter)->get_name() << "';" << endl; + indent_impl(code_block) << "field_.Type_ := " << type_to_enum((*f_iter)->get_type()) << ";" + << endl; + indent_impl(code_block) << "field_.ID := " << (*f_iter)->get_key() << ";" << endl; + indent_impl(code_block) << "oprot.WriteFieldBegin(field_);" << endl; + generate_serialize_field(code_block, is_exception, *f_iter, "Self.", local_vars); + indent_impl(code_block) << "oprot.WriteFieldEnd();" << endl; + if (null_allowed || has_isset) { + indent_down_impl(); + indent_impl(code_block) << "end;" << endl; + } + } + + indent_impl(code_block) << "oprot.WriteFieldStop();" << endl; + indent_impl(code_block) << "oprot.WriteStructEnd();" << endl; + + indent_down_impl(); + indent_impl(code_block) << "end;" << endl << endl; + + string cls_nm; + + cls_nm = type_name(tstruct, true, false, is_exception, is_exception); + + indent_impl(out) << "procedure " << cls_prefix << cls_nm << ".Write( const oprot: IProtocol);" + << endl; + indent_impl(out) << "var" << endl; + indent_up_impl(); + indent_impl(out) << "struc : TThriftStruct;" << endl; + if (fields.size() > 0) { + indent_impl(out) << "field_ : TThriftField;" << endl; + } + out << local_vars.str(); + indent_down_impl(); + out << code_block.str(); +} + +void t_delphi_generator::generate_delphi_struct_tostring_impl(ostream& out, + string cls_prefix, + t_struct* tstruct, + bool is_exception, + bool is_x_factory) { + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + string cls_nm; + + if (is_exception) { + cls_nm = type_name(tstruct, true, (!is_x_factory), is_x_factory, true); + } else { + cls_nm = type_name(tstruct, true, false); + } + + string tmp_sb = tmp("_sb"); + string tmp_first = tmp("_first"); + bool useFirstFlag = false; + + indent_impl(out) << "function " << cls_prefix << cls_nm << ".ToString: string;" << endl; + indent_impl(out) << "var" << endl; + indent_up_impl(); + indent_impl(out) << tmp_sb << " : TThriftStringBuilder;" << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + bool is_optional = ((*f_iter)->get_req() != t_field::T_REQUIRED); + if (is_optional) { + indent_impl(out) << tmp_first << " : System.Boolean;" << endl; + useFirstFlag = true; + } + break; + } + indent_down_impl(); + indent_impl(out) << "begin" << endl; + indent_up_impl(); + + indent_impl(out) << tmp_sb << " := TThriftStringBuilder.Create('(');" << endl; + indent_impl(out) << "try" << endl; + indent_up_impl(); + + if (useFirstFlag) { + indent_impl(out) << tmp_first << " := TRUE;" << endl; + } + + bool had_required = false; // set to true after first required field has been processed + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + bool null_allowed = type_can_be_null((*f_iter)->get_type()); + bool is_optional = ((*f_iter)->get_req() != t_field::T_REQUIRED); + if (null_allowed) { + indent_impl(out) << "if (Self." << prop_name((*f_iter), is_exception) << " <> nil)"; + if (is_optional) { + out << " and __isset_" << prop_name(*f_iter, is_exception); + } + out << " then begin" << endl; + indent_up_impl(); + } else { + if (is_optional) { + indent_impl(out) << "if (__isset_" << prop_name(*f_iter, is_exception) << ") then begin" + << endl; + indent_up_impl(); + } + } + + if (useFirstFlag && (!had_required)) { + indent_impl(out) << "if not " << tmp_first << " then " << tmp_sb << ".Append(',');" << endl; + if (is_optional) { + indent_impl(out) << tmp_first << " := FALSE;" << endl; + } + indent_impl(out) << tmp_sb << ".Append('" << prop_name((*f_iter), is_exception) << ": ');" + << endl; + } else { + indent_impl(out) << tmp_sb << ".Append(', " << prop_name((*f_iter), is_exception) << ": ');" + << endl; + } + + t_type* ttype = (*f_iter)->get_type(); + while (ttype->is_typedef()) { + ttype = ((t_typedef*)ttype)->get_type(); + } + + if (ttype->is_xception() || ttype->is_struct()) { + indent_impl(out) << "if (Self." << prop_name((*f_iter), is_exception) << " = nil) then " << tmp_sb + << ".Append('<null>') else " << tmp_sb << ".Append( Self." + << prop_name((*f_iter), is_exception) << ".ToString());" << endl; + } else if (ttype->is_enum()) { + indent_impl(out) << tmp_sb << ".Append(EnumUtils<" + << type_name(ttype, false, true, false, false) + << ">.ToString( System.Ord( Self." + << prop_name((*f_iter), is_exception) << ")));" << endl; + } else { + indent_impl(out) << tmp_sb << ".Append( Self." << prop_name((*f_iter), is_exception) << ");" + << endl; + } + + if (null_allowed || is_optional) { + indent_down_impl(); + indent_impl(out) << "end;" << endl; + } + + if (!is_optional) { + had_required = true; // now __first must be false, so we don't need to check it anymore + } + } + + indent_impl(out) << tmp_sb << ".Append(')');" << endl; + indent_impl(out) << "Result := " << tmp_sb << ".ToString;" << endl; + if (useFirstFlag) { + indent_impl(out) << "if " << tmp_first << " then {prevent warning};" << endl; + } + + indent_down_impl(); + indent_impl(out) << "finally" << endl; + indent_up_impl(); + indent_impl(out) << tmp_sb << ".Free;" << endl; + indent_down_impl(); + indent_impl(out) << "end;" << endl; + + indent_down_impl(); + indent_impl(out) << "end;" << endl << endl; +} + +bool t_delphi_generator::is_void(t_type* type) { + while (type->is_typedef()) { + type = ((t_typedef*)type)->get_type(); + } + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + if (tbase == t_base_type::TYPE_VOID) { + return true; + } + } + return false; +} + +THRIFT_REGISTER_GENERATOR( + delphi, + "delphi", + " ansistr_binary: Use AnsiString for binary datatype (default is TBytes).\n" + " register_types: Enable TypeRegistry, allows for creation of struct, union\n" + " and container instances by interface or TypeInfo()\n" + " constprefix: Name TConstants classes after IDL to reduce ambiguities\n" + " events: Enable and use processing events in the generated code.\n" + " xmldoc: Enable XMLDoc comments for Help Insight etc.\n" + " async: Generate IAsync interface to use Parallel Programming Library (XE7+ only).\n") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_erl_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_erl_generator.cc new file mode 100644 index 000000000..910a38a23 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_erl_generator.cc @@ -0,0 +1,1282 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <string> +#include <fstream> +#include <iostream> +#include <limits> +#include <vector> + +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sstream> +#include "thrift/platform.h" +#include "thrift/version.h" +#include "thrift/generate/t_generator.h" + +using std::map; +using std::ofstream; +using std::ostream; +using std::ostringstream; +using std::string; +using std::stringstream; +using std::vector; + +static const std::string endl = "\n"; // avoid ostream << std::endl flushes + +/** + * Erlang code generator. + * + */ +class t_erl_generator : public t_generator { +public: + t_erl_generator(t_program* program, + const std::map<std::string, std::string>& parsed_options, + const std::string& option_string) + : t_generator(program) { + (void)option_string; + std::map<std::string, std::string>::const_iterator iter; + + legacy_names_ = false; + maps_ = false; + otp16_ = false; + export_lines_first_ = true; + export_types_lines_first_ = true; + + for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + if( iter->first.compare("legacynames") == 0) { + legacy_names_ = true; + } else if( iter->first.compare("maps") == 0) { + maps_ = true; + } else if( iter->first.compare("otp16") == 0) { + otp16_ = true; + } else { + throw "unknown option erl:" + iter->first; + } + } + + if (maps_ && otp16_) { + throw "argument error: Cannot specify both maps and otp16; maps are not available for Erlang/OTP R16 or older"; + } + + out_dir_base_ = "gen-erl"; + } + + /** + * Init and close methods + */ + + void init_generator() override; + void close_generator() override; + + /** + * Program-level generation functions + */ + + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_const(t_const* tconst) override; + void generate_struct(t_struct* tstruct) override; + void generate_xception(t_struct* txception) override; + void generate_service(t_service* tservice) override; + void generate_member_type(std::ostream& out, t_type* type); + void generate_member_value(std::ostream& out, t_type* type, t_const_value* value); + + std::string render_member_type(t_field* field); + std::string render_member_value(t_field* field); + std::string render_member_requiredness(t_field* field); + + // std::string render_default_value(t_type* type); + std::string render_default_value(t_field* field); + std::string render_const_value(t_type* type, t_const_value* value); + std::string render_type_term(t_type* ttype, bool expand_structs, bool extended_info = false); + + /** + * Struct generation code + */ + + void generate_erl_struct(t_struct* tstruct, bool is_exception); + void generate_erl_struct_definition(std::ostream& out, t_struct* tstruct); + void generate_erl_struct_member(std::ostream& out, t_field* tmember); + void generate_erl_struct_info(std::ostream& out, t_struct* tstruct); + void generate_erl_extended_struct_info(std::ostream& out, t_struct* tstruct); + void generate_erl_function_helpers(t_function* tfunction); + void generate_type_metadata(std::string function_name, vector<string> names); + void generate_enum_info(t_enum* tenum); + void generate_enum_metadata(); + void generate_const_function(t_const* tconst, ostringstream& exports, ostringstream& functions); + void generate_const_functions(); + + /** + * Service-level generation functions + */ + + void generate_service_helpers(t_service* tservice); + void generate_service_metadata(t_service* tservice); + void generate_service_interface(t_service* tservice); + void generate_function_info(t_service* tservice, t_function* tfunction); + + /** + * Helper rendering functions + */ + + std::string erl_autogen_comment(); + std::string erl_imports(); + std::string render_includes(); + std::string type_name(t_type* ttype); + std::string render_const_list_values(t_type* type, t_const_value* value); + + std::string function_signature(t_function* tfunction, std::string prefix = ""); + + std::string argument_list(t_struct* tstruct); + std::string type_to_enum(t_type* ttype); + std::string type_module(t_type* ttype); + + std::string make_safe_for_module_name(std::string in) { + if (legacy_names_) { + return decapitalize(in); + } else { + return underscore(in); + } + } + + std::string atomify(std::string in) { + if (legacy_names_) { + return "'" + decapitalize(in) + "'"; + } else { + return "'" + in + "'"; + } + } + + std::string constify(std::string in) { + if (legacy_names_) { + return capitalize(in); + } else { + return uppercase(in); + } + } + + static std::string comment(string in); + +private: + bool has_default_value(t_field*); + + /* if true retain pre 0.9.2 naming scheme for functions, atoms and consts */ + bool legacy_names_; + + /* if true use maps instead of dicts in generated code */ + bool maps_; + + /* if true use non-namespaced dict and set instead of dict:dict and sets:set */ + bool otp16_; + + /** + * add function to export list + */ + + void export_function(t_function* tfunction, std::string prefix = ""); + void export_string(std::string name, int num); + + void export_types_string(std::string name, int num); + + /** + * write out headers and footers for hrl files + */ + + void hrl_header(std::ostream& out, std::string name); + void hrl_footer(std::ostream& out, std::string name); + + /** + * stuff to spit out at the top of generated files + */ + + bool export_lines_first_; + std::ostringstream export_lines_; + + bool export_types_lines_first_; + std::ostringstream export_types_lines_; + + /** + * File streams + */ + + std::ostringstream f_info_; + std::ostringstream f_info_ext_; + + ofstream_with_content_based_conditional_update f_types_file_; + ofstream_with_content_based_conditional_update f_types_hrl_file_; + + ofstream_with_content_based_conditional_update f_consts_file_; + ofstream_with_content_based_conditional_update f_consts_hrl_file_; + + std::ostringstream f_service_; + ofstream_with_content_based_conditional_update f_service_file_; + ofstream_with_content_based_conditional_update f_service_hrl_; + + /** + * Metadata containers + */ + std::vector<std::string> v_struct_names_; + std::vector<std::string> v_enum_names_; + std::vector<std::string> v_exception_names_; + std::vector<t_enum*> v_enums_; + std::vector<t_const*> v_consts_; +}; + +/** + * UI for file generation by opening up the necessary file output + * streams. + * + * @param tprogram The program to generate + */ +void t_erl_generator::init_generator() { + // Make output directory + MKDIR(get_out_dir().c_str()); + + // setup export lines + export_lines_first_ = true; + export_types_lines_first_ = true; + + string program_module_name = make_safe_for_module_name(program_name_); + + // types files + string f_types_name = get_out_dir() + program_module_name + "_types.erl"; + string f_types_hrl_name = get_out_dir() + program_module_name + "_types.hrl"; + + f_types_file_.open(f_types_name.c_str()); + f_types_hrl_file_.open(f_types_hrl_name.c_str()); + + hrl_header(f_types_hrl_file_, program_module_name + "_types"); + + f_types_file_ << erl_autogen_comment() << endl + << "-module(" << program_module_name << "_types)." << endl + << erl_imports() << endl; + + f_types_file_ << "-include(\"" << program_module_name << "_types.hrl\")." << endl + << endl; + + f_types_hrl_file_ << render_includes() << endl; + + // consts files + string f_consts_name = get_out_dir() + program_module_name + "_constants.erl"; + string f_consts_hrl_name = get_out_dir() + program_module_name + "_constants.hrl"; + + f_consts_file_.open(f_consts_name.c_str()); + f_consts_hrl_file_.open(f_consts_hrl_name.c_str()); + + f_consts_file_ << erl_autogen_comment() << endl + << "-module(" << program_module_name << "_constants)." << endl + << erl_imports() << endl + << "-include(\"" << program_module_name << "_types.hrl\")." << endl + << endl; + + f_consts_hrl_file_ << erl_autogen_comment() << endl << erl_imports() << endl + << "-include(\"" << program_module_name << "_types.hrl\")." << endl << endl; +} + +/** + * Boilerplate at beginning and end of header files + */ +void t_erl_generator::hrl_header(ostream& out, string name) { + out << erl_autogen_comment() << endl + << "-ifndef(_" << name << "_included)." << endl << "-define(_" << name << "_included, yeah)." + << endl; +} + +void t_erl_generator::hrl_footer(ostream& out, string name) { + (void)name; + out << "-endif." << endl; +} + +/** + * Renders all the imports necessary for including another Thrift program + */ +string t_erl_generator::render_includes() { + const vector<t_program*>& includes = program_->get_includes(); + string result = ""; + for (auto include : includes) { + result += "-include(\"" + make_safe_for_module_name(include->get_name()) + + "_types.hrl\").\n"; + } + if (includes.size() > 0) { + result += "\n"; + } + return result; +} + +/** + * Autogen'd comment + */ +string t_erl_generator::erl_autogen_comment() { + return std::string("%%\n") + "%% Autogenerated by Thrift Compiler (" + THRIFT_VERSION + ")\n" + + "%%\n" + "%% DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n" + + "%%\n"; +} + +/** + * Comment out text + */ + +string t_erl_generator::comment(string in) { + size_t pos = 0; + in.insert(pos, "%% "); + while ((pos = in.find_first_of('\n', pos)) != string::npos) { + in.insert(++pos, "%% "); + } + return in; +} + +/** + * Prints standard thrift imports + */ +string t_erl_generator::erl_imports() { + return ""; +} + +/** + * Closes the type files + */ +void t_erl_generator::close_generator() { + + export_types_string("struct_info", 1); + export_types_string("struct_info_ext", 1); + export_types_string("enum_info", 1); + export_types_string("enum_names", 0); + export_types_string("struct_names", 0); + export_types_string("exception_names", 0); + + f_types_file_ << "-export([" << export_types_lines_.str() << "])." << endl << endl; + + f_types_file_ << f_info_.str(); + f_types_file_ << "struct_info(_) -> erlang:error(function_clause)." << endl << endl; + + f_types_file_ << f_info_ext_.str(); + f_types_file_ << "struct_info_ext(_) -> erlang:error(function_clause)." << endl << endl; + + generate_const_functions(); + + generate_type_metadata("struct_names", v_struct_names_); + generate_enum_metadata(); + generate_type_metadata("enum_names", v_enum_names_); + generate_type_metadata("exception_names", v_exception_names_); + + hrl_footer(f_types_hrl_file_, string("BOGUS")); + + f_types_file_.close(); + f_types_hrl_file_.close(); + f_consts_file_.close(); + f_consts_hrl_file_.close(); +} + +const std::string emit_double_as_string(const double value) { + std::stringstream double_output_stream; + // sets the maximum precision: http://en.cppreference.com/w/cpp/io/manip/setprecision + // sets the output format to fixed: http://en.cppreference.com/w/cpp/io/manip/fixed (not in scientific notation) + double_output_stream << std::setprecision(std::numeric_limits<double>::digits10 + 1); + + #ifdef _MSC_VER + // strtod is broken in MSVC compilers older than 2015, so std::fixed fails to format a double literal. + // more details: https://blogs.msdn.microsoft.com/vcblog/2014/06/18/ + // c-runtime-crt-features-fixes-and-breaking-changes-in-visual-studio-14-ctp1/ + // and + // http://www.exploringbinary.com/visual-c-plus-plus-strtod-still-broken/ + #if _MSC_VER >= MSC_2015_VER + double_output_stream << std::fixed; + #else + // note that if this function is called from the erlang generator and the MSVC compiler is older than 2015, + // the double literal must be output in the scientific format. There can be some cases where the + // mantissa of the output does not have fractionals, which is illegal in Erlang. + // example => 10000000000000000.0 being output as 1e+16 + double_output_stream << std::scientific; + #endif + #else + double_output_stream << std::fixed; + #endif + + double_output_stream << value; + + return double_output_stream.str(); +} + +void t_erl_generator::generate_type_metadata(std::string function_name, vector<string> names) { + size_t num_structs = names.size(); + + indent(f_types_file_) << function_name << "() ->\n"; + indent_up(); + indent(f_types_file_) << "["; + + + for(size_t i=0; i < num_structs; i++) { + f_types_file_ << names.at(i); + + if (i < num_structs - 1) { + f_types_file_ << ", "; + } + } + + f_types_file_ << "].\n\n"; + indent_down(); +} + +/** + * Generates a typedef. no op + * + * @param ttypedef The type definition + */ +void t_erl_generator::generate_typedef(t_typedef* ttypedef) { + (void)ttypedef; +} + + +void t_erl_generator::generate_const_function(t_const* tconst, ostringstream& exports, ostringstream& functions) { + t_type* type = get_true_type(tconst->get_type()); + string name = tconst->get_name(); + t_const_value* value = tconst->get_value(); + + if (type->is_map()) { + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + string const_fun_name = lowercase(name); + + // Emit const function export. + if (exports.tellp() > 0) { exports << ", "; } + exports << const_fun_name << "/1, " << const_fun_name << "/2"; + + // Emit const function definition. + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator i, end = value->get_map().end(); + // The one-argument form throws an error if the key does not exist in the map. + for (i = value->get_map().begin(); i != end;) { + functions << const_fun_name << "(" << render_const_value(ktype, i->first) << ") -> " + << render_const_value(vtype, i->second); + ++i; + functions << (i != end ? ";\n" : ".\n\n"); + } + + // The two-argument form returns a default value if the key does not exist in the map. + for (i = value->get_map().begin(); i != end; ++i) { + functions << const_fun_name << "(" << render_const_value(ktype, i->first) << ", _) -> " + << render_const_value(vtype, i->second) << ";\n"; + } + functions << const_fun_name << "(_, Default) -> Default.\n\n"; + } else if (type->is_list()) { + string const_fun_name = lowercase(name); + + if (exports.tellp() > 0) { exports << ", "; } + exports << const_fun_name << "/1, " << const_fun_name << "/2"; + + size_t list_size = value->get_list().size(); + string rendered_list = render_const_list_values(type, value); + functions << const_fun_name << "(N) when N >= 1, N =< " << list_size << " ->\n" + << indent_str() << "element(N, {" << rendered_list << "}).\n"; + functions << const_fun_name << "(N, _) when N >= 1, N =< " << list_size << " ->\n" + << indent_str() << "element(N, {" << rendered_list << "});\n" + << const_fun_name << "(_, Default) -> Default.\n\n"; + indent_down(); + } +} + +void t_erl_generator::generate_const_functions() { + ostringstream exports; + ostringstream functions; + vector<t_const*>::iterator c_iter; + for (c_iter = v_consts_.begin(); c_iter != v_consts_.end(); ++c_iter) { + generate_const_function(*c_iter, exports, functions); + } + if (exports.tellp() > 0) { + f_consts_file_ << "-export([" << exports.str() << "]).\n\n" + << functions.str(); + } +} + + +/** + * Generates code for an enumerated type. Done using a class to scope + * the values. + * + * @param tenum The enumeration + */ +void t_erl_generator::generate_enum(t_enum* tenum) { + vector<t_enum_value*> constants = tenum->get_constants(); + vector<t_enum_value*>::iterator c_iter; + + v_enums_.push_back(tenum); + v_enum_names_.push_back(atomify(tenum->get_name())); + + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + int value = (*c_iter)->get_value(); + string name = (*c_iter)->get_name(); + indent(f_types_hrl_file_) << "-define(" << constify(make_safe_for_module_name(program_name_)) + << "_" << constify(tenum->get_name()) << "_" << constify(name) << ", " + << value << ")." << endl; + } + + f_types_hrl_file_ << endl; +} + +void t_erl_generator::generate_enum_info(t_enum* tenum){ + vector<t_enum_value*> constants = tenum->get_constants(); + size_t num_constants = constants.size(); + + indent(f_types_file_) << "enum_info(" << atomify(tenum->get_name()) << ") ->\n"; + indent_up(); + indent(f_types_file_) << "[\n"; + + for(size_t i=0; i < num_constants; i++) { + indent_up(); + t_enum_value* value = constants.at(i); + indent(f_types_file_) << "{" << atomify(value->get_name()) << ", " << value->get_value() << "}"; + + if (i < num_constants - 1) { + f_types_file_ << ",\n"; + } + indent_down(); + } + f_types_file_ << "\n"; + indent(f_types_file_) << "];\n\n"; + indent_down(); +} + +void t_erl_generator::generate_enum_metadata() { + size_t enum_count = v_enums_.size(); + + for(size_t i=0; i < enum_count; i++) { + t_enum* tenum = v_enums_.at(i); + generate_enum_info(tenum); + } + + indent(f_types_file_) << "enum_info(_) -> erlang:error(function_clause).\n\n"; +} + +/** + * Generate a constant value + */ +void t_erl_generator::generate_const(t_const* tconst) { + t_type* type = tconst->get_type(); + string name = tconst->get_name(); + t_const_value* value = tconst->get_value(); + + // Save the tconst so that function can be emitted in generate_const_functions(). + v_consts_.push_back(tconst); + + f_consts_hrl_file_ << "-define(" << constify(make_safe_for_module_name(program_name_)) << "_" + << constify(name) << ", " << render_const_value(type, value) << ")." << endl << endl; +} + +/** + * Prints the value of a constant with the given type. Note that type checking + * is NOT performed in this function as it is always run beforehand using the + * validate_types method in main.cc + */ +string t_erl_generator::render_const_value(t_type* type, t_const_value* value) { + type = get_true_type(type); + std::ostringstream out; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + out << '"' << get_escaped_string(value) << '"'; + break; + case t_base_type::TYPE_BOOL: + out << (value->get_integer() > 0 ? "true" : "false"); + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + out << value->get_integer(); + break; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) { + out << "float(" << value->get_integer() << ")"; + } else { + out << emit_double_as_string(value->get_double()); + } + break; + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + indent(out) << value->get_integer(); + + } else if (type->is_struct() || type->is_xception()) { + out << "#" << type_name(type) << "{"; + const vector<t_field*>& fields = ((t_struct*)type)->get_members(); + vector<t_field*>::const_iterator f_iter; + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + + bool first = true; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + t_type* field_type = NULL; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field_type = (*f_iter)->get_type(); + } + } + if (field_type == NULL) { + throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string(); + } + + if (first) { + first = false; + } else { + out << ","; + } + out << v_iter->first->get_string(); + out << " = "; + out << render_const_value(field_type, v_iter->second); + } + indent_down(); + indent(out) << "}"; + + } else if (type->is_map()) { + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + + if (maps_) { + out << "maps:from_list(["; + } else { + out << "dict:from_list(["; + } + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator i, end = value->get_map().end(); + for (i = value->get_map().begin(); i != end;) { + out << "{" << render_const_value(ktype, i->first) << "," + << render_const_value(vtype, i->second) << "}"; + if (++i != end) { + out << ","; + } + } + out << "])"; + } else if (type->is_set()) { + t_type* etype = ((t_set*)type)->get_elem_type(); + out << "sets:from_list(["; + vector<t_const_value*>::const_iterator i, end = value->get_list().end(); + for (i = value->get_list().begin(); i != end;) { + out << render_const_value(etype, *i); + if (++i != end) { + out << ","; + } + } + out << "])"; + } else if (type->is_list()) { + out << "[" << render_const_list_values(type, value) << "]"; + } else { + throw "CANNOT GENERATE CONSTANT FOR TYPE: " + type->get_name(); + } + return out.str(); +} + +string t_erl_generator::render_const_list_values(t_type* type, t_const_value* value) { + std::ostringstream out; + t_type* etype = ((t_list*)type)->get_elem_type(); + + bool first = true; + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + if (first) { + first = false; + } else { + out << ","; + } + out << render_const_value(etype, *v_iter); + } + return out.str(); +} + + +string t_erl_generator::render_default_value(t_field* field) { + t_type* type = field->get_type(); + if (type->is_struct() || type->is_xception()) { + return "#" + type_name(type) + "{}"; + } else if (type->is_map()) { + if (maps_) { + return "#{}"; + } else { + return "dict:new()"; + } + } else if (type->is_set()) { + return "sets:new()"; + } else if (type->is_list()) { + return "[]"; + } else { + return "undefined"; + } +} + +string t_erl_generator::render_member_type(t_field* field) { + t_type* type = get_true_type(field->get_type()); + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + return "string() | binary()"; + case t_base_type::TYPE_BOOL: + return "boolean()"; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + return "integer()"; + case t_base_type::TYPE_DOUBLE: + return "float()"; + default: + throw "compiler error: unsupported base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + return "integer()"; + } else if (type->is_struct() || type->is_xception()) { + return type_name(type) + "()"; + } else if (type->is_map()) { + if (maps_) { + return "map()"; + } else if (otp16_) { + return "dict()"; + } else { + return "dict:dict()"; + } + } else if (type->is_set()) { + if (otp16_) { + return "set()"; + } else { + return "sets:set()"; + } + } else if (type->is_list()) { + return "list()"; + } else { + throw "compiler error: unsupported type " + type->get_name(); + } +} + +string t_erl_generator::render_member_requiredness(t_field* field) { + switch (field->get_req()) { + case t_field::T_REQUIRED: + return "required"; + case t_field::T_OPTIONAL: + return "optional"; + default: + return "undefined"; + } +} + +/** + * Generates a struct + */ +void t_erl_generator::generate_struct(t_struct* tstruct) { + v_struct_names_.push_back(type_name(tstruct)); + generate_erl_struct(tstruct, false); +} + +/** + * Generates a struct definition for a thrift exception. Basically the same + * as a struct but extends the Exception class. + * + * @param txception The struct definition + */ +void t_erl_generator::generate_xception(t_struct* txception) { + v_exception_names_.push_back(type_name(txception)); + generate_erl_struct(txception, true); +} + +/** + * Generates a struct + */ +void t_erl_generator::generate_erl_struct(t_struct* tstruct, bool is_exception) { + (void)is_exception; + generate_erl_struct_definition(f_types_hrl_file_, tstruct); + generate_erl_struct_info(f_info_, tstruct); + generate_erl_extended_struct_info(f_info_ext_, tstruct); +} + +/** + * Generates a struct definition for a thrift data type. + * + * @param tstruct The struct definition + */ +void t_erl_generator::generate_erl_struct_definition(ostream& out, t_struct* tstruct) { + indent(out) << "%% struct " << type_name(tstruct) << endl << endl; + + std::stringstream buf; + buf << indent() << "-record(" << type_name(tstruct) << ", {"; + string field_indent(buf.str().size(), ' '); + + const vector<t_field*>& members = tstruct->get_members(); + for (vector<t_field*>::const_iterator m_iter = members.begin(); m_iter != members.end();) { + generate_erl_struct_member(buf, *m_iter); + if (++m_iter != members.end()) { + buf << "," << endl << field_indent; + } + } + buf << "})."; + + out << buf.str() << endl; + out << "-type " + type_name(tstruct) << "() :: #" + type_name(tstruct) + "{}." << endl << endl; +} + +/** + * Generates the record field definition + */ + +void t_erl_generator::generate_erl_struct_member(ostream& out, t_field* tmember) { + out << atomify(tmember->get_name()); + if (has_default_value(tmember)) + out << " = " << render_member_value(tmember); + out << " :: " << render_member_type(tmember); + if (tmember->get_req() != t_field::T_REQUIRED) + out << " | 'undefined'"; +} + +bool t_erl_generator::has_default_value(t_field* field) { + t_type* type = field->get_type(); + if (!field->get_value()) { + if (field->get_req() == t_field::T_REQUIRED) { + if (type->is_struct() || type->is_xception() || type->is_map() || type->is_set() + || type->is_list()) { + return true; + } else { + return false; + } + } else { + return false; + } + } else { + return true; + } +} + +string t_erl_generator::render_member_value(t_field* field) { + if (!field->get_value()) { + return render_default_value(field); + } else { + return render_const_value(field->get_type(), field->get_value()); + } +} + +/** + * Generates the read method for a struct + */ +void t_erl_generator::generate_erl_struct_info(ostream& out, t_struct* tstruct) { + indent(out) << "struct_info(" << type_name(tstruct) << ") ->" << endl; + indent_up(); + out << indent() << render_type_term(tstruct, true) << ";" << endl; + indent_down(); + out << endl; +} + +void t_erl_generator::generate_erl_extended_struct_info(ostream& out, t_struct* tstruct) { + indent(out) << "struct_info_ext(" << type_name(tstruct) << ") ->" << endl; + indent_up(); + out << indent() << render_type_term(tstruct, true, true) << ";" << endl; + indent_down(); + out << endl; +} + +/** + * Generates a thrift service. + * + * @param tservice The service definition + */ +void t_erl_generator::generate_service(t_service* tservice) { + service_name_ = make_safe_for_module_name(service_name_); + + string f_service_hrl_name = get_out_dir() + service_name_ + "_thrift.hrl"; + string f_service_name = get_out_dir() + service_name_ + "_thrift.erl"; + f_service_file_.open(f_service_name.c_str()); + f_service_hrl_.open(f_service_hrl_name.c_str()); + + // Reset service text aggregating stream streams + f_service_.str(""); + export_lines_.str(""); + export_lines_first_ = true; + + hrl_header(f_service_hrl_, service_name_); + + if (tservice->get_extends() != NULL) { + f_service_hrl_ << "-include(\"" + << make_safe_for_module_name(tservice->get_extends()->get_name()) + << "_thrift.hrl\"). % inherit " << endl; + } + + f_service_hrl_ << "-include(\"" << make_safe_for_module_name(program_name_) << "_types.hrl\")." + << endl << endl; + + // Generate the three main parts of the service (well, two for now in PHP) + generate_service_helpers(tservice); // cpiro: New Erlang Order + + generate_service_interface(tservice); + + generate_service_metadata(tservice); + + // indent_down(); + + f_service_file_ << erl_autogen_comment() << endl << "-module(" << service_name_ << "_thrift)." + << endl << "-behaviour(thrift_service)." << endl << endl << erl_imports() << endl; + + f_service_file_ << "-include(\"" << make_safe_for_module_name(tservice->get_name()) + << "_thrift.hrl\")." << endl << endl; + + f_service_file_ << "-export([" << export_lines_.str() << "])." << endl << endl; + + f_service_file_ << f_service_.str(); + + hrl_footer(f_service_hrl_, f_service_name); + + // Close service file + f_service_file_.close(); + f_service_hrl_.close(); +} + +void t_erl_generator::generate_service_metadata(t_service* tservice) { + export_string("function_names", 0); + vector<t_function*> functions = tservice->get_functions(); + size_t num_functions = functions.size(); + + indent(f_service_) << "function_names() -> " << endl; + indent_up(); + indent(f_service_) << "["; + + for (size_t i=0; i < num_functions; i++) { + t_function* current = functions.at(i); + f_service_ << atomify(current->get_name()); + if (i < num_functions - 1) { + f_service_ << ", "; + } + } + + f_service_ << "].\n\n"; + indent_down(); +} + +/** + * Generates helper functions for a service. + * + * @param tservice The service to generate a header definition for + */ +void t_erl_generator::generate_service_helpers(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + // indent(f_service_) << + // "% HELPER FUNCTIONS AND STRUCTURES" << endl << endl; + + export_string("struct_info", 1); + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_erl_function_helpers(*f_iter); + } + f_service_ << "struct_info(_) -> erlang:error(function_clause)." << endl; +} + +/** + * Generates a struct and helpers for a function. + * + * @param tfunction The function + */ +void t_erl_generator::generate_erl_function_helpers(t_function* tfunction) { + (void)tfunction; +} + +/** + * Generates a service interface definition. + * + * @param tservice The service to generate a header definition for + */ +void t_erl_generator::generate_service_interface(t_service* tservice) { + + export_string("function_info", 2); + + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + f_service_ << "%%% interface" << endl; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + f_service_ << indent() << "% " << function_signature(*f_iter) << endl; + + generate_function_info(tservice, *f_iter); + } + + // Inheritance - pass unknown functions to base class + if (tservice->get_extends() != NULL) { + indent(f_service_) << "function_info(Function, InfoType) ->" << endl; + indent_up(); + indent(f_service_) << make_safe_for_module_name(tservice->get_extends()->get_name()) + << "_thrift:function_info(Function, InfoType)." << endl; + indent_down(); + } else { + // return function_clause error for non-existent functions + indent(f_service_) << "function_info(_Func, _Info) -> erlang:error(function_clause)." << endl; + } + + indent(f_service_) << endl; +} + +/** + * Generates a function_info(FunctionName, params_type) and + * function_info(FunctionName, reply_type) + */ +void t_erl_generator::generate_function_info(t_service* tservice, t_function* tfunction) { + (void)tservice; + string name_atom = atomify(tfunction->get_name()); + + t_struct* xs = tfunction->get_xceptions(); + t_struct* arg_struct = tfunction->get_arglist(); + + // function_info(Function, params_type): + indent(f_service_) << "function_info(" << name_atom << ", params_type) ->" << endl; + indent_up(); + + indent(f_service_) << render_type_term(arg_struct, true) << ";" << endl; + + indent_down(); + + // function_info(Function, reply_type): + indent(f_service_) << "function_info(" << name_atom << ", reply_type) ->" << endl; + indent_up(); + + if (!tfunction->get_returntype()->is_void()) + indent(f_service_) << render_type_term(tfunction->get_returntype(), false) << ";" << endl; + else if (tfunction->is_oneway()) + indent(f_service_) << "oneway_void;" << endl; + else + indent(f_service_) << "{struct, []}" + << ";" << endl; + indent_down(); + + // function_info(Function, exceptions): + indent(f_service_) << "function_info(" << name_atom << ", exceptions) ->" << endl; + indent_up(); + indent(f_service_) << render_type_term(xs, true) << ";" << endl; + indent_down(); +} + +/** + * Renders a function signature of the form 'type name(args)' + * + * @param tfunction Function definition + * @return String of rendered function definition + */ +string t_erl_generator::function_signature(t_function* tfunction, string prefix) { + return prefix + tfunction->get_name() + "(This" + + capitalize(argument_list(tfunction->get_arglist())) + ")"; +} + +/** + * Add a function to the exports list + */ +void t_erl_generator::export_string(string name, int num) { + if (export_lines_first_) { + export_lines_first_ = false; + } else { + export_lines_ << ", "; + } + export_lines_ << name << "/" << num; +} + +void t_erl_generator::export_types_string(string name, int num) { + if (export_types_lines_first_) { + export_types_lines_first_ = false; + } else { + export_types_lines_ << ", "; + } + export_types_lines_ << name << "/" << num; +} + +void t_erl_generator::export_function(t_function* tfunction, string prefix) { + t_struct::members_type::size_type num = tfunction->get_arglist()->get_members().size(); + if (num > static_cast<t_struct::members_type::size_type>(std::numeric_limits<int>().max())) { + throw "integer overflow in t_erl_generator::export_function, name " + tfunction->get_name(); + } + export_string(prefix + tfunction->get_name(), + 1 // This + + static_cast<int>(num)); +} + +/** + * Renders a field list + */ +string t_erl_generator::argument_list(t_struct* tstruct) { + string result = ""; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + result += ", "; // initial comma to compensate for initial This + } else { + result += ", "; + } + result += capitalize((*f_iter)->get_name()); + } + return result; +} + +string t_erl_generator::type_name(t_type* ttype) { + string prefix = ttype->get_program()->get_namespace("erl"); + size_t prefix_length = prefix.length(); + if (prefix_length > 0 && prefix[prefix_length - 1] != '_') { + prefix += '.'; + } + + string name = ttype->get_name(); + + return atomify(prefix + name); +} + +/** + * Converts the parse type to a Erlang "type" (macro for int constants) + */ +string t_erl_generator::type_to_enum(t_type* type) { + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + return "?tType_STRING"; + case t_base_type::TYPE_BOOL: + return "?tType_BOOL"; + case t_base_type::TYPE_I8: + return "?tType_I8"; + case t_base_type::TYPE_I16: + return "?tType_I16"; + case t_base_type::TYPE_I32: + return "?tType_I32"; + case t_base_type::TYPE_I64: + return "?tType_I64"; + case t_base_type::TYPE_DOUBLE: + return "?tType_DOUBLE"; + } + } else if (type->is_enum()) { + return "?tType_I32"; + } else if (type->is_struct() || type->is_xception()) { + return "?tType_STRUCT"; + } else if (type->is_map()) { + return "?tType_MAP"; + } else if (type->is_set()) { + return "?tType_SET"; + } else if (type->is_list()) { + return "?tType_LIST"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +/** + * Generate an Erlang term which represents a thrift type + */ +std::string t_erl_generator::render_type_term(t_type* type, + bool expand_structs, + bool extended_info) { + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + return "string"; + case t_base_type::TYPE_BOOL: + return "bool"; + case t_base_type::TYPE_I8: + return "byte"; + case t_base_type::TYPE_I16: + return "i16"; + case t_base_type::TYPE_I32: + return "i32"; + case t_base_type::TYPE_I64: + return "i64"; + case t_base_type::TYPE_DOUBLE: + return "double"; + } + } else if (type->is_enum()) { + return "i32"; + } else if (type->is_struct() || type->is_xception()) { + if (expand_structs) { + + std::stringstream buf; + buf << "{struct, ["; + string field_indent(buf.str().size(), ' '); + + t_struct::members_type const& fields = static_cast<t_struct*>(type)->get_members(); + t_struct::members_type::const_iterator i, end = fields.end(); + for (i = fields.begin(); i != end;) { + t_struct::members_type::value_type member = *i; + int32_t key = member->get_key(); + string type = render_type_term(member->get_type(), false, false); // recursive call + + if (!extended_info) { + // Convert to format: {struct, [{Fid, Type}|...]} + buf << "{" << key << ", " << type << "}"; + } else { + // Convert to format: {struct, [{Fid, Req, Type, Name, Def}|...]} + string name = member->get_name(); + string value = render_member_value(member); + string requiredness = render_member_requiredness(member); + buf << "{" << key << ", " << requiredness << ", " << type << ", " << atomify(name) << ", " + << value << "}"; + } + + if (++i != end) { + buf << "," << endl << field_indent; + } + } + + buf << "]}" << endl; + return buf.str(); + } else { + return "{struct, {" + atomify(type_module(type)) + ", " + type_name(type) + "}}"; + } + } else if (type->is_map()) { + // {map, KeyType, ValType} + t_type* key_type = ((t_map*)type)->get_key_type(); + t_type* val_type = ((t_map*)type)->get_val_type(); + + return "{map, " + render_type_term(key_type, false) + ", " + render_type_term(val_type, false) + + "}"; + + } else if (type->is_set()) { + t_type* elem_type = ((t_set*)type)->get_elem_type(); + + return "{set, " + render_type_term(elem_type, false) + "}"; + + } else if (type->is_list()) { + t_type* elem_type = ((t_list*)type)->get_elem_type(); + + return "{list, " + render_type_term(elem_type, false) + "}"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +std::string t_erl_generator::type_module(t_type* ttype) { + return make_safe_for_module_name(ttype->get_program()->get_name()) + "_types"; +} + +THRIFT_REGISTER_GENERATOR( + erl, + "Erlang", + " legacynames: Output files retain naming conventions of Thrift 0.9.1 and earlier.\n" + " maps: Generate maps instead of dicts.\n" + " otp16: Generate non-namespaced dict and set instead of dict:dict and sets:set.\n") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_generator.cc new file mode 100644 index 000000000..ca3f5dd68 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_generator.cc @@ -0,0 +1,262 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "thrift/generate/t_generator.h" +using namespace std; + +/** + * Top level program generation function. Calls the generator subclass methods + * for preparing file streams etc. then iterates over all the parts of the + * program to perform the correct actions. + * + * @param program The thrift program to compile into C++ source + */ +void t_generator::generate_program() { + // Initialize the generator + init_generator(); + + // Generate enums + vector<t_enum*> enums = program_->get_enums(); + vector<t_enum*>::iterator en_iter; + for (en_iter = enums.begin(); en_iter != enums.end(); ++en_iter) { + generate_enum(*en_iter); + } + + // Generate typedefs + vector<t_typedef*> typedefs = program_->get_typedefs(); + vector<t_typedef*>::iterator td_iter; + for (td_iter = typedefs.begin(); td_iter != typedefs.end(); ++td_iter) { + generate_typedef(*td_iter); + } + + // Generate structs, exceptions, and unions in declared order + vector<t_struct*> objects = program_->get_objects(); + + vector<t_struct*>::iterator o_iter; + for (o_iter = objects.begin(); o_iter != objects.end(); ++o_iter) { + generate_forward_declaration(*o_iter); + } + for (o_iter = objects.begin(); o_iter != objects.end(); ++o_iter) { + if ((*o_iter)->is_xception()) { + generate_xception(*o_iter); + } else { + generate_struct(*o_iter); + } + } + + // Generate constants + vector<t_const*> consts = program_->get_consts(); + generate_consts(consts); + + // Generate services + vector<t_service*> services = program_->get_services(); + vector<t_service*>::iterator sv_iter; + for (sv_iter = services.begin(); sv_iter != services.end(); ++sv_iter) { + service_name_ = get_service_name(*sv_iter); + generate_service(*sv_iter); + } + + // Close the generator + close_generator(); +} + +std::set<std::string> t_generator::lang_keywords() const { + std::string keywords[] = { "BEGIN", "END", "__CLASS__", "__DIR__", "__FILE__", "__FUNCTION__", + "__LINE__", "__METHOD__", "__NAMESPACE__", "abstract", "alias", "and", "args", "as", + "assert", "begin", "break", "case", "catch", "class", "clone", "continue", "declare", + "def", "default", "del", "delete", "do", "dynamic", "elif", "else", "elseif", "elsif", + "end", "enddeclare", "endfor", "endforeach", "endif", "endswitch", "endwhile", "ensure", + "except", "exec", "finally", "float", "for", "foreach", "from", "function", "global", + "goto", "if", "implements", "import", "in", "inline", "instanceof", "interface", "is", + "lambda", "module", "native", "new", "next", "nil", "not", "or", "package", "pass", + "public", "print", "private", "protected", "raise", "redo", "rescue", "retry", "register", + "return", "self", "sizeof", "static", "super", "switch", "synchronized", "then", "this", + "throw", "transient", "try", "undef", "unless", "unsigned", "until", "use", "var", + "virtual", "volatile", "when", "while", "with", "xor", "yield" }; + return std::set<std::string>(keywords, keywords + sizeof(keywords)/sizeof(keywords[0]) ); +} + +void t_generator::validate_input() const { + validate(program_->get_enums()); + validate(program_->get_typedefs()); + validate(program_->get_objects()); + validate(program_->get_consts()); + validate(program_->get_services()); +} + +template <typename T> +void t_generator::validate(const vector<T>& list) const{ + typename vector<T>::const_iterator it; + for(it=list.begin(); it != list.end(); ++it) { + validate(*it); + } +} + +void t_generator::validate(t_function const* f) const { + validate_id(f->get_name()); + validate(f->get_arglist()); + validate(f->get_xceptions()); +} + +void t_generator::validate(t_service const* s) const { + validate_id(s->get_name()); + validate(s->get_functions()); +} + +void t_generator::validate(t_enum const* en) const { + validate_id(en->get_name()); + validate(en->get_constants()); +} +void t_generator::validate(t_struct const* s) const { + validate_id(s->get_name()); + validate(s->get_members()); +} + +void t_generator::validate(t_enum_value const* en_val) const { + validate_id(en_val->get_name()); +} +void t_generator::validate(t_typedef const* td) const { + validate_id(td->get_name()); +} +void t_generator::validate(t_const const* c) const { + validate_id(c->get_name()); +} +void t_generator::validate(t_field const* f) const { + validate_id(f->get_name()); +} + +void t_generator::validate_id(const string& id) const { + if (keywords_.find(id) != keywords_.end()) { + failure("Cannot use reserved language keyword: \"%s\"", id.c_str()); + } +} + +string t_generator::escape_string(const string& in) const { + string result = ""; + for (string::const_iterator it = in.begin(); it < in.end(); it++) { + std::map<char, std::string>::const_iterator res = escape_.find(*it); + if (res != escape_.end()) { + result.append(res->second); + } else { + result.push_back(*it); + } + } + return result; +} + +void t_generator::generate_consts(vector<t_const*> consts) { + vector<t_const*>::iterator c_iter; + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + generate_const(*c_iter); + } +} + +void t_generator::generate_docstring_comment(ostream& out, + const string& comment_start, + const string& line_prefix, + const string& contents, + const string& comment_end) { + if (!comment_start.empty()) + indent(out) << comment_start; + stringstream docs(contents, ios_base::in); + while (!(docs.eof() || docs.fail())) { + char line[1024]; + docs.getline(line, 1024); + + if (strlen(line) > 0) { + indent(out) << line_prefix << line << std::endl; + } else if (line_prefix.empty()){ + out << std::endl; + } else if(!docs.eof()) { + indent(out) << line_prefix << std::endl; + } + } + if (!comment_end.empty()) + indent(out) << comment_end; +} + +void t_generator_registry::register_generator(t_generator_factory* factory) { + gen_map_t& the_map = get_generator_map(); + if (the_map.find(factory->get_short_name()) != the_map.end()) { + failure("Duplicate generators for language \"%s\"!\n", factory->get_short_name().c_str()); + } + the_map[factory->get_short_name()] = factory; +} + +void t_generator::parse_options(const string& options, + string& language, + map<string, string>& parsed_options) { + string::size_type colon = options.find(':'); + language = options.substr(0, colon); + + if (colon != string::npos) { + string::size_type pos = colon + 1; + while (pos != string::npos && pos < options.size()) { + string::size_type next_pos = options.find(',', pos); + string option = options.substr(pos, next_pos - pos); + pos = ((next_pos == string::npos) ? next_pos : next_pos + 1); + + string::size_type separator = option.find('='); + string key, value; + if (separator == string::npos) { + key = option; + value = ""; + } else { + key = option.substr(0, separator); + value = option.substr(separator + 1); + } + + parsed_options[key] = value; + } + } +} + +t_generator* t_generator_registry::get_generator(t_program* program, + const string& language, + const map<string, string>& parsed_options, + const std::string& options) { + gen_map_t& the_map = get_generator_map(); + gen_map_t::iterator iter = the_map.find(language); + + if (iter == the_map.end()) { + return NULL; + } + + return iter->second->get_generator(program, parsed_options, options); +} + +t_generator* t_generator_registry::get_generator(t_program* program, const string& options) { + string language; + map<string, string> parsed_options; + t_generator::parse_options(options, language, parsed_options); + return get_generator(program, language, parsed_options, options); +} + +t_generator_registry::gen_map_t& t_generator_registry::get_generator_map() { + // http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.12 + static gen_map_t* the_map = new gen_map_t(); + return *the_map; +} + +t_generator_factory::t_generator_factory(const std::string& short_name, + const std::string& long_name, + const std::string& documentation) + : short_name_(short_name), long_name_(long_name), documentation_(documentation) { + t_generator_registry::register_generator(this); +} diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_generator.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_generator.h new file mode 100644 index 000000000..cb9d076b5 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_generator.h @@ -0,0 +1,452 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef T_GENERATOR_H +#define T_GENERATOR_H +#define MSC_2015_VER 1900 + +#include <cstring> +#include <string> +#include <iomanip> +#include <iostream> +#include <fstream> +#include <limits> +#include <sstream> +#include "thrift/common.h" +#include "thrift/logging.h" +#include "thrift/version.h" +#include "thrift/generate/t_generator_registry.h" +#include "thrift/parse/t_program.h" + +/** + * Base class for a thrift code generator. This class defines the basic + * routines for code generation and contains the top level method that + * dispatches code generation across various components. + * + */ +class t_generator { +public: + t_generator(t_program* program) + : keywords_(lang_keywords()){ + tmp_ = 0; + indent_ = 0; + program_ = program; + program_name_ = get_program_name(program); + escape_['\n'] = "\\n"; + escape_['\r'] = "\\r"; + escape_['\t'] = "\\t"; + escape_['"'] = "\\\""; + escape_['\\'] = "\\\\"; + } + + virtual ~t_generator() {} + + /** + * Framework generator method that iterates over all the parts of a program + * and performs general actions. This is implemented by the base class and + * should not normally be overwritten in the subclasses. + */ + virtual void generate_program(); + + const t_program* get_program() const { return program_; } + + void generate_docstring_comment(std::ostream& out, + const std::string& comment_start, + const std::string& line_prefix, + const std::string& contents, + const std::string& comment_end); + + static void parse_options(const std::string& options, std::string& language, + std::map<std::string, std::string>& parsed_options); + + /** + * check whether sub-namespace declaraction is used by generator. + * e.g. allow + * namespace py.twisted bar + * to specify namespace to use when -gen py:twisted is specified. + * Will be called with subnamespace, i.e. is_valid_namespace("twisted") + * will be called for the above example. + */ + static bool is_valid_namespace(const std::string& sub_namespace) { + (void)sub_namespace; + return false; + } + + /** + * Escape string to use one in generated sources. + */ + virtual std::string escape_string(const std::string& in) const; + + std::string get_escaped_string(t_const_value* constval) { + return escape_string(constval->get_string()); + } + + /** + * Check if all identifiers are valid for the target language + */ + virtual void validate_input() const; + +protected: + virtual std::set<std::string> lang_keywords() const; + + /** + * A list of reserved words that cannot be used as identifiers. + */ + const std::set<std::string> keywords_; + + virtual void validate_id(const std::string& id) const; + + virtual void validate(t_enum const* en) const; + virtual void validate(t_enum_value const* en_val) const; + virtual void validate(t_typedef const* td) const; + virtual void validate(t_const const* c) const; + virtual void validate(t_service const* s) const; + virtual void validate(t_struct const* c) const; + virtual void validate(t_field const* f) const; + virtual void validate(t_function const* f) const; + + template <typename T> + void validate(const std::vector<T>& list) const; + + /** + * Optional methods that may be implemented by subclasses to take necessary + * steps at the beginning or end of code generation. + */ + + virtual void init_generator() {} + virtual void close_generator() {} + + virtual void generate_consts(std::vector<t_const*> consts); + + /** + * Pure virtual methods implemented by the generator subclasses. + */ + + virtual void generate_typedef(t_typedef* ttypedef) = 0; + virtual void generate_enum(t_enum* tenum) = 0; + virtual void generate_const(t_const* tconst) { (void)tconst; } + virtual void generate_struct(t_struct* tstruct) = 0; + virtual void generate_service(t_service* tservice) = 0; + virtual void generate_forward_declaration(t_struct*) {} + virtual void generate_xception(t_struct* txception) { + // By default exceptions are the same as structs + generate_struct(txception); + } + + /** + * Method to get the program name, may be overridden + */ + virtual std::string get_program_name(t_program* tprogram) { return tprogram->get_name(); } + + /** + * Method to get the service name, may be overridden + */ + virtual std::string get_service_name(t_service* tservice) { return tservice->get_name(); } + + /** + * Get the current output directory + */ + virtual std::string get_out_dir() const { + if (program_->is_out_path_absolute()) { + return program_->get_out_path() + "/"; + } + + return program_->get_out_path() + out_dir_base_ + "/"; + } + + /** + * Creates a unique temporary variable name, which is just "name" with a + * number appended to it (i.e. name35) + */ + std::string tmp(std::string name) { + std::ostringstream out; + out << name << tmp_++; + return out.str(); + } + + /** + * Generates a comment about this code being autogenerated, using C++ style + * comments, which are also fair game in Java / PHP, yay! + * + * @return C-style comment mentioning that this file is autogenerated. + */ + virtual std::string autogen_comment() { + return std::string("/**\n") + " * " + autogen_summary() + "\n" + " *\n" + + " * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n" + + " * @generated\n" + " */\n"; + } + + virtual std::string autogen_summary() { + return std::string("Autogenerated by Thrift Compiler (") + THRIFT_VERSION + ")"; + } + + /** + * Indentation level modifiers + */ + + void indent_up() { ++indent_; } + + void indent_down() { --indent_; } + + /** + * Indentation validation helper + */ + int indent_count() { return indent_; } + + void indent_validate( int expected, const char * func_name) { + if (indent_ != expected) { + pverbose("Wrong indent count in %s: difference = %i \n", func_name, (expected - indent_)); + } + } + + /** + * Indentation print function + */ + std::string indent() { + std::string ind = ""; + int i; + for (i = 0; i < indent_; ++i) { + ind += indent_str(); + } + return ind; + } + + /** + * Indentation utility wrapper + */ + std::ostream& indent(std::ostream& os) { return os << indent(); } + + /** + * Capitalization helpers + */ + std::string capitalize(std::string in) { + in[0] = toupper(in[0]); + return in; + } + std::string decapitalize(std::string in) { + in[0] = tolower(in[0]); + return in; + } + static std::string lowercase(std::string in) { + for (size_t i = 0; i < in.size(); ++i) { + in[i] = tolower(in[i]); + } + return in; + } + static std::string uppercase(std::string in) { + for (size_t i = 0; i < in.size(); ++i) { + in[i] = toupper(in[i]); + } + return in; + } + + /** + * Transforms a camel case string to an equivalent one separated by underscores + * e.g. aMultiWord -> a_multi_word + * someName -> some_name + * CamelCase -> camel_case + * name -> name + * Name -> name + */ + std::string underscore(std::string in) { + in[0] = tolower(in[0]); + for (size_t i = 1; i < in.size(); ++i) { + if (isupper(in[i])) { + in[i] = tolower(in[i]); + in.insert(i, "_"); + } + } + return in; + } + + /** + * Transforms a string with words separated by underscores to a camel case equivalent + * e.g. a_multi_word -> aMultiWord + * some_name -> someName + * name -> name + */ + std::string camelcase(std::string in) { + std::ostringstream out; + bool underscore = false; + + for (size_t i = 0; i < in.size(); i++) { + if (in[i] == '_') { + underscore = true; + continue; + } + if (underscore) { + out << (char)toupper(in[i]); + underscore = false; + continue; + } + out << in[i]; + } + + return out.str(); + } + + const std::string emit_double_as_string(const double value) { + std::stringstream double_output_stream; + // sets the maximum precision: http://en.cppreference.com/w/cpp/io/manip/setprecision + // sets the output format to fixed: http://en.cppreference.com/w/cpp/io/manip/fixed (not in scientific notation) + double_output_stream << std::setprecision(std::numeric_limits<double>::digits10 + 1); + + #ifdef _MSC_VER + // strtod is broken in MSVC compilers older than 2015, so std::fixed fails to format a double literal. + // more details: https://blogs.msdn.microsoft.com/vcblog/2014/06/18/ + // c-runtime-crt-features-fixes-and-breaking-changes-in-visual-studio-14-ctp1/ + // and + // http://www.exploringbinary.com/visual-c-plus-plus-strtod-still-broken/ + #if _MSC_VER >= MSC_2015_VER + double_output_stream << std::fixed; + #endif + #else + double_output_stream << std::fixed; + #endif + + double_output_stream << value; + + return double_output_stream.str(); + } + +public: + /** + * Get the true type behind a series of typedefs. + */ + static const t_type* get_true_type(const t_type* type) { return type->get_true_type(); } + static t_type* get_true_type(t_type* type) { return type->get_true_type(); } + +protected: + /** + * The program being generated + */ + t_program* program_; + + /** + * Quick accessor for formatted program name that is currently being + * generated. + */ + std::string program_name_; + + /** + * Quick accessor for formatted service name that is currently being + * generated. + */ + std::string service_name_; + + /** + * Output type-specifc directory name ("gen-*") + */ + std::string out_dir_base_; + + /** + * Map of characters to escape in string literals. + */ + std::map<char, std::string> escape_; + + virtual std::string indent_str() const { + return " "; + } + +private: + /** + * Current code indentation level + */ + int indent_; + + /** + * Temporary variable counter, for making unique variable names + */ + int tmp_; +}; + +template<typename _CharT, typename _Traits = std::char_traits<_CharT> > +class template_ofstream_with_content_based_conditional_update : public std::ostringstream { +public: + template_ofstream_with_content_based_conditional_update(): contents_written(false) {} + + template_ofstream_with_content_based_conditional_update(std::string const& output_file_path_) + : output_file_path(output_file_path_), contents_written(false) {} + + ~template_ofstream_with_content_based_conditional_update() { + if (!contents_written) { + close(); + } + } + + void open(std::string const& output_file_path_) { + output_file_path = output_file_path_; + clear_buf(); + contents_written = false; + } + + void close() { + if (contents_written || output_file_path == "") + return; + + if (!is_readable(output_file_path)) { + dump(); + return; + } + + std::ifstream old_file; + old_file.exceptions(old_file.exceptions() | std::ifstream::badbit | std::ifstream::failbit); + old_file.open(output_file_path.c_str(), std::ios::in); + + if (old_file) { + std::string const old_file_contents(static_cast<std::ostringstream const&>(std::ostringstream() << old_file.rdbuf()).str()); + old_file.close(); + + if (old_file_contents != str()) { + dump(); + } + } + } + +protected: + void dump() { + std::ofstream out_file; + out_file.exceptions(out_file.exceptions() | std::ofstream::badbit | std::ofstream::failbit); + try { + out_file.open(output_file_path.c_str(), std::ios::out); + } + catch (const std::ios_base::failure& e) { + ::failure("failed to write the output to the file '%s', details: '%s'", output_file_path.c_str(), e.what()); + } + out_file << str(); + out_file.close(); + clear_buf(); + contents_written = true; + } + + void clear_buf() { + str(std::string()); + } + + static bool is_readable(std::string const& file_name) { + return static_cast<bool>(std::ifstream(file_name.c_str())); + } + +private: + std::string output_file_path; + bool contents_written; +}; +typedef template_ofstream_with_content_based_conditional_update<char> ofstream_with_content_based_conditional_update; + +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_generator_registry.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_generator_registry.h new file mode 100644 index 000000000..d27f71082 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_generator_registry.h @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef T_GENERATOR_REGISTRY_H +#define T_GENERATOR_REGISTRY_H + +class t_generator; + +/** + * A factory for producing generator classes of a particular language. + * + * This class is also responsible for: + * - Registering itself with the generator registry. + * - Providing documentation for the generators it produces. + */ +class t_generator_factory { +public: + t_generator_factory(const std::string& short_name, + const std::string& long_name, + const std::string& documentation); + + virtual ~t_generator_factory() = default; + + virtual t_generator* get_generator( + // The program to generate. + t_program* program, + // Note: parsed_options will not exist beyond the call to get_generator. + const std::map<std::string, std::string>& parsed_options, + // Note: option_string might not exist beyond the call to get_generator. + const std::string& option_string) = 0; + + virtual bool is_valid_namespace(const std::string& sub_namespace) = 0; + + std::string get_short_name() { return short_name_; } + std::string get_long_name() { return long_name_; } + std::string get_documentation() { return documentation_; } + +private: + std::string short_name_; + std::string long_name_; + std::string documentation_; +}; + +template <typename generator> +class t_generator_factory_impl : public t_generator_factory { +public: + t_generator_factory_impl(const std::string& short_name, + const std::string& long_name, + const std::string& documentation) + : t_generator_factory(short_name, long_name, documentation) {} + + t_generator* get_generator(t_program* program, + const std::map<std::string, std::string>& parsed_options, + const std::string& option_string) override { + return new generator(program, parsed_options, option_string); + } + + bool is_valid_namespace(const std::string& sub_namespace) override { + return generator::is_valid_namespace(sub_namespace); + } +}; + +class t_generator_registry { +public: + static void register_generator(t_generator_factory* factory); + + static t_generator* get_generator(t_program* program, const std::string& options); + static t_generator* get_generator(t_program* program, + const std::string& laugnage, + const std::map<std::string, std::string>& parsed_options, + const std::string& options); + + typedef std::map<std::string, t_generator_factory*> gen_map_t; + static gen_map_t& get_generator_map(); + +private: + t_generator_registry(); + t_generator_registry(const t_generator_registry&); +}; + +#define THRIFT_REGISTER_GENERATOR(language, long_name, doc) \ + class t_##language##_generator_factory_impl \ + : public t_generator_factory_impl<t_##language##_generator> { \ + public: \ + t_##language##_generator_factory_impl() \ + : t_generator_factory_impl<t_##language##_generator>(#language, long_name, doc) {} \ + }; \ + static t_##language##_generator_factory_impl _registerer; + +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_go_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_go_generator.cc new file mode 100644 index 000000000..33b7547b4 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_go_generator.cc @@ -0,0 +1,3742 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* + * This file is programmatically sanitized for style: + * astyle --style=1tbs -f -p -H -j -U t_go_generator.cc + * + * The output of astyle should not be taken unquestioningly, but it is a good + * guide for ensuring uniformity and readability. + */ + +#include <fstream> +#include <iostream> +#include <limits> +#include <string> +#include <unordered_map> +#include <vector> + +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sstream> +#include <algorithm> +#include <clocale> +#include "thrift/platform.h" +#include "thrift/version.h" +#include "thrift/generate/t_generator.h" + +using std::map; +using std::ostream; +using std::ostringstream; +using std::string; +using std::stringstream; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes + +/** + * A helper for automatically formatting the emitted Go code from the Thrift + * IDL per the Go style guide. + * + * Returns: + * - true, if the formatting process succeeded. + * - false, if the formatting process failed, which means the basic output was + * still generated. + */ +bool format_go_output(const string& file_path); + +const string DEFAULT_THRIFT_IMPORT = "github.com/apache/thrift/lib/go/thrift"; +static std::string package_flag; + +/** + * Go code generator. + */ +class t_go_generator : public t_generator { +public: + t_go_generator(t_program* program, + const std::map<std::string, std::string>& parsed_options, + const std::string& option_string) + : t_generator(program) { + (void)option_string; + std::map<std::string, std::string>::const_iterator iter; + + + gen_thrift_import_ = DEFAULT_THRIFT_IMPORT; + gen_package_prefix_ = ""; + package_flag = ""; + read_write_private_ = false; + ignore_initialisms_ = false; + for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + if( iter->first.compare("package_prefix") == 0) { + gen_package_prefix_ = (iter->second); + } else if( iter->first.compare("thrift_import") == 0) { + gen_thrift_import_ = (iter->second); + } else if( iter->first.compare("package") == 0) { + package_flag = (iter->second); + } else if( iter->first.compare("read_write_private") == 0) { + read_write_private_ = true; + } else if( iter->first.compare("ignore_initialisms") == 0) { + ignore_initialisms_ = true; + } else { + throw "unknown option go:" + iter->first; + } + } + + out_dir_base_ = "gen-go"; + } + + /** + * Init and close methods + */ + + void init_generator() override; + void close_generator() override; + + /** + * Program-level generation functions + */ + + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_const(t_const* tconst) override; + void generate_struct(t_struct* tstruct) override; + void generate_xception(t_struct* txception) override; + void generate_service(t_service* tservice) override; + + std::string render_const_value(t_type* type, t_const_value* value, const string& name, bool opt = false); + + /** + * Struct generation code + */ + + void generate_go_struct(t_struct* tstruct, bool is_exception); + void generate_go_struct_definition(std::ostream& out, + t_struct* tstruct, + bool is_xception = false, + bool is_result = false, + bool is_args = false); + void generate_go_struct_initializer(std::ostream& out, + t_struct* tstruct, + bool is_args_or_result = false); + void generate_isset_helpers(std::ostream& out, + t_struct* tstruct, + const string& tstruct_name, + bool is_result = false); + void generate_countsetfields_helper(std::ostream& out, + t_struct* tstruct, + const string& tstruct_name, + bool is_result = false); + void generate_go_struct_reader(std::ostream& out, + t_struct* tstruct, + const string& tstruct_name, + bool is_result = false); + void generate_go_struct_writer(std::ostream& out, + t_struct* tstruct, + const string& tstruct_name, + bool is_result = false, + bool uses_countsetfields = false); + void generate_go_function_helpers(t_function* tfunction); + void get_publicized_name_and_def_value(t_field* tfield, + string* OUT_pub_name, + t_const_value** OUT_def_value) const; + + /** + * Service-level generation functions + */ + + void generate_service_helpers(t_service* tservice); + void generate_service_interface(t_service* tservice); + void generate_service_client(t_service* tservice); + void generate_service_remote(t_service* tservice); + void generate_service_server(t_service* tservice); + void generate_process_function(t_service* tservice, t_function* tfunction); + + /** + * Serialization constructs + */ + + void generate_deserialize_field(std::ostream& out, + t_field* tfield, + bool declare, + std::string prefix = "", + bool inclass = false, + bool coerceData = false, + bool inkey = false, + bool in_container = false); + + void generate_deserialize_struct(std::ostream& out, + t_struct* tstruct, + bool is_pointer_field, + bool declare, + std::string prefix = ""); + + void generate_deserialize_container(std::ostream& out, + t_type* ttype, + bool pointer_field, + bool declare, + std::string prefix = ""); + + void generate_deserialize_set_element(std::ostream& out, + t_set* tset, + bool declare, + std::string prefix = ""); + + void generate_deserialize_map_element(std::ostream& out, + t_map* tmap, + bool declare, + std::string prefix = ""); + + void generate_deserialize_list_element(std::ostream& out, + t_list* tlist, + bool declare, + std::string prefix = ""); + + void generate_serialize_field(std::ostream& out, + t_field* tfield, + std::string prefix = "", + bool inkey = false); + + void generate_serialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); + + void generate_serialize_container(std::ostream& out, + t_type* ttype, + bool pointer_field, + std::string prefix = ""); + + void generate_serialize_map_element(std::ostream& out, + t_map* tmap, + std::string kiter, + std::string viter); + + void generate_serialize_set_element(std::ostream& out, t_set* tmap, std::string iter); + + void generate_serialize_list_element(std::ostream& out, t_list* tlist, std::string iter); + + void generate_go_docstring(std::ostream& out, t_struct* tstruct); + + void generate_go_docstring(std::ostream& out, t_function* tfunction); + + void generate_go_docstring(std::ostream& out, + t_doc* tdoc, + t_struct* tstruct, + const char* subheader); + + void generate_go_docstring(std::ostream& out, t_doc* tdoc); + + /** + * Helper rendering functions + */ + + std::string go_autogen_comment(); + std::string go_package(); + std::string go_imports_begin(bool consts); + std::string go_imports_end(); + std::string render_includes(bool consts); + std::string render_included_programs(string& unused_protection); + std::string render_program_import(const t_program* program, string& unused_protection); + std::string render_system_packages(std::vector<string> &system_packages); + std::string render_import_protection(); + std::string render_fastbinary_includes(); + std::string declare_argument(t_field* tfield); + std::string render_field_initial_value(t_field* tfield, const string& name, bool optional_field); + std::string type_name(t_type* ttype); + std::string module_name(t_type* ttype); + std::string function_signature(t_function* tfunction, std::string prefix = ""); + std::string function_signature_if(t_function* tfunction, + std::string prefix = "", + bool addError = false); + std::string argument_list(t_struct* tstruct); + std::string type_to_enum(t_type* ttype); + std::string type_to_go_type(t_type* ttype); + std::string type_to_go_type_with_opt(t_type* ttype, + bool optional_field); + std::string type_to_go_key_type(t_type* ttype); + std::string type_to_spec_args(t_type* ttype); + + static std::string get_real_go_module(const t_program* program) { + + if (!package_flag.empty()) { + return package_flag; + } + std::string real_module = program->get_namespace("go"); + if (!real_module.empty()) { + return real_module; + } + + return lowercase(program->get_name()); + } + +private: + std::string gen_package_prefix_; + std::string gen_thrift_import_; + bool read_write_private_; + bool ignore_initialisms_; + + /** + * File streams + */ + + ofstream_with_content_based_conditional_update f_types_; + std::string f_types_name_; + ofstream_with_content_based_conditional_update f_consts_; + std::string f_consts_name_; + std::stringstream f_const_values_; + + std::string package_name_; + std::string package_dir_; + std::unordered_map<std::string, std::string> package_identifiers_; + std::set<std::string> package_identifiers_set_; + std::string read_method_name_; + std::string write_method_name_; + + std::set<std::string> commonInitialisms; + + std::string camelcase(const std::string& value) const; + void fix_common_initialism(std::string& value, int i) const; + std::string publicize(const std::string& value, bool is_args_or_result = false) const; + std::string privatize(const std::string& value) const; + std::string new_prefix(const std::string& value) const; + static std::string variable_name_to_go_name(const std::string& value); + static bool is_pointer_field(t_field* tfield, bool in_container = false); + static bool omit_initialization(t_field* tfield); +}; + +// returns true if field initialization can be omitted since it has corresponding go type zero value +// or default value is not set +bool t_go_generator::omit_initialization(t_field* tfield) { + t_const_value* value = tfield->get_value(); + if (!value) { + return true; + } + t_type* type = tfield->get_type()->get_true_type(); + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + + switch (tbase) { + case t_base_type::TYPE_VOID: + throw ""; + + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + //[]byte are always inline + return false; + } + // strings are pointers if has no default + return value->get_string().empty(); + + case t_base_type::TYPE_BOOL: + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + return value->get_integer() == 0; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) { + return value->get_integer() == 0; + } else { + return value->get_double() == 0.; + } + } + } + return false; +} + +// Returns true if the type need a reference if used as optional without default +static bool type_need_reference(t_type* type) { + type = type->get_true_type(); + if (type->is_map() || type->is_set() || type->is_list() || type->is_struct() + || type->is_xception() || type->is_binary()) { + return false; + } + return true; +} + +// returns false if field could not use comparison to default value as !IsSet* +bool t_go_generator::is_pointer_field(t_field* tfield, bool in_container_value) { + (void)in_container_value; + if (tfield->annotations_.count("cpp.ref") != 0) { + return true; + } + t_type* type = tfield->get_type()->get_true_type(); + // Structs in containers are pointers + if (type->is_struct() || type->is_xception()) { + return true; + } + if (!(tfield->get_req() == t_field::T_OPTIONAL)) { + return false; + } + bool has_default = tfield->get_value(); + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + + switch (tbase) { + case t_base_type::TYPE_VOID: + throw ""; + + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + //[]byte are always inline + return false; + } + // strings are pointers if has no default + return !has_default; + + case t_base_type::TYPE_BOOL: + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + case t_base_type::TYPE_DOUBLE: + return !has_default; + } + } else if (type->is_enum()) { + return !has_default; + } else if (type->is_struct() || type->is_xception()) { + return true; + } else if (type->is_map()) { + return has_default; + } else if (type->is_set()) { + return has_default; + } else if (type->is_list()) { + return has_default; + } else if (type->is_typedef()) { + return has_default; + } + + throw "INVALID TYPE IN type_to_go_type: " + type->get_name(); +} + +std::string t_go_generator::camelcase(const std::string& value) const { + std::string value2(value); + std::setlocale(LC_ALL, "C"); // set locale to classic + + // Fix common initialism in first word + fix_common_initialism(value2, 0); + + // as long as we are changing things, let's change _ followed by lowercase to + // capital and fix common initialisms + for (std::string::size_type i = 1; i < value2.size() - 1; ++i) { + if (value2[i] == '_') { + if (islower(value2[i + 1])) { + value2.replace(i, 2, 1, toupper(value2[i + 1])); + } + + if (i > static_cast<std::string::size_type>(std::numeric_limits<int>().max())) { + throw "integer overflow in t_go_generator::camelcase, value = " + value; + } + fix_common_initialism(value2, static_cast<int>(i)); + } + } + + return value2; +} + +// Checks to see if the word starting at i in value contains a common initialism +// and if so replaces it with the upper case version of the word. +void t_go_generator::fix_common_initialism(std::string& value, int i) const { + if (!ignore_initialisms_) { + size_t wordLen = value.find('_', i); + if (wordLen != std::string::npos) { + wordLen -= i; + } + std::string word = value.substr(i, wordLen); + std::transform(word.begin(), word.end(), word.begin(), ::toupper); + if (commonInitialisms.find(word) != commonInitialisms.end()) { + value.replace(i, word.length(), word); + } + } +} + +std::string t_go_generator::publicize(const std::string& value, bool is_args_or_result) const { + if (value.size() <= 0) { + return value; + } + + std::string value2(value), prefix; + + string::size_type dot_pos = value.rfind('.'); + if (dot_pos != string::npos) { + prefix = value.substr(0, dot_pos + 1) + prefix; + value2 = value.substr(dot_pos + 1); + } + + if (!isupper(value2[0])) { + value2[0] = toupper(value2[0]); + } + + value2 = camelcase(value2); + + // final length before further checks, the string may become longer + size_t len_before = value2.length(); + + // IDL identifiers may start with "New" which interferes with the CTOR pattern + // Adding an extra underscore to all those identifiers solves this + if ((len_before >= 3) && (value2.substr(0, 3) == "New")) { + value2 += '_'; + } + + // IDL identifiers may end with "Args"/"Result" which interferes with the implicit service + // function structs + // Adding another extra underscore to all those identifiers solves this + // Suppress this check for the actual helper struct names + if (!is_args_or_result) { + bool ends_with_args = (len_before >= 4) && (value2.substr(len_before - 4, 4) == "Args"); + bool ends_with_rslt = (len_before >= 6) && (value2.substr(len_before - 6, 6) == "Result"); + if (ends_with_args || ends_with_rslt) { + value2 += '_'; + } + } + + // Avoid naming collisions with other services + if (is_args_or_result) { + prefix += publicize(service_name_); + } + + return prefix + value2; +} + +std::string t_go_generator::new_prefix(const std::string& value) const { + if (value.size() <= 0) { + return value; + } + + string::size_type dot_pos = value.rfind('.'); + if (dot_pos != string::npos) { + return value.substr(0, dot_pos + 1) + "New" + publicize(value.substr(dot_pos + 1)); + } + return "New" + publicize(value); +} + +std::string t_go_generator::privatize(const std::string& value) const { + if (value.size() <= 0) { + return value; + } + + std::string value2(value); + + if (!islower(value2[0])) { + value2[0] = tolower(value2[0]); + } + + value2 = camelcase(value2); + + return value2; +} + +std::string t_go_generator::variable_name_to_go_name(const std::string& value) { + if (value.size() <= 0) { + return value; + } + + std::string value2(value); + std::transform(value2.begin(), value2.end(), value2.begin(), ::tolower); + + switch (value[0]) { + case 'b': + case 'B': + if (value2 != "break") { + return value; + } + + break; + + case 'c': + case 'C': + if (value2 != "case" && value2 != "chan" && value2 != "const" && value2 != "continue") { + return value; + } + + break; + + case 'd': + case 'D': + if (value2 != "default" && value2 != "defer") { + return value; + } + + break; + + case 'e': + case 'E': + if (value2 != "else" && value2 != "error") { + return value; + } + + break; + + case 'f': + case 'F': + if (value2 != "fallthrough" && value2 != "for" && value2 != "func") { + return value; + } + + break; + + case 'g': + case 'G': + if (value2 != "go" && value2 != "goto") { + return value; + } + + break; + + case 'i': + case 'I': + if (value2 != "if" && value2 != "import" && value2 != "interface") { + return value; + } + + break; + + case 'm': + case 'M': + if (value2 != "map") { + return value; + } + + break; + + case 'p': + case 'P': + if (value2 != "package") { + return value; + } + + break; + + case 'r': + case 'R': + if (value2 != "range" && value2 != "return") { + return value; + } + + break; + + case 's': + case 'S': + if (value2 != "select" && value2 != "struct" && value2 != "switch") { + return value; + } + + break; + + case 't': + case 'T': + if (value2 != "type") { + return value; + } + + break; + + case 'v': + case 'V': + if (value2 != "var") { + return value; + } + + break; + + default: + return value; + } + + return value2 + "_a1"; +} + +/** + * Prepares for file generation by opening up the necessary file output + * streams. + * + * @param tprogram The program to generate + */ +void t_go_generator::init_generator() { + // Make output directory + string module = get_real_go_module(program_); + string target = module; + package_dir_ = get_out_dir(); + + // This set is taken from https://github.com/golang/lint/blob/master/lint.go#L692 + commonInitialisms.insert("API"); + commonInitialisms.insert("ASCII"); + commonInitialisms.insert("CPU"); + commonInitialisms.insert("CSS"); + commonInitialisms.insert("DNS"); + commonInitialisms.insert("EOF"); + commonInitialisms.insert("GUID"); + commonInitialisms.insert("HTML"); + commonInitialisms.insert("HTTP"); + commonInitialisms.insert("HTTPS"); + commonInitialisms.insert("ID"); + commonInitialisms.insert("IP"); + commonInitialisms.insert("JSON"); + commonInitialisms.insert("LHS"); + commonInitialisms.insert("QPS"); + commonInitialisms.insert("RAM"); + commonInitialisms.insert("RHS"); + commonInitialisms.insert("RPC"); + commonInitialisms.insert("SLA"); + commonInitialisms.insert("SMTP"); + commonInitialisms.insert("SSH"); + commonInitialisms.insert("TCP"); + commonInitialisms.insert("TLS"); + commonInitialisms.insert("TTL"); + commonInitialisms.insert("UDP"); + commonInitialisms.insert("UI"); + commonInitialisms.insert("UID"); + commonInitialisms.insert("UUID"); + commonInitialisms.insert("URI"); + commonInitialisms.insert("URL"); + commonInitialisms.insert("UTF8"); + commonInitialisms.insert("VM"); + commonInitialisms.insert("XML"); + commonInitialisms.insert("XSRF"); + commonInitialisms.insert("XSS"); + + // names of read and write methods + if (read_write_private_) { + read_method_name_ = "read"; + write_method_name_ = "write"; + } else { + read_method_name_ = "Read"; + write_method_name_ = "Write"; + } + + while (true) { + // TODO: Do better error checking here. + MKDIR(package_dir_.c_str()); + + if (module.empty()) { + break; + } + + string::size_type pos = module.find('.'); + + if (pos == string::npos) { + package_dir_ += "/"; + package_dir_ += module; + package_name_ = module; + module.clear(); + } else { + package_dir_ += "/"; + package_dir_ += module.substr(0, pos); + module.erase(0, pos + 1); + } + } + + string::size_type loc; + + while ((loc = target.find(".")) != string::npos) { + target.replace(loc, 1, 1, '/'); + } + + // Make output files + f_types_name_ = package_dir_ + "/" + program_name_ + ".go"; + f_types_.open(f_types_name_); + + f_consts_name_ = package_dir_ + "/" + program_name_ + "-consts.go"; + f_consts_.open(f_consts_name_); + + // Print header + f_types_ << go_autogen_comment() << go_package() << render_includes(false); + + f_consts_ << go_autogen_comment() << go_package() << render_includes(true); + + f_const_values_ << endl << "func init() {" << endl; + + // Create file for the GoUnusedProtection__ variable + string f_unused_prot_name_ = package_dir_ + "/" + "GoUnusedProtection__.go"; + ofstream_with_content_based_conditional_update f_unused_prot_; + f_unused_prot_.open(f_unused_prot_name_.c_str()); + f_unused_prot_ << go_autogen_comment() << go_package() << render_import_protection(); + f_unused_prot_.close(); +} + +string t_go_generator::render_included_programs(string& unused_prot) { + const vector<t_program*>& includes = program_->get_includes(); + string result = ""; + string local_namespace = program_->get_namespace("go"); + for (auto include : includes) { + if (!local_namespace.empty() && local_namespace == include->get_namespace("go")) { + continue; + } + + result += render_program_import(include, unused_prot); + } + return result; +} + +string t_go_generator::render_program_import(const t_program* program, string& unused_protection) { + string result = ""; + + string go_module = get_real_go_module(program); + string go_path = go_module; + size_t found = 0; + for (size_t j = 0; j < go_module.size(); j++) { + // Import statement uses slashes ('/') in namespace + if (go_module[j] == '.') { + go_path[j] = '/'; + found = j + 1; + } + } + + auto it = package_identifiers_.find(go_module); + auto last_component = go_module.substr(found); + if (it == package_identifiers_.end()) { + auto value = last_component; + // This final path component has already been used, let's construct a more unique alias + if (package_identifiers_set_.find(value) != package_identifiers_set_.end()) { + // TODO: This would produce more readable code if it appended trailing go_module + // path components to generate a more readable name unique identifier (e.g. use + // packageacommon as the alias for packagea/common instead of common=). But just + // appending an integer is much simpler code + value = tmp(value); + } + package_identifiers_set_.insert(value); + it = package_identifiers_.emplace(go_module, std::move(value)).first; + } + auto const& package_identifier = it->second; + result += "\t"; + // if the package_identifier is different than final path component we need an alias + if (last_component.compare(package_identifier) != 0) { + result += package_identifier + " "; + } + string s; + + for (auto const& e : package_identifiers_set_) + { + s += e; + s += ','; + } + + s.pop_back(); + + result += "\"" + gen_package_prefix_ + go_path + "\"\n"; + unused_protection += "var _ = " + package_identifier + ".GoUnusedProtection__\n"; + return result; +} + +string t_go_generator::render_system_packages(std::vector<string>& system_packages) { + string result = ""; + + for (vector<string>::iterator iter = system_packages.begin(); iter != system_packages.end(); ++iter) { + string package = *iter; + result += "\t\""+ package +"\"\n"; + + // Reserve these package names in case the collide with imported Thrift packages + package_identifiers_set_.insert(package); + package_identifiers_.emplace(package, package); + } + return result; +} + +/** + * Renders all the imports necessary for including another Thrift program. + * If consts include the additional imports. + */ +string t_go_generator::render_includes(bool consts) { + const vector<t_program*>& includes = program_->get_includes(); + string result = ""; + string unused_prot = ""; + result += go_imports_begin(consts); + result += render_included_programs(unused_prot); + + if (includes.size() > 0) { + result += "\n"; + } + + return result + go_imports_end() + unused_prot; +} + +string t_go_generator::render_import_protection() { + return string("var GoUnusedProtection__ int;\n\n"); +} + +/** + * Renders all the imports necessary to use the accelerated TBinaryProtocol + */ +string t_go_generator::render_fastbinary_includes() { + return ""; +} + +/** + * Autogen'd comment + */ +string t_go_generator::go_autogen_comment() { + return + std::string() + + "// Autogenerated by Thrift Compiler (" + THRIFT_VERSION + ")\n" + "// DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n\n"; +} + +/** + * Prints standard thrift package + */ +string t_go_generator::go_package() { + return string("package ") + package_name_ + "\n\n"; +} + +/** + * Render the beginning of the import statement. + * If consts include the additional imports. + */ +string t_go_generator::go_imports_begin(bool consts) { + std::vector<string> system_packages; + system_packages.push_back("bytes"); + system_packages.push_back("context"); + system_packages.push_back("reflect"); + // If not writing constants, and there are enums, need extra imports. + if (!consts && get_program()->get_enums().size() > 0) { + system_packages.push_back("database/sql/driver"); + system_packages.push_back("errors"); + } + system_packages.push_back("fmt"); + system_packages.push_back(gen_thrift_import_); + return "import(\n" + render_system_packages(system_packages); +} + +/** + * End the import statement, include undscore-assignments + * + * These "_ =" prevent the go compiler complaining about unused imports. + * This will have to do in lieu of more intelligent import statement construction + */ +string t_go_generator::go_imports_end() { + return string( + ")\n\n" + "// (needed to ensure safety because of naive import list construction.)\n" + "var _ = thrift.ZERO\n" + "var _ = fmt.Printf\n" + "var _ = context.Background\n" + "var _ = reflect.DeepEqual\n" + "var _ = bytes.Equal\n\n"); +} + +/** + * Closes the type files + */ +void t_go_generator::close_generator() { + f_const_values_ << "}" << endl << endl; + f_consts_ << f_const_values_.str(); + + // Close types and constants files + f_consts_.close(); + f_types_.close(); + format_go_output(f_types_name_); + format_go_output(f_consts_name_); +} + +/** + * Generates a typedef. + * + * @param ttypedef The type definition + */ +void t_go_generator::generate_typedef(t_typedef* ttypedef) { + generate_go_docstring(f_types_, ttypedef); + string new_type_name(publicize(ttypedef->get_symbolic())); + string base_type(type_to_go_type(ttypedef->get_type())); + + if (base_type == new_type_name) { + return; + } + + f_types_ << "type " << new_type_name << " " << base_type << endl << endl; + // Generate a convenience function that converts an instance of a type + // (which may be a constant) into a pointer to an instance of a type. + f_types_ << "func " << new_type_name << "Ptr(v " << new_type_name << ") *" << new_type_name + << " { return &v }" << endl << endl; +} + +/** + * Generates code for an enumerated type. Done using a class to scope + * the values. + * + * @param tenum The enumeration + */ +void t_go_generator::generate_enum(t_enum* tenum) { + std::ostringstream to_string_mapping, from_string_mapping; + std::string tenum_name(publicize(tenum->get_name())); + generate_go_docstring(f_types_, tenum); + f_types_ << "type " << tenum_name << " int64" << endl << "const (" << endl; + + to_string_mapping << indent() << "func (p " << tenum_name << ") String() string {" << endl; + to_string_mapping << indent() << " switch p {" << endl; + + from_string_mapping << indent() << "func " << tenum_name << "FromString(s string) (" << tenum_name + << ", error) {" << endl; + from_string_mapping << indent() << " switch s {" << endl; + + vector<t_enum_value*> constants = tenum->get_constants(); + vector<t_enum_value*>::iterator c_iter; + int value = -1; + + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + value = (*c_iter)->get_value(); + + string iter_std_name(escape_string((*c_iter)->get_name())); + string iter_name((*c_iter)->get_name()); + f_types_ << indent() << " " << tenum_name << "_" << iter_name << ' ' << tenum_name << " = " + << value << endl; + // Dictionaries to/from string names of enums + to_string_mapping << indent() << " case " << tenum_name << "_" << iter_name << ": return \"" + << iter_std_name << "\"" << endl; + + if (iter_std_name != escape_string(iter_name)) { + from_string_mapping << indent() << " case \"" << iter_std_name << "\", \"" + << escape_string(iter_name) << "\": return " << tenum_name << "_" + << iter_name << ", nil " << endl; + } else { + from_string_mapping << indent() << " case \"" << iter_std_name << "\": return " << tenum_name + << "_" << iter_name << ", nil " << endl; + } + } + + to_string_mapping << indent() << " }" << endl; + to_string_mapping << indent() << " return \"<UNSET>\"" << endl; + to_string_mapping << indent() << "}" << endl; + from_string_mapping << indent() << " }" << endl; + from_string_mapping << indent() << " return " << tenum_name << "(0)," + << " fmt.Errorf(\"not a valid " << tenum_name << " string\")" << endl; + from_string_mapping << indent() << "}" << endl; + + f_types_ << ")" << endl << endl << to_string_mapping.str() << endl << from_string_mapping.str() + << endl << endl; + + // Generate a convenience function that converts an instance of an enum + // (which may be a constant) into a pointer to an instance of that enum + // type. + f_types_ << "func " << tenum_name << "Ptr(v " << tenum_name << ") *" << tenum_name + << " { return &v }" << endl << endl; + + // Generate MarshalText + f_types_ << "func (p " << tenum_name << ") MarshalText() ([]byte, error) {" << endl; + f_types_ << "return []byte(p.String()), nil" << endl; + f_types_ << "}" << endl << endl; + + // Generate UnmarshalText + f_types_ << "func (p *" << tenum_name << ") UnmarshalText(text []byte) error {" << endl; + f_types_ << "q, err := " << tenum_name << "FromString(string(text))" << endl; + f_types_ << "if (err != nil) {" << endl << "return err" << endl << "}" << endl; + f_types_ << "*p = q" << endl; + f_types_ << "return nil" << endl; + f_types_ << "}" << endl << endl; + + // Generate Scan for sql.Scanner interface + f_types_ << "func (p *" << tenum_name << ") Scan(value interface{}) error {" <<endl; + f_types_ << "v, ok := value.(int64)" <<endl; + f_types_ << "if !ok {" <<endl; + f_types_ << "return errors.New(\"Scan value is not int64\")" <<endl; + f_types_ << "}" <<endl; + f_types_ << "*p = " << tenum_name << "(v)" << endl; + f_types_ << "return nil" << endl; + f_types_ << "}" << endl << endl; + + // Generate Value for driver.Valuer interface + f_types_ << "func (p * " << tenum_name << ") Value() (driver.Value, error) {" <<endl; + f_types_ << " if p == nil {" << endl; + f_types_ << " return nil, nil" << endl; + f_types_ << " }" << endl; + f_types_ << "return int64(*p), nil" << endl; + f_types_ << "}" << endl; + +} + +/** + * Generate a constant value + */ +void t_go_generator::generate_const(t_const* tconst) { + t_type* type = tconst->get_type(); + string name = publicize(tconst->get_name()); + t_const_value* value = tconst->get_value(); + if (type->is_base_type() || type->is_enum()) { + indent(f_consts_) << "const " << name << " = " << render_const_value(type, value, name) << endl; + } else { + f_const_values_ << indent() << name << " = " << render_const_value(type, value, name) << endl + << endl; + + f_consts_ << indent() << "var " << name << " " << type_to_go_type(type) << endl; + } +} + +/** + * Prints the value of a constant with the given type. Note that type checking + * is NOT performed in this function as it is always run beforehand using the + * validate_types method in main.cc + */ +string t_go_generator::render_const_value(t_type* type, t_const_value* value, const string& name, bool opt) { + type = get_true_type(type); + std::ostringstream out; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + + if (opt) { + out << "&(&struct{x "; + switch (tbase) { + case t_base_type::TYPE_BOOL: + out << "bool}{"; + out << (value->get_integer() > 0 ? "true" : "false"); + break; + + case t_base_type::TYPE_I8: + out << "int8}{"; + out << value->get_integer(); + break; + case t_base_type::TYPE_I16: + out << "int16}{"; + out << value->get_integer(); + break; + case t_base_type::TYPE_I32: + out << "int32}{"; + out << value->get_integer(); + break; + case t_base_type::TYPE_I64: + out << "int64}{"; + out << value->get_integer(); + break; + + case t_base_type::TYPE_DOUBLE: + out << "float64}{"; + if (value->get_type() == t_const_value::CV_INTEGER) { + out << value->get_integer(); + } else { + out << value->get_double(); + } + break; + + case t_base_type::TYPE_STRING: + out << "string}{"; + out << '"' + get_escaped_string(value) + '"'; + break; + + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + out << "}).x"; + } else { + switch (tbase) { + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + out << "[]byte(\"" << get_escaped_string(value) << "\")"; + } else { + out << '"' << get_escaped_string(value) << '"'; + } + + break; + + case t_base_type::TYPE_BOOL: + out << (value->get_integer() > 0 ? "true" : "false"); + break; + + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + if (opt) { + out << "&(&struct{x int}{"; + } + out << value->get_integer(); + if (opt) { + out << "}).x"; + } + break; + + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) { + out << value->get_integer(); + } else { + out << value->get_double(); + } + + break; + + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + } + } else if (type->is_enum()) { + indent(out) << value->get_integer(); + } else if (type->is_struct() || type->is_xception()) { + out << "&" << publicize(type_name(type)) << "{"; + indent_up(); + const vector<t_field*>& fields = ((t_struct*)type)->get_members(); + vector<t_field*>::const_iterator f_iter; + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + t_type* field_type = NULL; + bool is_optional = false; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field_type = (*f_iter)->get_type(); + is_optional = is_pointer_field(*f_iter); + } + } + + if (field_type == NULL) { + throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string(); + } + out << endl << indent() << publicize(v_iter->first->get_string()) << ": " + << render_const_value(field_type, v_iter->second, name, is_optional) << "," << endl; + } + + indent_down(); + out << "}"; + + } else if (type->is_map()) { + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + out << "map[" << type_to_go_type(ktype) << "]" << type_to_go_type(vtype) << "{" << endl; + indent_up(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + out << indent() << render_const_value(ktype, v_iter->first, name) << ": " + << render_const_value(vtype, v_iter->second, name) << "," << endl; + } + + indent_down(); + out << indent() << "}"; + } else if (type->is_list()) { + t_type* etype = ((t_list*)type)->get_elem_type(); + const vector<t_const_value*>& val = value->get_list(); + out << "[]" << type_to_go_type(etype) << "{" << endl; + indent_up(); + vector<t_const_value*>::const_iterator v_iter; + + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + out << indent() << render_const_value(etype, *v_iter, name) << ", "; + } + + indent_down(); + out << indent() << "}"; + } else if (type->is_set()) { + t_type* etype = ((t_set*)type)->get_elem_type(); + const vector<t_const_value*>& val = value->get_list(); + out << "[]" << type_to_go_key_type(etype) << "{" << endl; + indent_up(); + vector<t_const_value*>::const_iterator v_iter; + + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + out << indent() << render_const_value(etype, *v_iter, name) << ", "; + } + + indent_down(); + out << indent() << "}"; + } else { + throw "CANNOT GENERATE CONSTANT FOR TYPE: " + type->get_name(); + } + + return out.str(); +} + +/** + * Generates a go struct + */ +void t_go_generator::generate_struct(t_struct* tstruct) { + generate_go_struct(tstruct, false); +} + +/** + * Generates a struct definition for a thrift exception. Basically the same + * as a struct but extends the Exception class. + * + * @param txception The struct definition + */ +void t_go_generator::generate_xception(t_struct* txception) { + generate_go_struct(txception, true); +} + +/** + * Generates a go struct + */ +void t_go_generator::generate_go_struct(t_struct* tstruct, bool is_exception) { + generate_go_struct_definition(f_types_, tstruct, is_exception); +} + +void t_go_generator::get_publicized_name_and_def_value(t_field* tfield, + string* OUT_pub_name, + t_const_value** OUT_def_value) const { + const string base_field_name = tfield->get_name(); + const string escaped_field_name = escape_string(base_field_name); + *OUT_pub_name = publicize(escaped_field_name); + *OUT_def_value = tfield->get_value(); +} + +void t_go_generator::generate_go_struct_initializer(ostream& out, + t_struct* tstruct, + bool is_args_or_result) { + out << publicize(type_name(tstruct), is_args_or_result) << "{"; + const vector<t_field*>& members = tstruct->get_members(); + for (auto member : members) { + bool pointer_field = is_pointer_field(member); + string publicized_name; + t_const_value* def_value; + get_publicized_name_and_def_value(member, &publicized_name, &def_value); + if (!pointer_field && def_value != NULL && !omit_initialization(member)) { + out << endl << indent() << publicized_name << ": " + << render_field_initial_value(member, member->get_name(), pointer_field) << "," + << endl; + } + } + + out << "}" << endl; +} + +/** + * Generates a struct definition for a thrift data type. + * + * @param tstruct The struct definition + */ +void t_go_generator::generate_go_struct_definition(ostream& out, + t_struct* tstruct, + bool is_exception, + bool is_result, + bool is_args) { + const vector<t_field*>& members = tstruct->get_members(); + const vector<t_field*>& sorted_members = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator m_iter; + + std::string tstruct_name(publicize(tstruct->get_name(), is_args || is_result)); + generate_go_docstring(out, tstruct); + out << indent() << "type " << tstruct_name << " struct {" << endl; + /* + Here we generate the structure specification for the fastbinary codec. + These specifications have the following structure: + thrift_spec -> tuple of item_spec + item_spec -> nil | (tag, type_enum, name, spec_args, default) + tag -> integer + type_enum -> TType.I32 | TType.STRING | TType.STRUCT | ... + name -> string_literal + default -> nil # Handled by __init__ + spec_args -> nil # For simple types + | (type_enum, spec_args) # Value type for list/set + | (type_enum, spec_args, type_enum, spec_args) + # Key and value for map + | (class_name, spec_args_ptr) # For struct/exception + class_name -> identifier # Basically a pointer to the class + spec_args_ptr -> expression # just class_name.spec_args + + TODO(dreiss): Consider making this work for structs with negative tags. + */ + // TODO(dreiss): Look into generating an empty tuple instead of nil + // for structures with no members. + // TODO(dreiss): Test encoding of structs where some inner structs + // don't have thrift_spec. + indent_up(); + + int num_setable = 0; + if (sorted_members.empty() || (sorted_members[0]->get_key() >= 0)) { + int sorted_keys_pos = 0; + + for (m_iter = sorted_members.begin(); m_iter != sorted_members.end(); ++m_iter) { + // Set field to optional if field is union, this is so we can get a + // pointer to the field. + if (tstruct->is_union()) + (*m_iter)->set_req(t_field::T_OPTIONAL); + if (sorted_keys_pos != (*m_iter)->get_key()) { + int first_unused = (std::max)(1, sorted_keys_pos++); + while (sorted_keys_pos != (*m_iter)->get_key()) { + ++sorted_keys_pos; + } + int last_unused = sorted_keys_pos - 1; + if (first_unused < last_unused) { + indent(out) << "// unused fields # " << first_unused << " to " << last_unused << endl; + } else if (first_unused == last_unused) { + indent(out) << "// unused field # " << first_unused << endl; + } + } + + t_type* fieldType = (*m_iter)->get_type(); + string goType = type_to_go_type_with_opt(fieldType, is_pointer_field(*m_iter)); + string gotag = "db:\"" + escape_string((*m_iter)->get_name()) + "\" "; + + // Only add the `omitempty` tag if this field is optional and has no default value. + // Otherwise a proper value like `false` for a bool field will be ommitted from + // the JSON output since Go Marshal won't output `zero values`. + bool has_default = (*m_iter)->get_value(); + bool is_optional = (*m_iter)->get_req() == t_field::T_OPTIONAL; + if (is_optional && !has_default) { + gotag += "json:\"" + escape_string((*m_iter)->get_name()) + ",omitempty\""; + } else { + gotag += "json:\"" + escape_string((*m_iter)->get_name()) + "\""; + } + + // Check for user override of db and json tags using "go.tag" + std::map<string, string>::iterator it = (*m_iter)->annotations_.find("go.tag"); + if (it != (*m_iter)->annotations_.end()) { + gotag = it->second; + } + indent(out) << publicize((*m_iter)->get_name()) << " " << goType << " `thrift:\"" + << escape_string((*m_iter)->get_name()) << "," << sorted_keys_pos; + if ((*m_iter)->get_req() == t_field::T_REQUIRED) { + out << ",required"; + } + + out << "\" " << gotag << "`" << endl; + sorted_keys_pos++; + } + } else { + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + // This fills in default values, as opposed to nulls + out << indent() << publicize((*m_iter)->get_name()) << " " + << type_to_go_type((*m_iter)->get_type()) << endl; + } + } + + indent_down(); + out << indent() << "}" << endl << endl; + out << indent() << "func New" << tstruct_name << "() *" << tstruct_name << " {" << endl; + out << indent() << " return &"; + generate_go_struct_initializer(out, tstruct, is_result || is_args); + out << indent() << "}" << endl << endl; + // Default values for optional fields + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + string publicized_name; + t_const_value* def_value; + get_publicized_name_and_def_value(*m_iter, &publicized_name, &def_value); + t_type* fieldType = (*m_iter)->get_type(); + string goType = type_to_go_type_with_opt(fieldType, false); + string def_var_name = tstruct_name + "_" + publicized_name + "_DEFAULT"; + if ((*m_iter)->get_req() == t_field::T_OPTIONAL || is_pointer_field(*m_iter)) { + out << indent() << "var " << def_var_name << " " << goType; + if (def_value != NULL) { + out << " = " << render_const_value(fieldType, def_value, (*m_iter)->get_name()); + } + out << endl; + } + + // num_setable is used for deciding if Count* methods will be generated for union fields. + // This applies to all nullable fields including slices (used for set, list and binary) and maps, not just pointers. + t_type* type = fieldType->get_true_type(); + if (is_pointer_field(*m_iter)|| type->is_map() || type->is_set() || type->is_list() || type->is_binary()) { + num_setable += 1; + } + + if (is_pointer_field(*m_iter)) { + string goOptType = type_to_go_type_with_opt(fieldType, true); + string maybepointer = goOptType != goType ? "*" : ""; + out << indent() << "func (p *" << tstruct_name << ") Get" << publicized_name << "() " + << goType << " {" << endl; + out << indent() << " if !p.IsSet" << publicized_name << "() {" << endl; + out << indent() << " return " << def_var_name << endl; + out << indent() << " }" << endl; + out << indent() << "return " << maybepointer << "p." << publicized_name << endl; + out << indent() << "}" << endl; + } else { + out << endl; + out << indent() << "func (p *" << tstruct_name << ") Get" << publicized_name << "() " + << goType << " {" << endl; + out << indent() << " return p." << publicized_name << endl; + out << indent() << "}" << endl; + } + } + + if (tstruct->is_union() && num_setable > 0) { + generate_countsetfields_helper(out, tstruct, tstruct_name, is_result); + } + + generate_isset_helpers(out, tstruct, tstruct_name, is_result); + generate_go_struct_reader(out, tstruct, tstruct_name, is_result); + generate_go_struct_writer(out, tstruct, tstruct_name, is_result, num_setable > 0); + + out << indent() << "func (p *" << tstruct_name << ") String() string {" << endl; + out << indent() << " if p == nil {" << endl; + out << indent() << " return \"<nil>\"" << endl; + out << indent() << " }" << endl; + out << indent() << " return fmt.Sprintf(\"" << escape_string(tstruct_name) << "(%+v)\", *p)" + << endl; + out << indent() << "}" << endl << endl; + + if (is_exception) { + out << indent() << "func (p *" << tstruct_name << ") Error() string {" << endl; + out << indent() << " return p.String()" << endl; + out << indent() << "}" << endl << endl; + } +} + +/** + * Generates the IsSet helper methods for a struct + */ +void t_go_generator::generate_isset_helpers(ostream& out, + t_struct* tstruct, + const string& tstruct_name, + bool is_result) { + (void)is_result; + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + const string escaped_tstruct_name(escape_string(tstruct->get_name())); + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + const string field_name(publicize(escape_string((*f_iter)->get_name()))); + if ((*f_iter)->get_req() == t_field::T_OPTIONAL || is_pointer_field(*f_iter)) { + out << indent() << "func (p *" << tstruct_name << ") IsSet" << field_name << "() bool {" + << endl; + indent_up(); + t_type* ttype = (*f_iter)->get_type()->get_true_type(); + bool is_byteslice = ttype->is_binary(); + bool compare_to_nil_only = ttype->is_set() || ttype->is_list() || ttype->is_map() + || (is_byteslice && !(*f_iter)->get_value()); + if (is_pointer_field(*f_iter) || compare_to_nil_only) { + out << indent() << "return p." << field_name << " != nil" << endl; + } else { + string def_var_name = tstruct_name + "_" + field_name + "_DEFAULT"; + if (is_byteslice) { + out << indent() << "return !bytes.Equal(p." << field_name << ", " << def_var_name << ")" + << endl; + } else { + out << indent() << "return p." << field_name << " != " << def_var_name << endl; + } + } + indent_down(); + out << indent() << "}" << endl << endl; + } + } +} + +/** + * Generates the CountSetFields helper method for a struct + */ +void t_go_generator::generate_countsetfields_helper(ostream& out, + t_struct* tstruct, + const string& tstruct_name, + bool is_result) { + (void)is_result; + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + const string escaped_tstruct_name(escape_string(tstruct->get_name())); + + out << indent() << "func (p *" << tstruct_name << ") CountSetFields" << tstruct_name << "() int {" + << endl; + indent_up(); + out << indent() << "count := 0" << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_REQUIRED) + continue; + + t_type* type = (*f_iter)->get_type()->get_true_type(); + + if (!(is_pointer_field(*f_iter) || type->is_map() || type->is_set() || type->is_list() || type->is_binary())) + continue; + + const string field_name(publicize(escape_string((*f_iter)->get_name()))); + + out << indent() << "if (p.IsSet" << field_name << "()) {" << endl; + indent_up(); + out << indent() << "count++" << endl; + indent_down(); + out << indent() << "}" << endl; + } + + out << indent() << "return count" << endl << endl; + indent_down(); + out << indent() << "}" << endl << endl; +} + +/** + * Generates the read method for a struct + */ +void t_go_generator::generate_go_struct_reader(ostream& out, + t_struct* tstruct, + const string& tstruct_name, + bool is_result) { + (void)is_result; + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + string escaped_tstruct_name(escape_string(tstruct->get_name())); + out << indent() << "func (p *" << tstruct_name << ") " << read_method_name_ << "(iprot thrift.TProtocol) error {" + << endl; + indent_up(); + out << indent() << "if _, err := iprot.ReadStructBegin(); err != nil {" << endl; + out << indent() << " return thrift.PrependError(fmt.Sprintf(\"%T read error: \", p), err)" + << endl; + out << indent() << "}" << endl << endl; + + // Required variables does not have IsSet functions, so we need tmp vars to check them. + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_REQUIRED) { + const string field_name(publicize(escape_string((*f_iter)->get_name()))); + indent(out) << "var isset" << field_name << " bool = false;" << endl; + } + } + out << endl; + + // Loop over reading in fields + indent(out) << "for {" << endl; + indent_up(); + // Read beginning field marker + out << indent() << "_, fieldTypeId, fieldId, err := iprot.ReadFieldBegin()" << endl; + out << indent() << "if err != nil {" << endl; + out << indent() << " return thrift.PrependError(fmt.Sprintf(" + "\"%T field %d read error: \", p, fieldId), err)" << endl; + out << indent() << "}" << endl; + // Check for field STOP marker and break + out << indent() << "if fieldTypeId == thrift.STOP { break; }" << endl; + + string thriftFieldTypeId; + // Generate deserialization code for known cases + int32_t field_id = -1; + + // Switch statement on the field we are reading, false if no fields present + bool have_switch = !fields.empty(); + if (have_switch) { + indent(out) << "switch fieldId {" << endl; + } + + // All the fields we know + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + field_id = (*f_iter)->get_key(); + + // if negative id, ensure we generate a valid method name + string field_method_prefix("ReadField"); + int32_t field_method_suffix = field_id; + + if (field_method_suffix < 0) { + field_method_prefix += "_"; + field_method_suffix *= -1; + } + + out << indent() << "case " << field_id << ":" << endl; + indent_up(); + thriftFieldTypeId = type_to_enum((*f_iter)->get_type()); + + if (thriftFieldTypeId == "thrift.BINARY") { + thriftFieldTypeId = "thrift.STRING"; + } + + out << indent() << "if fieldTypeId == " << thriftFieldTypeId << " {" << endl; + out << indent() << " if err := p." << field_method_prefix << field_method_suffix << "(iprot); err != nil {" + << endl; + out << indent() << " return err" << endl; + out << indent() << " }" << endl; + + // Mark required field as read + if ((*f_iter)->get_req() == t_field::T_REQUIRED) { + const string field_name(publicize(escape_string((*f_iter)->get_name()))); + out << indent() << " isset" << field_name << " = true" << endl; + } + + out << indent() << "} else {" << endl; + out << indent() << " if err := iprot.Skip(fieldTypeId); err != nil {" << endl; + out << indent() << " return err" << endl; + out << indent() << " }" << endl; + out << indent() << "}" << endl; + + + indent_down(); + } + + // Begin switch default case + if (have_switch) { + out << indent() << "default:" << endl; + indent_up(); + } + + // Skip unknown fields in either case + out << indent() << "if err := iprot.Skip(fieldTypeId); err != nil {" << endl; + out << indent() << " return err" << endl; + out << indent() << "}" << endl; + + // End switch default case + if (have_switch) { + indent_down(); + out << indent() << "}" << endl; + } + + // Read field end marker + out << indent() << "if err := iprot.ReadFieldEnd(); err != nil {" << endl; + out << indent() << " return err" << endl; + out << indent() << "}" << endl; + indent_down(); + out << indent() << "}" << endl; + out << indent() << "if err := iprot.ReadStructEnd(); err != nil {" << endl; + out << indent() << " return thrift.PrependError(fmt.Sprintf(" + "\"%T read struct end error: \", p), err)" << endl; + out << indent() << "}" << endl; + + // Return error if any required fields are missing. + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_REQUIRED) { + const string field_name(publicize(escape_string((*f_iter)->get_name()))); + out << indent() << "if !isset" << field_name << "{" << endl; + out << indent() << " return thrift.NewTProtocolExceptionWithType(thrift.INVALID_DATA, " + "fmt.Errorf(\"Required field " << field_name << " is not set\"));" << endl; + out << indent() << "}" << endl; + } + } + + out << indent() << "return nil" << endl; + indent_down(); + out << indent() << "}" << endl << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + string field_type_name(publicize((*f_iter)->get_type()->get_name())); + string field_name(publicize((*f_iter)->get_name())); + string field_method_prefix("ReadField"); + int32_t field_id = (*f_iter)->get_key(); + int32_t field_method_suffix = field_id; + + if (field_method_suffix < 0) { + field_method_prefix += "_"; + field_method_suffix *= -1; + } + + out << indent() << "func (p *" << tstruct_name << ") " << field_method_prefix << field_method_suffix + << "(iprot thrift.TProtocol) error {" << endl; + indent_up(); + generate_deserialize_field(out, *f_iter, false, "p."); + indent_down(); + out << indent() << " return nil" << endl; + out << indent() << "}" << endl << endl; + } +} + +void t_go_generator::generate_go_struct_writer(ostream& out, + t_struct* tstruct, + const string& tstruct_name, + bool is_result, + bool uses_countsetfields) { + (void)is_result; + string name(tstruct->get_name()); + const vector<t_field*>& fields = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator f_iter; + indent(out) << "func (p *" << tstruct_name << ") " << write_method_name_ << "(oprot thrift.TProtocol) error {" << endl; + indent_up(); + if (tstruct->is_union() && uses_countsetfields) { + std::string tstruct_name(publicize(tstruct->get_name())); + out << indent() << "if c := p.CountSetFields" << tstruct_name << "(); c != 1 {" << endl + << indent() + << " return fmt.Errorf(\"%T write union: exactly one field must be set (%d set).\", p, c)" + << endl << indent() << "}" << endl; + } + out << indent() << "if err := oprot.WriteStructBegin(\"" << name << "\"); err != nil {" << endl; + out << indent() << " return thrift.PrependError(fmt.Sprintf(" + "\"%T write struct begin error: \", p), err) }" << endl; + + string field_name; + string escape_field_name; + // t_const_value* field_default_value; + t_field::e_req field_required; + int32_t field_id = -1; + + out << indent() << "if p != nil {" << endl; + indent_up(); + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + string field_method_prefix("writeField"); + field_name = (*f_iter)->get_name(); + escape_field_name = escape_string(field_name); + field_id = (*f_iter)->get_key(); + int32_t field_method_suffix = field_id; + + if (field_method_suffix < 0) { + field_method_prefix += "_"; + field_method_suffix *= -1; + } + + out << indent() << "if err := p." << field_method_prefix << field_method_suffix + << "(oprot); err != nil { return err }" << endl; + } + + indent_down(); + out << indent() << "}" << endl; + + // Write the struct map + out << indent() << "if err := oprot.WriteFieldStop(); err != nil {" << endl; + out << indent() << " return thrift.PrependError(\"write field stop error: \", err) }" << endl; + out << indent() << "if err := oprot.WriteStructEnd(); err != nil {" << endl; + out << indent() << " return thrift.PrependError(\"write struct stop error: \", err) }" << endl; + out << indent() << "return nil" << endl; + indent_down(); + out << indent() << "}" << endl << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + string field_method_prefix("writeField"); + field_id = (*f_iter)->get_key(); + field_name = (*f_iter)->get_name(); + escape_field_name = escape_string(field_name); + // field_default_value = (*f_iter)->get_value(); + field_required = (*f_iter)->get_req(); + int32_t field_method_suffix = field_id; + + if (field_method_suffix < 0) { + field_method_prefix += "_"; + field_method_suffix *= -1; + } + + out << indent() << "func (p *" << tstruct_name << ") " << field_method_prefix << field_method_suffix + << "(oprot thrift.TProtocol) (err error) {" << endl; + indent_up(); + + if (field_required == t_field::T_OPTIONAL) { + out << indent() << "if p.IsSet" << publicize(field_name) << "() {" << endl; + indent_up(); + } + + out << indent() << "if err := oprot.WriteFieldBegin(\"" << escape_field_name << "\", " + << type_to_enum((*f_iter)->get_type()) << ", " << field_id << "); err != nil {" << endl; + out << indent() << " return thrift.PrependError(fmt.Sprintf(\"%T write field begin error " + << field_id << ":" << escape_field_name << ": \", p), err) }" << endl; + + // Write field contents + generate_serialize_field(out, *f_iter, "p."); + + // Write field closer + out << indent() << "if err := oprot.WriteFieldEnd(); err != nil {" << endl; + out << indent() << " return thrift.PrependError(fmt.Sprintf(\"%T write field end error " + << field_id << ":" << escape_field_name << ": \", p), err) }" << endl; + + if (field_required == t_field::T_OPTIONAL) { + indent_down(); + out << indent() << "}" << endl; + } + + indent_down(); + out << indent() << " return err" << endl; + out << indent() << "}" << endl << endl; + } +} + +/** + * Generates a thrift service. + * + * @param tservice The service definition + */ +void t_go_generator::generate_service(t_service* tservice) { + string test_suffix("_test"); + string filename = lowercase(service_name_); + string f_service_name; + + generate_service_interface(tservice); + generate_service_client(tservice); + generate_service_server(tservice); + generate_service_helpers(tservice); + generate_service_remote(tservice); + f_types_ << endl; +} + +/** + * Generates helper functions for a service. + * + * @param tservice The service to generate a header definition for + */ +void t_go_generator::generate_service_helpers(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + f_types_ << "// HELPER FUNCTIONS AND STRUCTURES" << endl << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* ts = (*f_iter)->get_arglist(); + generate_go_struct_definition(f_types_, ts, false, false, true); + generate_go_function_helpers(*f_iter); + } +} + +/** + * Generates a struct and helpers for a function. + * + * @param tfunction The function + */ +void t_go_generator::generate_go_function_helpers(t_function* tfunction) { + if (!tfunction->is_oneway()) { + t_struct result(program_, tfunction->get_name() + "_result"); + t_field success(tfunction->get_returntype(), "success", 0); + success.set_req(t_field::T_OPTIONAL); + + if (!tfunction->get_returntype()->is_void()) { + result.append(&success); + } + + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& fields = xs->get_members(); + vector<t_field*>::const_iterator f_iter; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* f = *f_iter; + f->set_req(t_field::T_OPTIONAL); + result.append(f); + } + + generate_go_struct_definition(f_types_, &result, false, true); + } +} + +/** + * Generates a service interface definition. + * + * @param tservice The service to generate a header definition for + */ +void t_go_generator::generate_service_interface(t_service* tservice) { + string extends = ""; + string extends_if = ""; + string serviceName(publicize(tservice->get_name())); + string interfaceName = serviceName; + + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + size_t index = extends.rfind("."); + + if (index != string::npos) { + extends_if = "\n" + indent() + " " + extends.substr(0, index + 1) + + publicize(extends.substr(index + 1)) + "\n"; + } else { + extends_if = "\n" + indent() + publicize(extends) + "\n"; + } + } + + f_types_ << indent() << "type " << interfaceName << " interface {" << extends_if; + indent_up(); + generate_go_docstring(f_types_, tservice); + vector<t_function*> functions = tservice->get_functions(); + + if (!functions.empty()) { + f_types_ << endl; + vector<t_function*>::iterator f_iter; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_go_docstring(f_types_, (*f_iter)); + f_types_ << indent() << function_signature_if(*f_iter, "", true) << endl; + } + } + + indent_down(); + f_types_ << indent() << "}" << endl << endl; +} + +/** + * Generates a service client definition. + * + * @param tservice The service to generate a server for. + */ +void t_go_generator::generate_service_client(t_service* tservice) { + string extends = ""; + string extends_field = ""; + string extends_client = ""; + string extends_client_new = ""; + string serviceName(publicize(tservice->get_name())); + + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + size_t index = extends.rfind("."); + + if (index != string::npos) { + extends_client = extends.substr(0, index + 1) + publicize(extends.substr(index + 1)) + + "Client"; + extends_client_new = extends.substr(0, index + 1) + "New" + + publicize(extends.substr(index + 1)) + "Client"; + } else { + extends_client = publicize(extends) + "Client"; + extends_client_new = "New" + extends_client; + } + } + + extends_field = extends_client.substr(extends_client.find(".") + 1); + + generate_go_docstring(f_types_, tservice); + f_types_ << indent() << "type " << serviceName << "Client struct {" << endl; + indent_up(); + + if (!extends_client.empty()) { + f_types_ << indent() << "*" << extends_client << endl; + } else { + f_types_ << indent() << "c thrift.TClient" << endl; + } + + indent_down(); + f_types_ << indent() << "}" << endl << endl; + + // Legacy constructor function + f_types_ << indent() << "func New" << serviceName + << "ClientFactory(t thrift.TTransport, f thrift.TProtocolFactory) *" << serviceName + << "Client {" << endl; + indent_up(); + f_types_ << indent() << "return &" << serviceName << "Client"; + + if (!extends.empty()) { + f_types_ << "{" << extends_field << ": " << extends_client_new << "Factory(t, f)}"; + } else { + indent_up(); + f_types_ << "{" << endl; + f_types_ << indent() << "c: thrift.NewTStandardClient(f.GetProtocol(t), f.GetProtocol(t))," << endl; + indent_down(); + f_types_ << indent() << "}" << endl; + } + + indent_down(); + f_types_ << indent() << "}" << endl << endl; + // Legacy constructor function with custom input & output protocols + f_types_ + << indent() << "func New" << serviceName + << "ClientProtocol(t thrift.TTransport, iprot thrift.TProtocol, oprot thrift.TProtocol) *" + << serviceName << "Client {" << endl; + indent_up(); + f_types_ << indent() << "return &" << serviceName << "Client"; + + if (!extends.empty()) { + f_types_ << "{" << extends_field << ": " << extends_client_new << "Protocol(t, iprot, oprot)}" + << endl; + } else { + indent_up(); + f_types_ << "{" << endl; + f_types_ << indent() << "c: thrift.NewTStandardClient(iprot, oprot)," << endl; + indent_down(); + f_types_ << indent() << "}" << endl; + } + + indent_down(); + f_types_ << indent() << "}" << endl << endl; + + // Constructor function + f_types_ << indent() << "func New" << serviceName + << "Client(c thrift.TClient) *" << serviceName << "Client {" << endl; + indent_up(); + f_types_ << indent() << "return &" << serviceName << "Client{" << endl; + + indent_up(); + if (!extends.empty()) { + f_types_ << indent() << extends_field << ": " << extends_client_new << "(c)," << endl; + } else { + f_types_ << indent() << "c: c," << endl; + } + indent_down(); + f_types_ << indent() << "}" << endl; + + indent_down(); + f_types_ << indent() << "}" << endl << endl; + + if (extends.empty()) { + f_types_ << indent() << "func (p *" << serviceName << "Client) Client_() thrift.TClient {" << endl; + indent_up(); + f_types_ << indent() << "return p.c" << endl; + indent_down(); + f_types_ << indent() << "}" << endl; + } + + // Generate client method implementations + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator f_iter; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* arg_struct = (*f_iter)->get_arglist(); + const vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator fld_iter; + string funname = publicize((*f_iter)->get_name()); + // Open function + generate_go_docstring(f_types_, (*f_iter)); + f_types_ << indent() << "func (p *" << serviceName << "Client) " + << function_signature_if(*f_iter, "", true) << " {" << endl; + indent_up(); + + std::string method = (*f_iter)->get_name(); + std::string argsType = publicize(method + "_args", true); + std::string argsName = tmp("_args"); + f_types_ << indent() << "var " << argsName << " " << argsType << endl; + + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + f_types_ << indent() << argsName << "." << publicize((*fld_iter)->get_name()) + << " = " << variable_name_to_go_name((*fld_iter)->get_name()) << endl; + } + + if (!(*f_iter)->is_oneway()) { + std::string resultName = tmp("_result"); + std::string resultType = publicize(method + "_result", true); + f_types_ << indent() << "var " << resultName << " " << resultType << endl; + f_types_ << indent() << "if err = p.Client_().Call(ctx, \"" + << method << "\", &" << argsName << ", &" << resultName << "); err != nil {" << endl; + + indent_up(); + f_types_ << indent() << "return" << endl; + indent_down(); + f_types_ << indent() << "}" << endl; + + t_struct* xs = (*f_iter)->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + + if (!xceptions.empty()) { + f_types_ << indent() << "switch {" << endl; + + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + const std::string pubname = publicize((*x_iter)->get_name()); + const std::string field = resultName + "." + pubname; + + f_types_ << indent() << "case " << field << "!= nil:" << endl; + indent_up(); + + if (!(*f_iter)->get_returntype()->is_void()) { + f_types_ << indent() << "return r, " << field << endl; + } else { + f_types_ << indent() << "return "<< field << endl; + } + + indent_down(); + } + + f_types_ << indent() << "}" << endl << endl; + } + + if (!(*f_iter)->get_returntype()->is_void()) { + f_types_ << indent() << "return " << resultName << ".GetSuccess(), nil" << endl; + } else { + f_types_ << indent() << "return nil" << endl; + } + } else { + // TODO: would be nice to not to duplicate the call generation + f_types_ << indent() << "if err := p.Client_().Call(ctx, \"" + << method << "\", &"<< argsName << ", nil); err != nil {" << endl; + + indent_up(); + f_types_ << indent() << "return err" << endl; + indent_down(); + f_types_ << indent() << "}" << endl; + f_types_ << indent() << "return nil" << endl; + } + + indent_down(); + f_types_ << "}" << endl << endl; + } +} + +/** + * Generates a command line tool for making remote requests + * + * @param tservice The service to generate a remote for. + */ +void t_go_generator::generate_service_remote(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + t_service* parent = tservice->get_extends(); + + // collect inherited functions + while (parent != NULL) { + vector<t_function*> p_functions = parent->get_functions(); + functions.insert(functions.end(), p_functions.begin(), p_functions.end()); + parent = parent->get_extends(); + } + + // This file is not useful if there are no functions; don't generate it + if (functions.size() == 0) { + return; + } + + string f_remote_dir = package_dir_ + "/" + underscore(service_name_) + "-remote"; + MKDIR(f_remote_dir.c_str()); + + vector<t_function*>::iterator f_iter; + string f_remote_name = f_remote_dir + "/" + + underscore(service_name_) + "-remote.go"; + ofstream_with_content_based_conditional_update f_remote; + f_remote.open(f_remote_name.c_str()); + + string unused_protection; + + std::vector<string> system_packages; + system_packages.push_back("context"); + system_packages.push_back("flag"); + system_packages.push_back("fmt"); + system_packages.push_back("math"); + system_packages.push_back("net"); + system_packages.push_back("net/url"); + system_packages.push_back("os"); + system_packages.push_back("strconv"); + system_packages.push_back("strings"); + + f_remote << go_autogen_comment(); + f_remote << indent() << "package main" << endl << endl; + f_remote << indent() << "import (" << endl; + f_remote << render_system_packages(system_packages); + f_remote << indent() << "\t\"" + gen_thrift_import_ + "\"" << endl; + f_remote << indent() << render_included_programs(unused_protection); + f_remote << render_program_import(program_, unused_protection); + f_remote << indent() << ")" << endl; + f_remote << indent() << endl; + f_remote << indent() << unused_protection; // filled in render_included_programs() + f_remote << indent() << endl; + f_remote << indent() << "func Usage() {" << endl; + f_remote << indent() << " fmt.Fprintln(os.Stderr, \"Usage of \", os.Args[0], \" " + "[-h host:port] [-u url] [-f[ramed]] function [arg1 [arg2...]]:\")" + << endl; + f_remote << indent() << " flag.PrintDefaults()" << endl; + f_remote << indent() << " fmt.Fprintln(os.Stderr, \"\\nFunctions:\")" << endl; + + string package_name_aliased = package_identifiers_[get_real_go_module(program_)]; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + f_remote << " fmt.Fprintln(os.Stderr, \" " << (*f_iter)->get_returntype()->get_name() << " " + << (*f_iter)->get_name() << "("; + t_struct* arg_struct = (*f_iter)->get_arglist(); + const std::vector<t_field*>& args = arg_struct->get_members(); + std::vector<t_field*>::size_type num_args = args.size(); + bool first = true; + + for (std::vector<t_field*>::size_type i = 0; i < num_args; ++i) { + if (first) { + first = false; + } else { + f_remote << ", "; + } + + f_remote << args[i]->get_type()->get_name() << " " << args[i]->get_name(); + } + + f_remote << ")\")" << endl; + } + + f_remote << indent() << " fmt.Fprintln(os.Stderr)" << endl; + f_remote << indent() << " os.Exit(0)" << endl; + f_remote << indent() << "}" << endl; + f_remote << indent() << endl; + + f_remote << indent() << "type httpHeaders map[string]string" << endl; + f_remote << indent() << endl; + f_remote << indent() << "func (h httpHeaders) String() string {" << endl; + f_remote << indent() << " var m map[string]string = h" << endl; + f_remote << indent() << " return fmt.Sprintf(\"%s\", m)" << endl; + f_remote << indent() << "}" << endl; + f_remote << indent() << endl; + f_remote << indent() << "func (h httpHeaders) Set(value string) error {" << endl; + f_remote << indent() << " parts := strings.Split(value, \": \")" << endl; + f_remote << indent() << " if len(parts) != 2 {" << endl; + f_remote << indent() << " return fmt.Errorf(\"header should be of format 'Key: Value'\")" << endl; + f_remote << indent() << " }" << endl; + f_remote << indent() << " h[parts[0]] = parts[1]" << endl; + f_remote << indent() << " return nil" << endl; + f_remote << indent() << "}" << endl; + f_remote << indent() << endl; + + f_remote << indent() << "func main() {" << endl; + indent_up(); + f_remote << indent() << "flag.Usage = Usage" << endl; + f_remote << indent() << "var host string" << endl; + f_remote << indent() << "var port int" << endl; + f_remote << indent() << "var protocol string" << endl; + f_remote << indent() << "var urlString string" << endl; + f_remote << indent() << "var framed bool" << endl; + f_remote << indent() << "var useHttp bool" << endl; + f_remote << indent() << "headers := make(httpHeaders)" << endl; + f_remote << indent() << "var parsedUrl *url.URL" << endl; + f_remote << indent() << "var trans thrift.TTransport" << endl; + f_remote << indent() << "_ = strconv.Atoi" << endl; + f_remote << indent() << "_ = math.Abs" << endl; + f_remote << indent() << "flag.Usage = Usage" << endl; + f_remote << indent() << "flag.StringVar(&host, \"h\", \"localhost\", \"Specify host and port\")" + << endl; + f_remote << indent() << "flag.IntVar(&port, \"p\", 9090, \"Specify port\")" << endl; + f_remote << indent() << "flag.StringVar(&protocol, \"P\", \"binary\", \"" + "Specify the protocol (binary, compact, simplejson, json)\")" << endl; + f_remote << indent() << "flag.StringVar(&urlString, \"u\", \"\", \"Specify the url\")" << endl; + f_remote << indent() << "flag.BoolVar(&framed, \"framed\", false, \"Use framed transport\")" + << endl; + f_remote << indent() << "flag.BoolVar(&useHttp, \"http\", false, \"Use http\")" << endl; + f_remote << indent() << "flag.Var(headers, \"H\", \"Headers to set on the http(s) request (e.g. -H \\\"Key: Value\\\")\")" << endl; + f_remote << indent() << "flag.Parse()" << endl; + f_remote << indent() << endl; + f_remote << indent() << "if len(urlString) > 0 {" << endl; + f_remote << indent() << " var err error" << endl; + f_remote << indent() << " parsedUrl, err = url.Parse(urlString)" << endl; + f_remote << indent() << " if err != nil {" << endl; + f_remote << indent() << " fmt.Fprintln(os.Stderr, \"Error parsing URL: \", err)" << endl; + f_remote << indent() << " flag.Usage()" << endl; + f_remote << indent() << " }" << endl; + f_remote << indent() << " host = parsedUrl.Host" << endl; + f_remote << indent() << " useHttp = len(parsedUrl.Scheme) <= 0 || parsedUrl.Scheme == \"http\" || parsedUrl.Scheme == \"https\"" + << endl; + f_remote << indent() << "} else if useHttp {" << endl; + f_remote << indent() << " _, err := url.Parse(fmt.Sprint(\"http://\", host, \":\", port))" + << endl; + f_remote << indent() << " if err != nil {" << endl; + f_remote << indent() << " fmt.Fprintln(os.Stderr, \"Error parsing URL: \", err)" << endl; + f_remote << indent() << " flag.Usage()" << endl; + f_remote << indent() << " }" << endl; + f_remote << indent() << "}" << endl; + f_remote << indent() << endl; + f_remote << indent() << "cmd := flag.Arg(0)" << endl; + f_remote << indent() << "var err error" << endl; + f_remote << indent() << "if useHttp {" << endl; + f_remote << indent() << " trans, err = thrift.NewTHttpClient(parsedUrl.String())" << endl; + f_remote << indent() << " if len(headers) > 0 {" << endl; + f_remote << indent() << " httptrans := trans.(*thrift.THttpClient)" << endl; + f_remote << indent() << " for key, value := range headers {" << endl; + f_remote << indent() << " httptrans.SetHeader(key, value)" << endl; + f_remote << indent() << " }" << endl; + f_remote << indent() << " }" << endl; + f_remote << indent() << "} else {" << endl; + f_remote << indent() << " portStr := fmt.Sprint(port)" << endl; + f_remote << indent() << " if strings.Contains(host, \":\") {" << endl; + f_remote << indent() << " host, portStr, err = net.SplitHostPort(host)" << endl; + f_remote << indent() << " if err != nil {" << endl; + f_remote << indent() << " fmt.Fprintln(os.Stderr, \"error with host:\", err)" + << endl; + f_remote << indent() << " os.Exit(1)" << endl; + f_remote << indent() << " }" << endl; + f_remote << indent() << " }" << endl; + f_remote << indent() << " trans, err = thrift.NewTSocket(net.JoinHostPort(host, portStr))" + << endl; + f_remote << indent() << " if err != nil {" << endl; + f_remote << indent() << " fmt.Fprintln(os.Stderr, \"error resolving address:\", err)" << endl; + f_remote << indent() << " os.Exit(1)" << endl; + f_remote << indent() << " }" << endl; + f_remote << indent() << " if framed {" << endl; + f_remote << indent() << " trans = thrift.NewTFramedTransport(trans)" << endl; + f_remote << indent() << " }" << endl; + f_remote << indent() << "}" << endl; + f_remote << indent() << "if err != nil {" << endl; + f_remote << indent() << " fmt.Fprintln(os.Stderr, \"Error creating transport\", err)" << endl; + f_remote << indent() << " os.Exit(1)" << endl; + f_remote << indent() << "}" << endl; + f_remote << indent() << "defer trans.Close()" << endl; + f_remote << indent() << "var protocolFactory thrift.TProtocolFactory" << endl; + f_remote << indent() << "switch protocol {" << endl; + f_remote << indent() << "case \"compact\":" << endl; + f_remote << indent() << " protocolFactory = thrift.NewTCompactProtocolFactory()" << endl; + f_remote << indent() << " break" << endl; + f_remote << indent() << "case \"simplejson\":" << endl; + f_remote << indent() << " protocolFactory = thrift.NewTSimpleJSONProtocolFactory()" << endl; + f_remote << indent() << " break" << endl; + f_remote << indent() << "case \"json\":" << endl; + f_remote << indent() << " protocolFactory = thrift.NewTJSONProtocolFactory()" << endl; + f_remote << indent() << " break" << endl; + f_remote << indent() << "case \"binary\", \"\":" << endl; + f_remote << indent() << " protocolFactory = thrift.NewTBinaryProtocolFactoryDefault()" << endl; + f_remote << indent() << " break" << endl; + f_remote << indent() << "default:" << endl; + f_remote << indent() << " fmt.Fprintln(os.Stderr, \"Invalid protocol specified: \", protocol)" + << endl; + f_remote << indent() << " Usage()" << endl; + f_remote << indent() << " os.Exit(1)" << endl; + f_remote << indent() << "}" << endl; + f_remote << indent() << "iprot := protocolFactory.GetProtocol(trans)" << endl; + f_remote << indent() << "oprot := protocolFactory.GetProtocol(trans)" << endl; + f_remote << indent() << "client := " << package_name_aliased << ".New" << publicize(service_name_) + << "Client(thrift.NewTStandardClient(iprot, oprot))" << endl; + f_remote << indent() << "if err := trans.Open(); err != nil {" << endl; + f_remote << indent() << " fmt.Fprintln(os.Stderr, \"Error opening socket to \", " + "host, \":\", port, \" \", err)" << endl; + f_remote << indent() << " os.Exit(1)" << endl; + f_remote << indent() << "}" << endl; + f_remote << indent() << endl; + f_remote << indent() << "switch cmd {" << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* arg_struct = (*f_iter)->get_arglist(); + const std::vector<t_field*>& args = arg_struct->get_members(); + std::vector<t_field*>::size_type num_args = args.size(); + string funcName((*f_iter)->get_name()); + string pubName(publicize(funcName)); + string argumentsName(publicize(funcName + "_args", true)); + f_remote << indent() << "case \"" << escape_string(funcName) << "\":" << endl; + indent_up(); + f_remote << indent() << "if flag.NArg() - 1 != " << num_args << " {" << endl; + f_remote << indent() << " fmt.Fprintln(os.Stderr, \"" << escape_string(pubName) << " requires " + << num_args << " args\")" << endl; + f_remote << indent() << " flag.Usage()" << endl; + f_remote << indent() << "}" << endl; + + for (std::vector<t_field*>::size_type i = 0; i < num_args; ++i) { + std::vector<t_field*>::size_type flagArg = i + 1; + t_type* the_type(args[i]->get_type()); + t_type* the_type2(get_true_type(the_type)); + + if (the_type2->is_enum()) { + f_remote << indent() << "tmp" << i << ", err := (strconv.Atoi(flag.Arg(" << flagArg << ")))" + << endl; + f_remote << indent() << "if err != nil {" << endl; + f_remote << indent() << " Usage()" << endl; + f_remote << indent() << " return" << endl; + f_remote << indent() << "}" << endl; + f_remote << indent() << "argvalue" << i << " := " << package_name_aliased << "." + << publicize(the_type->get_name()) << "(tmp" << i << ")" << endl; + } else if (the_type2->is_base_type()) { + t_base_type::t_base e = ((t_base_type*)the_type2)->get_base(); + string err(tmp("err")); + + switch (e) { + case t_base_type::TYPE_VOID: + break; + + case t_base_type::TYPE_STRING: + if (the_type2->is_binary()) { + f_remote << indent() << "argvalue" << i << " := []byte(flag.Arg(" << flagArg << "))" + << endl; + } else { + f_remote << indent() << "argvalue" << i << " := flag.Arg(" << flagArg << ")" << endl; + } + break; + + case t_base_type::TYPE_BOOL: + f_remote << indent() << "argvalue" << i << " := flag.Arg(" << flagArg << ") == \"true\"" + << endl; + break; + + case t_base_type::TYPE_I8: + f_remote << indent() << "tmp" << i << ", " << err << " := (strconv.Atoi(flag.Arg(" + << flagArg << ")))" << endl; + f_remote << indent() << "if " << err << " != nil {" << endl; + f_remote << indent() << " Usage()" << endl; + f_remote << indent() << " return" << endl; + f_remote << indent() << "}" << endl; + f_remote << indent() << "argvalue" << i << " := int8(tmp" << i << ")" << endl; + break; + + case t_base_type::TYPE_I16: + f_remote << indent() << "tmp" << i << ", " << err << " := (strconv.Atoi(flag.Arg(" + << flagArg << ")))" << endl; + f_remote << indent() << "if " << err << " != nil {" << endl; + f_remote << indent() << " Usage()" << endl; + f_remote << indent() << " return" << endl; + f_remote << indent() << "}" << endl; + f_remote << indent() << "argvalue" << i << " := int16(tmp" << i << ")" << endl; + break; + + case t_base_type::TYPE_I32: + f_remote << indent() << "tmp" << i << ", " << err << " := (strconv.Atoi(flag.Arg(" + << flagArg << ")))" << endl; + f_remote << indent() << "if " << err << " != nil {" << endl; + f_remote << indent() << " Usage()" << endl; + f_remote << indent() << " return" << endl; + f_remote << indent() << "}" << endl; + f_remote << indent() << "argvalue" << i << " := int32(tmp" << i << ")" << endl; + break; + + case t_base_type::TYPE_I64: + f_remote << indent() << "argvalue" << i << ", " << err + << " := (strconv.ParseInt(flag.Arg(" << flagArg << "), 10, 64))" << endl; + f_remote << indent() << "if " << err << " != nil {" << endl; + f_remote << indent() << " Usage()" << endl; + f_remote << indent() << " return" << endl; + f_remote << indent() << "}" << endl; + break; + + case t_base_type::TYPE_DOUBLE: + f_remote << indent() << "argvalue" << i << ", " << err + << " := (strconv.ParseFloat(flag.Arg(" << flagArg << "), 64))" << endl; + f_remote << indent() << "if " << err << " != nil {" << endl; + f_remote << indent() << " Usage()" << endl; + f_remote << indent() << " return" << endl; + f_remote << indent() << "}" << endl; + break; + + default: + throw("Invalid base type in generate_service_remote"); + } + + // f_remote << publicize(args[i]->get_name()) << "(strconv.Atoi(flag.Arg(" << flagArg << + // ")))"; + } else if (the_type2->is_struct()) { + string arg(tmp("arg")); + string mbTrans(tmp("mbTrans")); + string err1(tmp("err")); + string factory(tmp("factory")); + string jsProt(tmp("jsProt")); + string err2(tmp("err")); + std::string tstruct_name(publicize(the_type->get_name())); + std::string tstruct_module( module_name(the_type)); + if(tstruct_module.empty()) { + tstruct_module = package_name_aliased; + } + + f_remote << indent() << arg << " := flag.Arg(" << flagArg << ")" << endl; + f_remote << indent() << mbTrans << " := thrift.NewTMemoryBufferLen(len(" << arg << "))" + << endl; + f_remote << indent() << "defer " << mbTrans << ".Close()" << endl; + f_remote << indent() << "_, " << err1 << " := " << mbTrans << ".WriteString(" << arg << ")" + << endl; + f_remote << indent() << "if " << err1 << " != nil {" << endl; + f_remote << indent() << " Usage()" << endl; + f_remote << indent() << " return" << endl; + f_remote << indent() << "}" << endl; + f_remote << indent() << factory << " := thrift.NewTJSONProtocolFactory()" << endl; + f_remote << indent() << jsProt << " := " << factory << ".GetProtocol(" << mbTrans << ")" + << endl; + f_remote << indent() << "argvalue" << i << " := " << tstruct_module << ".New" << tstruct_name + << "()" << endl; + f_remote << indent() << err2 << " := argvalue" << i << "." << read_method_name_ << "(" << jsProt << ")" << endl; + f_remote << indent() << "if " << err2 << " != nil {" << endl; + f_remote << indent() << " Usage()" << endl; + f_remote << indent() << " return" << endl; + f_remote << indent() << "}" << endl; + } else if (the_type2->is_container() || the_type2->is_xception()) { + string arg(tmp("arg")); + string mbTrans(tmp("mbTrans")); + string err1(tmp("err")); + string factory(tmp("factory")); + string jsProt(tmp("jsProt")); + string err2(tmp("err")); + std::string argName(publicize(args[i]->get_name())); + f_remote << indent() << arg << " := flag.Arg(" << flagArg << ")" << endl; + f_remote << indent() << mbTrans << " := thrift.NewTMemoryBufferLen(len(" << arg << "))" + << endl; + f_remote << indent() << "defer " << mbTrans << ".Close()" << endl; + f_remote << indent() << "_, " << err1 << " := " << mbTrans << ".WriteString(" << arg << ")" + << endl; + f_remote << indent() << "if " << err1 << " != nil { " << endl; + f_remote << indent() << " Usage()" << endl; + f_remote << indent() << " return" << endl; + f_remote << indent() << "}" << endl; + f_remote << indent() << factory << " := thrift.NewTJSONProtocolFactory()" << endl; + f_remote << indent() << jsProt << " := " << factory << ".GetProtocol(" << mbTrans << ")" + << endl; + f_remote << indent() << "containerStruct" << i << " := " << package_name_aliased << ".New" + << argumentsName << "()" << endl; + f_remote << indent() << err2 << " := containerStruct" << i << ".ReadField" << (i + 1) << "(" + << jsProt << ")" << endl; + f_remote << indent() << "if " << err2 << " != nil {" << endl; + f_remote << indent() << " Usage()" << endl; + f_remote << indent() << " return" << endl; + f_remote << indent() << "}" << endl; + f_remote << indent() << "argvalue" << i << " := containerStruct" << i << "." << argName + << endl; + } else { + throw("Invalid argument type in generate_service_remote"); + } + + if (the_type->is_typedef()) { + std::string typedef_module(module_name(the_type)); + if(typedef_module.empty()) { + typedef_module = package_name_aliased; + } + f_remote << indent() << "value" << i << " := " << typedef_module << "." + << publicize(the_type->get_name()) << "(argvalue" << i << ")" << endl; + } else { + f_remote << indent() << "value" << i << " := argvalue" << i << endl; + } + } + + f_remote << indent() << "fmt.Print(client." << pubName << "("; + bool argFirst = true; + + f_remote << "context.Background()"; + for (std::vector<t_field*>::size_type i = 0; i < num_args; ++i) { + if (argFirst) { + argFirst = false; + f_remote << ", "; + } else { + f_remote << ", "; + } + + if (args[i]->get_type()->is_enum()) { + f_remote << "value" << i; + } else if (args[i]->get_type()->is_base_type()) { + t_base_type::t_base e = ((t_base_type*)(args[i]->get_type()))->get_base(); + + switch (e) { + case t_base_type::TYPE_VOID: + break; + + case t_base_type::TYPE_STRING: + case t_base_type::TYPE_BOOL: + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + case t_base_type::TYPE_DOUBLE: + f_remote << "value" << i; + break; + + default: + throw("Invalid base type in generate_service_remote"); + } + + // f_remote << publicize(args[i]->get_name()) << "(strconv.Atoi(flag.Arg(" << flagArg << + // ")))"; + } else { + f_remote << "value" << i; + } + } + + f_remote << "))" << endl; + f_remote << indent() << "fmt.Print(\"\\n\")" << endl; + f_remote << indent() << "break" << endl; + indent_down(); + } + + f_remote << indent() << "case \"\":" << endl; + f_remote << indent() << " Usage()" << endl; + f_remote << indent() << " break" << endl; + f_remote << indent() << "default:" << endl; + f_remote << indent() << " fmt.Fprintln(os.Stderr, \"Invalid function \", cmd)" << endl; + f_remote << indent() << "}" << endl; + indent_down(); + f_remote << indent() << "}" << endl; + // Close service file + f_remote.close(); + format_go_output(f_remote_name); +#ifndef _MSC_VER + // Make file executable, love that bitwise OR action + chmod(f_remote_name.c_str(), + S_IRUSR | S_IWUSR | S_IXUSR +#ifndef _WIN32 + | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH +#endif + ); +#endif +} + +/** + * Generates a service server definition. + * + * @param tservice The service to generate a server for. + */ +void t_go_generator::generate_service_server(t_service* tservice) { + // Generate the dispatch methods + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + string extends = ""; + string extends_processor = ""; + string extends_processor_new = ""; + string serviceName(publicize(tservice->get_name())); + + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + size_t index = extends.rfind("."); + + if (index != string::npos) { + extends_processor = extends.substr(0, index + 1) + publicize(extends.substr(index + 1)) + + "Processor"; + extends_processor_new = extends.substr(0, index + 1) + "New" + + publicize(extends.substr(index + 1)) + "Processor"; + } else { + extends_processor = publicize(extends) + "Processor"; + extends_processor_new = "New" + extends_processor; + } + } + + string pServiceName(privatize(tservice->get_name())); + // Generate the header portion + string self(tmp("self")); + + if (extends_processor.empty()) { + f_types_ << indent() << "type " << serviceName << "Processor struct {" << endl; + f_types_ << indent() << " processorMap map[string]thrift.TProcessorFunction" << endl; + f_types_ << indent() << " handler " << serviceName << endl; + f_types_ << indent() << "}" << endl << endl; + f_types_ << indent() << "func (p *" << serviceName + << "Processor) AddToProcessorMap(key string, processor thrift.TProcessorFunction) {" + << endl; + f_types_ << indent() << " p.processorMap[key] = processor" << endl; + f_types_ << indent() << "}" << endl << endl; + f_types_ << indent() << "func (p *" << serviceName + << "Processor) GetProcessorFunction(key string) " + "(processor thrift.TProcessorFunction, ok bool) {" << endl; + f_types_ << indent() << " processor, ok = p.processorMap[key]" << endl; + f_types_ << indent() << " return processor, ok" << endl; + f_types_ << indent() << "}" << endl << endl; + f_types_ << indent() << "func (p *" << serviceName + << "Processor) ProcessorMap() map[string]thrift.TProcessorFunction {" << endl; + f_types_ << indent() << " return p.processorMap" << endl; + f_types_ << indent() << "}" << endl << endl; + f_types_ << indent() << "func New" << serviceName << "Processor(handler " << serviceName + << ") *" << serviceName << "Processor {" << endl << endl; + f_types_ + << indent() << " " << self << " := &" << serviceName + << "Processor{handler:handler, processorMap:make(map[string]thrift.TProcessorFunction)}" + << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + string escapedFuncName(escape_string((*f_iter)->get_name())); + f_types_ << indent() << " " << self << ".processorMap[\"" << escapedFuncName << "\"] = &" + << pServiceName << "Processor" << publicize((*f_iter)->get_name()) + << "{handler:handler}" << endl; + } + + string x(tmp("x")); + f_types_ << indent() << "return " << self << endl; + f_types_ << indent() << "}" << endl << endl; + f_types_ << indent() << "func (p *" << serviceName + << "Processor) Process(ctx context.Context, iprot, oprot thrift.TProtocol) (success bool, err " + "thrift.TException) {" << endl; + f_types_ << indent() << " name, _, seqId, err := iprot.ReadMessageBegin()" << endl; + f_types_ << indent() << " if err != nil { return false, err }" << endl; + f_types_ << indent() << " if processor, ok := p.GetProcessorFunction(name); ok {" << endl; + f_types_ << indent() << " return processor.Process(ctx, seqId, iprot, oprot)" << endl; + f_types_ << indent() << " }" << endl; + f_types_ << indent() << " iprot.Skip(thrift.STRUCT)" << endl; + f_types_ << indent() << " iprot.ReadMessageEnd()" << endl; + f_types_ << indent() << " " << x + << " := thrift.NewTApplicationException(thrift.UNKNOWN_METHOD, \"Unknown function " + "\" + name)" << endl; + f_types_ << indent() << " oprot.WriteMessageBegin(name, thrift.EXCEPTION, seqId)" << endl; + f_types_ << indent() << " " << x << ".Write(oprot)" << endl; + f_types_ << indent() << " oprot.WriteMessageEnd()" << endl; + f_types_ << indent() << " oprot.Flush(ctx)" << endl; + f_types_ << indent() << " return false, " << x << endl; + f_types_ << indent() << "" << endl; + f_types_ << indent() << "}" << endl << endl; + } else { + f_types_ << indent() << "type " << serviceName << "Processor struct {" << endl; + f_types_ << indent() << " *" << extends_processor << endl; + f_types_ << indent() << "}" << endl << endl; + f_types_ << indent() << "func New" << serviceName << "Processor(handler " << serviceName + << ") *" << serviceName << "Processor {" << endl; + f_types_ << indent() << " " << self << " := &" << serviceName << "Processor{" + << extends_processor_new << "(handler)}" << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + string escapedFuncName(escape_string((*f_iter)->get_name())); + f_types_ << indent() << " " << self << ".AddToProcessorMap(\"" << escapedFuncName + << "\", &" << pServiceName << "Processor" << publicize((*f_iter)->get_name()) + << "{handler:handler})" << endl; + } + + f_types_ << indent() << " return " << self << endl; + f_types_ << indent() << "}" << endl << endl; + } + + // Generate the process subfunctions + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_process_function(tservice, *f_iter); + } + + f_types_ << endl; +} + +/** + * Generates a process function definition. + * + * @param tfunction The function to write a dispatcher for + */ +void t_go_generator::generate_process_function(t_service* tservice, t_function* tfunction) { + // Open function + string processorName = privatize(tservice->get_name()) + "Processor" + + publicize(tfunction->get_name()); + string argsname = publicize(tfunction->get_name() + "_args", true); + string resultname = publicize(tfunction->get_name() + "_result", true); + + // t_struct* xs = tfunction->get_xceptions(); + // const std::vector<t_field*>& xceptions = xs->get_members(); + f_types_ << indent() << "type " << processorName << " struct {" << endl; + f_types_ << indent() << " handler " << publicize(tservice->get_name()) << endl; + f_types_ << indent() << "}" << endl << endl; + f_types_ << indent() << "func (p *" << processorName + << ") Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err " + "thrift.TException) {" << endl; + indent_up(); + f_types_ << indent() << "args := " << argsname << "{}" << endl; + f_types_ << indent() << "if err = args." << read_method_name_ << "(iprot); err != nil {" << endl; + f_types_ << indent() << " iprot.ReadMessageEnd()" << endl; + if (!tfunction->is_oneway()) { + f_types_ << indent() + << " x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error())" + << endl; + f_types_ << indent() << " oprot.WriteMessageBegin(\"" << escape_string(tfunction->get_name()) + << "\", thrift.EXCEPTION, seqId)" << endl; + f_types_ << indent() << " x.Write(oprot)" << endl; + f_types_ << indent() << " oprot.WriteMessageEnd()" << endl; + f_types_ << indent() << " oprot.Flush(ctx)" << endl; + } + f_types_ << indent() << " return false, err" << endl; + f_types_ << indent() << "}" << endl << endl; + f_types_ << indent() << "iprot.ReadMessageEnd()" << endl; + + if (!tfunction->is_oneway()) { + f_types_ << indent() << "result := " << resultname << "{}" << endl; + } + bool need_reference = type_need_reference(tfunction->get_returntype()); + if (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void()) { + f_types_ << "var retval " << type_to_go_type(tfunction->get_returntype()) << endl; + } + + f_types_ << indent() << "var err2 error" << endl; + f_types_ << indent() << "if "; + + if (!tfunction->is_oneway()) { + if (!tfunction->get_returntype()->is_void()) { + f_types_ << "retval, "; + } + } + + // Generate the function call + t_struct* arg_struct = tfunction->get_arglist(); + const std::vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + f_types_ << "err2 = p.handler." << publicize(tfunction->get_name()) << "("; + bool first = true; + + f_types_ << "ctx"; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + f_types_ << ", "; + } else { + f_types_ << ", "; + } + + f_types_ << "args." << publicize((*f_iter)->get_name()); + } + + f_types_ << "); err2 != nil {" << endl; + + t_struct* exceptions = tfunction->get_xceptions(); + const vector<t_field*>& x_fields = exceptions->get_members(); + if (!x_fields.empty()) { + f_types_ << indent() << "switch v := err2.(type) {" << endl; + + vector<t_field*>::const_iterator xf_iter; + + for (xf_iter = x_fields.begin(); xf_iter != x_fields.end(); ++xf_iter) { + f_types_ << indent() << " case " << type_to_go_type(((*xf_iter)->get_type())) << ":" + << endl; + f_types_ << indent() << "result." << publicize((*xf_iter)->get_name()) << " = v" << endl; + } + + f_types_ << indent() << " default:" << endl; + } + + if (!tfunction->is_oneway()) { + f_types_ << indent() << " x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, " + "\"Internal error processing " << escape_string(tfunction->get_name()) + << ": \" + err2.Error())" << endl; + f_types_ << indent() << " oprot.WriteMessageBegin(\"" << escape_string(tfunction->get_name()) + << "\", thrift.EXCEPTION, seqId)" << endl; + f_types_ << indent() << " x.Write(oprot)" << endl; + f_types_ << indent() << " oprot.WriteMessageEnd()" << endl; + f_types_ << indent() << " oprot.Flush(ctx)" << endl; + } + + f_types_ << indent() << " return true, err2" << endl; + + if (!x_fields.empty()) { + f_types_ << indent() << "}" << endl; + } + + f_types_ << indent() << "}"; // closes err2 != nil + + if (!tfunction->is_oneway()) { + if (!tfunction->get_returntype()->is_void()) { + f_types_ << " else {" << endl; // make sure we set Success retval only on success + indent_up(); + f_types_ << indent() << "result.Success = "; + if (need_reference) { + f_types_ << "&"; + } + f_types_ << "retval" << endl; + indent_down(); + f_types_ << "}" << endl; + } else { + f_types_ << endl; + } + f_types_ << indent() << "if err2 = oprot.WriteMessageBegin(\"" + << escape_string(tfunction->get_name()) << "\", thrift.REPLY, seqId); err2 != nil {" + << endl; + f_types_ << indent() << " err = err2" << endl; + f_types_ << indent() << "}" << endl; + f_types_ << indent() << "if err2 = result." << write_method_name_ << "(oprot); err == nil && err2 != nil {" << endl; + f_types_ << indent() << " err = err2" << endl; + f_types_ << indent() << "}" << endl; + f_types_ << indent() << "if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil {" + << endl; + f_types_ << indent() << " err = err2" << endl; + f_types_ << indent() << "}" << endl; + f_types_ << indent() << "if err2 = oprot.Flush(ctx); err == nil && err2 != nil {" << endl; + f_types_ << indent() << " err = err2" << endl; + f_types_ << indent() << "}" << endl; + f_types_ << indent() << "if err != nil {" << endl; + f_types_ << indent() << " return" << endl; + f_types_ << indent() << "}" << endl; + f_types_ << indent() << "return true, err" << endl; + } else { + f_types_ << endl; + f_types_ << indent() << "return true, nil" << endl; + } + indent_down(); + f_types_ << indent() << "}" << endl << endl; +} + +/** + * Deserializes a field of any type. + */ +void t_go_generator::generate_deserialize_field(ostream& out, + t_field* tfield, + bool declare, + string prefix, + bool inclass, + bool coerceData, + bool inkey, + bool in_container_value) { + (void)inclass; + (void)coerceData; + t_type* orig_type = tfield->get_type(); + t_type* type = get_true_type(orig_type); + string name(prefix + publicize(tfield->get_name())); + + if (type->is_void()) { + throw "CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " + name; + } + + if (type->is_struct() || type->is_xception()) { + generate_deserialize_struct(out, + (t_struct*)type, + is_pointer_field(tfield, in_container_value), + declare, + name); + } else if (type->is_container()) { + generate_deserialize_container(out, orig_type, is_pointer_field(tfield), declare, name); + } else if (type->is_base_type() || type->is_enum()) { + + if (declare) { + string type_name = inkey ? type_to_go_key_type(tfield->get_type()) + : type_to_go_type(tfield->get_type()); + + out << "var " << tfield->get_name() << " " << type_name << endl; + } + + indent(out) << "if v, err := iprot."; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + + case t_base_type::TYPE_STRING: + if (type->is_binary() && !inkey) { + out << "ReadBinary()"; + } else { + out << "ReadString()"; + } + + break; + + case t_base_type::TYPE_BOOL: + out << "ReadBool()"; + break; + + case t_base_type::TYPE_I8: + out << "ReadByte()"; + break; + + case t_base_type::TYPE_I16: + out << "ReadI16()"; + break; + + case t_base_type::TYPE_I32: + out << "ReadI32()"; + break; + + case t_base_type::TYPE_I64: + out << "ReadI64()"; + break; + + case t_base_type::TYPE_DOUBLE: + out << "ReadDouble()"; + break; + + default: + throw "compiler error: no Go name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "ReadI32()"; + } + + out << "; err != nil {" << endl; + out << indent() << "return thrift.PrependError(\"error reading field " << tfield->get_key() + << ": \", err)" << endl; + + out << "} else {" << endl; + string wrap; + + if (type->is_enum() || orig_type->is_typedef()) { + wrap = publicize(type_name(orig_type)); + } else if (((t_base_type*)type)->get_base() == t_base_type::TYPE_I8) { + wrap = "int8"; + } + + string maybe_address = (is_pointer_field(tfield) ? "&" : ""); + if (wrap == "") { + indent(out) << name << " = " << maybe_address << "v" << endl; + } else { + indent(out) << "temp := " << wrap << "(v)" << endl; + indent(out) << name << " = " << maybe_address << "temp" << endl; + } + + out << "}" << endl; + } else { + throw "INVALID TYPE IN generate_deserialize_field '" + type->get_name() + "' for field '" + + tfield->get_name() + "'"; + } +} + +/** + * Generates an unserializer for a struct, calling read() + */ +void t_go_generator::generate_deserialize_struct(ostream& out, + t_struct* tstruct, + bool pointer_field, + bool declare, + string prefix) { + string eq(declare ? " := " : " = "); + + out << indent() << prefix << eq << (pointer_field ? "&" : ""); + generate_go_struct_initializer(out, tstruct); + out << indent() << "if err := " << prefix << "." << read_method_name_ << "(iprot); err != nil {" << endl; + out << indent() << " return thrift.PrependError(fmt.Sprintf(\"%T error reading struct: \", " + << prefix << "), err)" << endl; + out << indent() << "}" << endl; +} + +/** + * Serialize a container by writing out the header followed by + * data and then a footer. + */ +void t_go_generator::generate_deserialize_container(ostream& out, + t_type* orig_type, + bool pointer_field, + bool declare, + string prefix) { + t_type* ttype = get_true_type(orig_type); + string eq(" = "); + + if (declare) { + eq = " := "; + } + + // Declare variables, read header + if (ttype->is_map()) { + out << indent() << "_, _, size, err := iprot.ReadMapBegin()" << endl; + out << indent() << "if err != nil {" << endl; + out << indent() << " return thrift.PrependError(\"error reading map begin: \", err)" << endl; + out << indent() << "}" << endl; + out << indent() << "tMap := make(" << type_to_go_type(orig_type) << ", size)" << endl; + out << indent() << prefix << eq << " " << (pointer_field ? "&" : "") << "tMap" << endl; + } else if (ttype->is_set()) { + out << indent() << "_, size, err := iprot.ReadSetBegin()" << endl; + out << indent() << "if err != nil {" << endl; + out << indent() << " return thrift.PrependError(\"error reading set begin: \", err)" << endl; + out << indent() << "}" << endl; + out << indent() << "tSet := make(" << type_to_go_type(orig_type) << ", 0, size)" << endl; + out << indent() << prefix << eq << " " << (pointer_field ? "&" : "") << "tSet" << endl; + } else if (ttype->is_list()) { + out << indent() << "_, size, err := iprot.ReadListBegin()" << endl; + out << indent() << "if err != nil {" << endl; + out << indent() << " return thrift.PrependError(\"error reading list begin: \", err)" << endl; + out << indent() << "}" << endl; + out << indent() << "tSlice := make(" << type_to_go_type(orig_type) << ", 0, size)" << endl; + out << indent() << prefix << eq << " " << (pointer_field ? "&" : "") << "tSlice" << endl; + } else { + throw "INVALID TYPE IN generate_deserialize_container '" + ttype->get_name() + "' for prefix '" + + prefix + "'"; + } + + // For loop iterates over elements + out << indent() << "for i := 0; i < size; i ++ {" << endl; + indent_up(); + + if (pointer_field) { + prefix = "(*" + prefix + ")"; + } + if (ttype->is_map()) { + generate_deserialize_map_element(out, (t_map*)ttype, declare, prefix); + } else if (ttype->is_set()) { + generate_deserialize_set_element(out, (t_set*)ttype, declare, prefix); + } else if (ttype->is_list()) { + generate_deserialize_list_element(out, (t_list*)ttype, declare, prefix); + } + + indent_down(); + out << indent() << "}" << endl; + + // Read container end + if (ttype->is_map()) { + out << indent() << "if err := iprot.ReadMapEnd(); err != nil {" << endl; + out << indent() << " return thrift.PrependError(\"error reading map end: \", err)" << endl; + out << indent() << "}" << endl; + } else if (ttype->is_set()) { + out << indent() << "if err := iprot.ReadSetEnd(); err != nil {" << endl; + out << indent() << " return thrift.PrependError(\"error reading set end: \", err)" << endl; + out << indent() << "}" << endl; + } else if (ttype->is_list()) { + out << indent() << "if err := iprot.ReadListEnd(); err != nil {" << endl; + out << indent() << " return thrift.PrependError(\"error reading list end: \", err)" << endl; + out << indent() << "}" << endl; + } +} + +/** + * Generates code to deserialize a map + */ +void t_go_generator::generate_deserialize_map_element(ostream& out, + t_map* tmap, + bool declare, + string prefix) { + (void)declare; + string key = tmp("_key"); + string val = tmp("_val"); + t_field fkey(tmap->get_key_type(), key); + t_field fval(tmap->get_val_type(), val); + fkey.set_req(t_field::T_OPT_IN_REQ_OUT); + fval.set_req(t_field::T_OPT_IN_REQ_OUT); + generate_deserialize_field(out, &fkey, true, "", false, false, true); + generate_deserialize_field(out, &fval, true, "", false, false, false, true); + indent(out) << prefix << "[" << key << "] = " << val << endl; +} + +/** + * Write a set element + */ +void t_go_generator::generate_deserialize_set_element(ostream& out, + t_set* tset, + bool declare, + string prefix) { + (void)declare; + string elem = tmp("_elem"); + t_field felem(tset->get_elem_type(), elem); + felem.set_req(t_field::T_OPT_IN_REQ_OUT); + generate_deserialize_field(out, &felem, true, "", false, false, false, true); + indent(out) << prefix << " = append(" << prefix << ", " << elem << ")" << endl; +} + +/** + * Write a list element + */ +void t_go_generator::generate_deserialize_list_element(ostream& out, + t_list* tlist, + bool declare, + string prefix) { + (void)declare; + string elem = tmp("_elem"); + t_field felem(((t_list*)tlist)->get_elem_type(), elem); + felem.set_req(t_field::T_OPT_IN_REQ_OUT); + generate_deserialize_field(out, &felem, true, "", false, false, false, true); + indent(out) << prefix << " = append(" << prefix << ", " << elem << ")" << endl; +} + +/** + * Serializes a field of any type. + * + * @param tfield The field to serialize + * @param prefix Name to prepend to field name + */ +void t_go_generator::generate_serialize_field(ostream& out, + t_field* tfield, + string prefix, + bool inkey) { + t_type* type = get_true_type(tfield->get_type()); + string name(prefix + publicize(tfield->get_name())); + + // Do nothing for void types + if (type->is_void()) { + throw "compiler error: cannot generate serialize for void type: " + name; + } + + if (type->is_struct() || type->is_xception()) { + generate_serialize_struct(out, (t_struct*)type, name); + } else if (type->is_container()) { + generate_serialize_container(out, type, is_pointer_field(tfield), name); + } else if (type->is_base_type() || type->is_enum()) { + indent(out) << "if err := oprot."; + + if (is_pointer_field(tfield)) { + name = "*" + name; + } + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + + case t_base_type::TYPE_STRING: + if (type->is_binary() && !inkey) { + out << "WriteBinary(" << name << ")"; + } else { + out << "WriteString(string(" << name << "))"; + } + + break; + + case t_base_type::TYPE_BOOL: + out << "WriteBool(bool(" << name << "))"; + break; + + case t_base_type::TYPE_I8: + out << "WriteByte(int8(" << name << "))"; + break; + + case t_base_type::TYPE_I16: + out << "WriteI16(int16(" << name << "))"; + break; + + case t_base_type::TYPE_I32: + out << "WriteI32(int32(" << name << "))"; + break; + + case t_base_type::TYPE_I64: + out << "WriteI64(int64(" << name << "))"; + break; + + case t_base_type::TYPE_DOUBLE: + out << "WriteDouble(float64(" << name << "))"; + break; + + default: + throw "compiler error: no Go name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "WriteI32(int32(" << name << "))"; + } + + out << "; err != nil {" << endl; + out << indent() << "return thrift.PrependError(fmt.Sprintf(\"%T." + << escape_string(tfield->get_name()) << " (" << tfield->get_key() + << ") field write error: \", p), err) }" << endl; + } else { + throw "compiler error: Invalid type in generate_serialize_field '" + type->get_name() + + "' for field '" + name + "'"; + } +} + +/** + * Serializes all the members of a struct. + * + * @param tstruct The struct to serialize + * @param prefix String prefix to attach to all fields + */ +void t_go_generator::generate_serialize_struct(ostream& out, t_struct* tstruct, string prefix) { + (void)tstruct; + out << indent() << "if err := " << prefix << "." << write_method_name_ << "(oprot); err != nil {" << endl; + out << indent() << " return thrift.PrependError(fmt.Sprintf(\"%T error writing struct: \", " + << prefix << "), err)" << endl; + out << indent() << "}" << endl; +} + +void t_go_generator::generate_serialize_container(ostream& out, + t_type* ttype, + bool pointer_field, + string prefix) { + if (pointer_field) { + prefix = "*" + prefix; + } + if (ttype->is_map()) { + out << indent() << "if err := oprot.WriteMapBegin(" + << type_to_enum(((t_map*)ttype)->get_key_type()) << ", " + << type_to_enum(((t_map*)ttype)->get_val_type()) << ", " + << "len(" << prefix << ")); err != nil {" << endl; + out << indent() << " return thrift.PrependError(\"error writing map begin: \", err)" << endl; + out << indent() << "}" << endl; + } else if (ttype->is_set()) { + out << indent() << "if err := oprot.WriteSetBegin(" + << type_to_enum(((t_set*)ttype)->get_elem_type()) << ", " + << "len(" << prefix << ")); err != nil {" << endl; + out << indent() << " return thrift.PrependError(\"error writing set begin: \", err)" << endl; + out << indent() << "}" << endl; + } else if (ttype->is_list()) { + out << indent() << "if err := oprot.WriteListBegin(" + << type_to_enum(((t_list*)ttype)->get_elem_type()) << ", " + << "len(" << prefix << ")); err != nil {" << endl; + out << indent() << " return thrift.PrependError(\"error writing list begin: \", err)" << endl; + out << indent() << "}" << endl; + } else { + throw "compiler error: Invalid type in generate_serialize_container '" + ttype->get_name() + + "' for prefix '" + prefix + "'"; + } + + if (ttype->is_map()) { + t_map* tmap = (t_map*)ttype; + out << indent() << "for k, v := range " << prefix << " {" << endl; + indent_up(); + generate_serialize_map_element(out, tmap, "k", "v"); + indent_down(); + indent(out) << "}" << endl; + } else if (ttype->is_set()) { + t_set* tset = (t_set*)ttype; + out << indent() << "for i := 0; i<len(" << prefix << "); i++ {" << endl; + out << indent() << " for j := i+1; j<len(" << prefix << "); j++ {" << endl; + string wrapped_prefix = prefix; + if (pointer_field) { + wrapped_prefix = "(" + prefix + ")"; + } + out << indent() << " if reflect.DeepEqual(" << wrapped_prefix << "[i]," << wrapped_prefix << "[j]) { " << endl; + out << indent() << " return thrift.PrependError(\"\", fmt.Errorf(\"%T error writing set field: slice is not unique\", " << wrapped_prefix << "[i]))" << endl; + out << indent() << " }" << endl; + out << indent() << " }" << endl; + out << indent() << "}" << endl; + out << indent() << "for _, v := range " << prefix << " {" << endl; + indent_up(); + generate_serialize_set_element(out, tset, "v"); + indent_down(); + indent(out) << "}" << endl; + } else if (ttype->is_list()) { + t_list* tlist = (t_list*)ttype; + out << indent() << "for _, v := range " << prefix << " {" << endl; + + indent_up(); + generate_serialize_list_element(out, tlist, "v"); + indent_down(); + indent(out) << "}" << endl; + } + + if (ttype->is_map()) { + out << indent() << "if err := oprot.WriteMapEnd(); err != nil {" << endl; + out << indent() << " return thrift.PrependError(\"error writing map end: \", err)" << endl; + out << indent() << "}" << endl; + } else if (ttype->is_set()) { + out << indent() << "if err := oprot.WriteSetEnd(); err != nil {" << endl; + out << indent() << " return thrift.PrependError(\"error writing set end: \", err)" << endl; + out << indent() << "}" << endl; + } else if (ttype->is_list()) { + out << indent() << "if err := oprot.WriteListEnd(); err != nil {" << endl; + out << indent() << " return thrift.PrependError(\"error writing list end: \", err)" << endl; + out << indent() << "}" << endl; + } +} + +/** + * Serializes the members of a map. + * + */ +void t_go_generator::generate_serialize_map_element(ostream& out, + t_map* tmap, + string kiter, + string viter) { + t_field kfield(tmap->get_key_type(), ""); + t_field vfield(tmap->get_val_type(), ""); + kfield.set_req(t_field::T_OPT_IN_REQ_OUT); + vfield.set_req(t_field::T_OPT_IN_REQ_OUT); + generate_serialize_field(out, &kfield, kiter, true); + generate_serialize_field(out, &vfield, viter); +} + +/** + * Serializes the members of a set. + */ +void t_go_generator::generate_serialize_set_element(ostream& out, t_set* tset, string prefix) { + t_field efield(tset->get_elem_type(), ""); + efield.set_req(t_field::T_OPT_IN_REQ_OUT); + generate_serialize_field(out, &efield, prefix); +} + +/** + * Serializes the members of a list. + */ +void t_go_generator::generate_serialize_list_element(ostream& out, t_list* tlist, string prefix) { + t_field efield(tlist->get_elem_type(), ""); + efield.set_req(t_field::T_OPT_IN_REQ_OUT); + generate_serialize_field(out, &efield, prefix); +} + +/** + * Generates the docstring for a given struct. + */ +void t_go_generator::generate_go_docstring(ostream& out, t_struct* tstruct) { + generate_go_docstring(out, tstruct, tstruct, "Attributes"); +} + +/** + * Generates the docstring for a given function. + */ +void t_go_generator::generate_go_docstring(ostream& out, t_function* tfunction) { + generate_go_docstring(out, tfunction, tfunction->get_arglist(), "Parameters"); +} + +/** + * Generates the docstring for a struct or function. + */ +void t_go_generator::generate_go_docstring(ostream& out, + t_doc* tdoc, + t_struct* tstruct, + const char* subheader) { + bool has_doc = false; + stringstream ss; + + if (tdoc->has_doc()) { + has_doc = true; + ss << tdoc->get_doc(); + } + + const vector<t_field*>& fields = tstruct->get_members(); + + if (fields.size() > 0) { + if (has_doc) { + ss << endl; + } + + has_doc = true; + ss << subheader << ":\n"; + vector<t_field*>::const_iterator p_iter; + + for (p_iter = fields.begin(); p_iter != fields.end(); ++p_iter) { + t_field* p = *p_iter; + ss << " - " << publicize(p->get_name()); + + if (p->has_doc()) { + ss << ": " << p->get_doc(); + } else { + ss << endl; + } + } + } + + if (has_doc) { + generate_docstring_comment(out, "", "// ", ss.str(), ""); + } +} + +/** + * Generates the docstring for a generic object. + */ +void t_go_generator::generate_go_docstring(ostream& out, t_doc* tdoc) { + if (tdoc->has_doc()) { + generate_docstring_comment(out, "", "//", tdoc->get_doc(), ""); + } +} + +/** + * Declares an argument, which may include initialization as necessary. + * + * @param tfield The field + */ +string t_go_generator::declare_argument(t_field* tfield) { + std::ostringstream result; + result << publicize(tfield->get_name()) << "="; + + if (tfield->get_value() != NULL) { + result << "thrift_spec[" << tfield->get_key() << "][4]"; + } else { + result << "nil"; + } + + return result.str(); +} + +/** + * Renders a struct field initial value. + * + * @param tfield The field, which must have `tfield->get_value() != NULL` + */ +string t_go_generator::render_field_initial_value(t_field* tfield, + const string& name, + bool optional_field) { + t_type* type = get_true_type(tfield->get_type()); + + if (optional_field) { + // The caller will make a second pass for optional fields, + // assigning the result of render_const_value to "*field_name". It + // is maddening that Go syntax does not allow for a type-agnostic + // way to initialize a pointer to a const value, but so it goes. + // The alternative would be to write type specific functions that + // convert from const values to pointer types, but given the lack + // of overloading it would be messy. + return "new(" + type_to_go_type(tfield->get_type()) + ")"; + } else { + return render_const_value(type, tfield->get_value(), name); + } +} + +/** + * Renders a function signature of the form 'type name(args)' + * + * @param tfunction Function definition + * @return String of rendered function definition + */ +string t_go_generator::function_signature(t_function* tfunction, string prefix) { + // TODO(mcslee): Nitpicky, no ',' if argument_list is empty + return publicize(prefix + tfunction->get_name()) + "(" + argument_list(tfunction->get_arglist()) + + ")"; +} + +/** + * Renders an interface function signature of the form 'type name(args)' + * + * @param tfunction Function definition + * @return String of rendered function definition + */ +string t_go_generator::function_signature_if(t_function* tfunction, string prefix, bool addError) { + string signature = publicize(prefix + tfunction->get_name()) + "("; + signature += "ctx context.Context"; + if (!tfunction->get_arglist()->get_members().empty()) { + signature += ", " + argument_list(tfunction->get_arglist()); + } + signature += ") ("; + + t_type* ret = tfunction->get_returntype(); + t_struct* exceptions = tfunction->get_xceptions(); + string errs = argument_list(exceptions); + + if (!ret->is_void()) { + signature += "r " + type_to_go_type(ret); + + if (addError || errs.size() == 0) { + signature += ", "; + } + } + + if (addError) { + signature += "err error"; + } + + signature += ")"; + return signature; +} + +/** + * Renders a field list + */ +string t_go_generator::argument_list(t_struct* tstruct) { + string result = ""; + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + result += ", "; + } + + result += variable_name_to_go_name((*f_iter)->get_name()) + " " + + type_to_go_type((*f_iter)->get_type()); + } + + return result; +} + +string t_go_generator::type_name(t_type* ttype) { + string module( module_name(ttype)); + if( ! module.empty()) { + return module + "." + ttype->get_name(); + } + + return ttype->get_name(); +} + +string t_go_generator::module_name(t_type* ttype) { + t_program* program = ttype->get_program(); + + if (program != NULL && program != program_) { + if (program->get_namespace("go").empty() || + program_->get_namespace("go").empty() || + program->get_namespace("go") != program_->get_namespace("go")) { + string module(get_real_go_module(program)); + return package_identifiers_[module]; + } + } + + return ""; +} + +/** + * Converts the parse type to a go tyoe + */ +string t_go_generator::type_to_enum(t_type* type) { + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + + case t_base_type::TYPE_STRING: + /* this is wrong, binary is still a string type internally + if (type->is_binary()) { + return "thrift.BINARY"; + } + */ + return "thrift.STRING"; + + case t_base_type::TYPE_BOOL: + return "thrift.BOOL"; + + case t_base_type::TYPE_I8: + return "thrift.BYTE"; + + case t_base_type::TYPE_I16: + return "thrift.I16"; + + case t_base_type::TYPE_I32: + return "thrift.I32"; + + case t_base_type::TYPE_I64: + return "thrift.I64"; + + case t_base_type::TYPE_DOUBLE: + return "thrift.DOUBLE"; + } + } else if (type->is_enum()) { + return "thrift.I32"; + } else if (type->is_struct() || type->is_xception()) { + return "thrift.STRUCT"; + } else if (type->is_map()) { + return "thrift.MAP"; + } else if (type->is_set()) { + return "thrift.SET"; + } else if (type->is_list()) { + return "thrift.LIST"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +/** + * Converts the parse type to a go map type, will throw an exception if it will + * not produce a valid go map type. + */ +string t_go_generator::type_to_go_key_type(t_type* type) { + t_type* resolved_type = type; + + while (resolved_type->is_typedef()) { + resolved_type = ((t_typedef*)resolved_type)->get_type()->get_true_type(); + } + + if (resolved_type->is_map() || resolved_type->is_list() || resolved_type->is_set()) { + throw "Cannot produce a valid type for a Go map key: " + type_to_go_type(type) + " - aborting."; + } + + if (resolved_type->is_binary()) + return "string"; + + return type_to_go_type(type); +} + +/** + * Converts the parse type to a go type + */ +string t_go_generator::type_to_go_type(t_type* type) { + return type_to_go_type_with_opt(type, false); +} + +/** + * Converts the parse type to a go type, taking into account whether the field + * associated with the type is T_OPTIONAL. + */ +string t_go_generator::type_to_go_type_with_opt(t_type* type, + bool optional_field) { + string maybe_pointer(optional_field ? "*" : ""); + + if (type->is_typedef() && ((t_typedef*)type)->is_forward_typedef()) { + type = ((t_typedef*)type)->get_true_type(); + } + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + + switch (tbase) { + case t_base_type::TYPE_VOID: + throw ""; + + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + return maybe_pointer + "[]byte"; + } + + return maybe_pointer + "string"; + + case t_base_type::TYPE_BOOL: + return maybe_pointer + "bool"; + + case t_base_type::TYPE_I8: + return maybe_pointer + "int8"; + + case t_base_type::TYPE_I16: + return maybe_pointer + "int16"; + + case t_base_type::TYPE_I32: + return maybe_pointer + "int32"; + + case t_base_type::TYPE_I64: + return maybe_pointer + "int64"; + + case t_base_type::TYPE_DOUBLE: + return maybe_pointer + "float64"; + } + } else if (type->is_enum()) { + return maybe_pointer + publicize(type_name(type)); + } else if (type->is_struct() || type->is_xception()) { + return "*" + publicize(type_name(type)); + } else if (type->is_map()) { + t_map* t = (t_map*)type; + string keyType = type_to_go_key_type(t->get_key_type()); + string valueType = type_to_go_type(t->get_val_type()); + return maybe_pointer + string("map[") + keyType + "]" + valueType; + } else if (type->is_set()) { + t_set* t = (t_set*)type; + string elemType = type_to_go_type(t->get_elem_type()); + return maybe_pointer + string("[]") + elemType; + } else if (type->is_list()) { + t_list* t = (t_list*)type; + string elemType = type_to_go_type(t->get_elem_type()); + return maybe_pointer + string("[]") + elemType; + } else if (type->is_typedef()) { + return maybe_pointer + publicize(type_name(type)); + } + + throw "INVALID TYPE IN type_to_go_type: " + type->get_name(); +} + +/** See the comment inside generate_go_struct_definition for what this is. */ +string t_go_generator::type_to_spec_args(t_type* ttype) { + while (ttype->is_typedef()) { + ttype = ((t_typedef*)ttype)->get_type(); + } + + if (ttype->is_base_type() || ttype->is_enum()) { + return "nil"; + } else if (ttype->is_struct() || ttype->is_xception()) { + return "(" + type_name(ttype) + ", " + type_name(ttype) + ".thrift_spec)"; + } else if (ttype->is_map()) { + return "(" + type_to_enum(((t_map*)ttype)->get_key_type()) + "," + + type_to_spec_args(((t_map*)ttype)->get_key_type()) + "," + + type_to_enum(((t_map*)ttype)->get_val_type()) + "," + + type_to_spec_args(((t_map*)ttype)->get_val_type()) + ")"; + } else if (ttype->is_set()) { + return "(" + type_to_enum(((t_set*)ttype)->get_elem_type()) + "," + + type_to_spec_args(((t_set*)ttype)->get_elem_type()) + ")"; + } else if (ttype->is_list()) { + return "(" + type_to_enum(((t_list*)ttype)->get_elem_type()) + "," + + type_to_spec_args(((t_list*)ttype)->get_elem_type()) + ")"; + } + + throw "INVALID TYPE IN type_to_spec_args: " + ttype->get_name(); +} + +bool format_go_output(const string& file_path) { + + // formatting via gofmt deactivated due to THRIFT-3893 + // Please look at the ticket and make sure you fully understand all the implications + // before submitting a patch that enables this feature again. Thank you. + (void) file_path; + return false; + + /* + const string command = "gofmt -w " + file_path; + + if (system(command.c_str()) == 0) { + return true; + } + + fprintf(stderr, "WARNING - Running '%s' failed.\n", command.c_str()); + return false; + */ + } + +THRIFT_REGISTER_GENERATOR(go, "Go", + " package_prefix= Package prefix for generated files.\n" \ + " thrift_import= Override thrift package import path (default:" + DEFAULT_THRIFT_IMPORT + ")\n" \ + " package= Package name (default: inferred from thrift file name)\n" \ + " ignore_initialisms\n" + " Disable automatic spelling correction of initialisms (e.g. \"URL\")\n" \ + " read_write_private\n" + " Make read/write methods private, default is public Read/Write\n") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_gv_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_gv_generator.cc new file mode 100644 index 000000000..2c0acf979 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_gv_generator.cc @@ -0,0 +1,345 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <string> +#include <fstream> +#include <iostream> +#include <vector> +#include <map> +#include <list> + +#include <stdlib.h> +#include <sys/stat.h> +#include <sstream> +#include "thrift/platform.h" +#include "thrift/generate/t_generator.h" + +using std::map; +using std::ofstream; +using std::ostringstream; +using std::pair; +using std::string; +using std::stringstream; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes + +/** + * Graphviz code generator + */ +class t_gv_generator : public t_generator { +public: + t_gv_generator(t_program* program, + const std::map<std::string, std::string>& parsed_options, + const std::string& option_string) + : t_generator(program) { + (void)option_string; + std::map<std::string, std::string>::const_iterator iter; + + exception_arrows = false; + for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + if( iter->first.compare("exceptions") == 0) { + exception_arrows = true; + } else { + throw "unknown option gv:" + iter->first; + } + } + + out_dir_base_ = "gen-gv"; + } + + /** + * Init and end of generator + */ + void init_generator() override; + void close_generator() override; + + /** + * Program-level generation functions + */ + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_const(t_const* tconst) override; + void generate_struct(t_struct* tstruct) override; + void generate_service(t_service* tservice) override; + +protected: + /** + * Helpers + */ + void print_type(t_type* ttype, string struct_field_ref); + void print_const_value(t_type* type, t_const_value* tvalue); + +private: + ofstream_with_content_based_conditional_update f_out_; + std::list<string> edges; + bool exception_arrows; +}; + +/** + * Init generator: + * - Adds some escaping for the Graphviz domain. + * - Create output directory and open file for writting. + * - Write the file header. + */ +void t_gv_generator::init_generator() { + escape_['{'] = "\\{"; + escape_['}'] = "\\}"; + + // Make output directory + MKDIR(get_out_dir().c_str()); + string fname = get_out_dir() + program_->get_name() + ".gv"; + f_out_.open(fname.c_str()); + f_out_ << "digraph \"" << escape_string(program_name_) << "\" {" << endl; + f_out_ << "node [style=filled, shape=record];" << endl; + f_out_ << "edge [arrowsize=0.5];" << endl; + f_out_ << "rankdir=LR" << endl; +} + +/** + * Closes generator: + * - Print accumulated nodes connections. + * - Print footnote. + * - Closes file. + */ +void t_gv_generator::close_generator() { + // Print edges + std::list<string>::iterator iter = edges.begin(); + for (; iter != edges.end(); iter++) { + f_out_ << (*iter) << endl; + } + + // Print graph end } and close file + f_out_ << "}" << endl; + f_out_.close(); +} + +void t_gv_generator::generate_typedef(t_typedef* ttypedef) { + string name = ttypedef->get_name(); + f_out_ << "node [fillcolor=azure];" << endl; + f_out_ << name << " [label=\""; + + f_out_ << escape_string(name); + f_out_ << " :: "; + print_type(ttypedef->get_type(), name); + + f_out_ << "\"];" << endl; +} + +void t_gv_generator::generate_enum(t_enum* tenum) { + string name = tenum->get_name(); + f_out_ << "node [fillcolor=white];" << endl; + f_out_ << name << " [label=\"enum " << escape_string(name); + + vector<t_enum_value*> values = tenum->get_constants(); + vector<t_enum_value*>::iterator val_iter; + for (val_iter = values.begin(); val_iter != values.end(); ++val_iter) { + f_out_ << '|' << (*val_iter)->get_name(); + f_out_ << " = "; + f_out_ << (*val_iter)->get_value(); + } + + f_out_ << "\"];" << endl; +} + +void t_gv_generator::generate_const(t_const* tconst) { + string name = tconst->get_name(); + + f_out_ << "node [fillcolor=aliceblue];" << endl; + f_out_ << "const_" << name << " [label=\""; + + f_out_ << escape_string(name); + f_out_ << " = "; + print_const_value(tconst->get_type(), tconst->get_value()); + f_out_ << " :: "; + print_type(tconst->get_type(), "const_" + name); + + f_out_ << "\"];" << endl; +} + +void t_gv_generator::generate_struct(t_struct* tstruct) { + string name = tstruct->get_name(); + + if (tstruct->is_xception()) { + f_out_ << "node [fillcolor=lightpink];" << endl; + f_out_ << name << " [label=\""; + f_out_ << "exception " << escape_string(name); + } else if (tstruct->is_union()) { + f_out_ << "node [fillcolor=lightcyan];" << endl; + f_out_ << name << " [label=\""; + f_out_ << "union " << escape_string(name); + } else { + f_out_ << "node [fillcolor=beige];" << endl; + f_out_ << name << " [label=\""; + f_out_ << "struct " << escape_string(name); + } + + vector<t_field*> members = tstruct->get_members(); + vector<t_field*>::iterator mem_iter = members.begin(); + for (; mem_iter != members.end(); mem_iter++) { + string field_name = (*mem_iter)->get_name(); + + // print port (anchor reference) + f_out_ << "|<field_" << field_name << '>'; + + // field name :: field type + f_out_ << (*mem_iter)->get_name(); + f_out_ << " :: "; + print_type((*mem_iter)->get_type(), name + ":field_" + field_name); + } + + f_out_ << "\"];" << endl; +} + +void t_gv_generator::print_type(t_type* ttype, string struct_field_ref) { + if (ttype->is_container()) { + if (ttype->is_list()) { + f_out_ << "list\\<"; + print_type(((t_list*)ttype)->get_elem_type(), struct_field_ref); + f_out_ << "\\>"; + } else if (ttype->is_set()) { + f_out_ << "set\\<"; + print_type(((t_set*)ttype)->get_elem_type(), struct_field_ref); + f_out_ << "\\>"; + } else if (ttype->is_map()) { + f_out_ << "map\\<"; + print_type(((t_map*)ttype)->get_key_type(), struct_field_ref); + f_out_ << ", "; + print_type(((t_map*)ttype)->get_val_type(), struct_field_ref); + f_out_ << "\\>"; + } + } else if (ttype->is_base_type()) { + f_out_ << (ttype->is_binary() ? "binary" : ttype->get_name()); + } else { + f_out_ << ttype->get_name(); + edges.push_back(struct_field_ref + " -> " + ttype->get_name()); + } +} + +/** + * Prints out an string representation of the provided constant value + */ +void t_gv_generator::print_const_value(t_type* type, t_const_value* tvalue) { + bool first = true; + switch (tvalue->get_type()) { + case t_const_value::CV_INTEGER: + f_out_ << tvalue->get_integer(); + break; + case t_const_value::CV_DOUBLE: + f_out_ << tvalue->get_double(); + break; + case t_const_value::CV_STRING: + f_out_ << "\\\"" << get_escaped_string(tvalue) << "\\\""; + break; + case t_const_value::CV_MAP: { + f_out_ << "\\{ "; + map<t_const_value*, t_const_value*, t_const_value::value_compare> map_elems = tvalue->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::iterator map_iter; + for (map_iter = map_elems.begin(); map_iter != map_elems.end(); map_iter++) { + if (!first) { + f_out_ << ", "; + } + first = false; + print_const_value(((t_map*)type)->get_key_type(), map_iter->first); + f_out_ << " = "; + print_const_value(((t_map*)type)->get_val_type(), map_iter->second); + } + f_out_ << " \\}"; + } break; + case t_const_value::CV_LIST: { + f_out_ << "\\{ "; + vector<t_const_value*> list_elems = tvalue->get_list(); + ; + vector<t_const_value*>::iterator list_iter; + for (list_iter = list_elems.begin(); list_iter != list_elems.end(); list_iter++) { + if (!first) { + f_out_ << ", "; + } + first = false; + if (type->is_list()) { + print_const_value(((t_list*)type)->get_elem_type(), *list_iter); + } else { + print_const_value(((t_set*)type)->get_elem_type(), *list_iter); + } + } + f_out_ << " \\}"; + } break; + case t_const_value::CV_IDENTIFIER: + f_out_ << escape_string(type->get_name()) << "." + << escape_string(tvalue->get_identifier_name()); + break; + default: + f_out_ << "UNKNOWN"; + break; + } +} + +void t_gv_generator::generate_service(t_service* tservice) { + string service_name = get_service_name(tservice); + f_out_ << "subgraph cluster_" << service_name << " {" << endl; + f_out_ << "node [fillcolor=bisque];" << endl; + f_out_ << "style=dashed;" << endl; + f_out_ << "label = \"" << escape_string(service_name) << " service\";" << endl; + + // TODO: service extends + + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator fn_iter = functions.begin(); + for (; fn_iter != functions.end(); fn_iter++) { + string fn_name = (*fn_iter)->get_name(); + + f_out_ << "function_" << service_name << fn_name; + f_out_ << "[label=\"<return_type>function " << escape_string(fn_name); + f_out_ << " :: "; + print_type((*fn_iter)->get_returntype(), "function_" + service_name + fn_name + ":return_type"); + + vector<t_field*> args = (*fn_iter)->get_arglist()->get_members(); + vector<t_field*>::iterator arg_iter = args.begin(); + for (; arg_iter != args.end(); arg_iter++) { + f_out_ << "|<param_" << (*arg_iter)->get_name() << ">"; + f_out_ << (*arg_iter)->get_name(); + if ((*arg_iter)->get_value() != NULL) { + f_out_ << " = "; + print_const_value((*arg_iter)->get_type(), (*arg_iter)->get_value()); + } + f_out_ << " :: "; + print_type((*arg_iter)->get_type(), + "function_" + service_name + fn_name + ":param_" + (*arg_iter)->get_name()); + } + // end of node + f_out_ << "\"];" << endl; + + // Exception edges + if (exception_arrows) { + vector<t_field*> excepts = (*fn_iter)->get_xceptions()->get_members(); + vector<t_field*>::iterator ex_iter = excepts.begin(); + for (; ex_iter != excepts.end(); ex_iter++) { + edges.push_back("function_" + service_name + fn_name + " -> " + + (*ex_iter)->get_type()->get_name() + " [color=red]"); + } + } + } + + f_out_ << " }" << endl; +} + +THRIFT_REGISTER_GENERATOR( + gv, + "Graphviz", + " exceptions: Whether to draw arrows from functions to exception.\n") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_haxe_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_haxe_generator.cc new file mode 100644 index 000000000..13f2cd1ba --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_haxe_generator.cc @@ -0,0 +1,2981 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <sstream> +#include <string> +#include <fstream> +#include <iostream> +#include <vector> +#include <cctype> + +#include <sys/stat.h> +#include <stdexcept> + +#include "thrift/platform.h" +#include "thrift/generate/t_oop_generator.h" + +using std::map; +using std::ostream; +using std::ostringstream; +using std::string; +using std::stringstream; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes + +/** + * Haxe code generator. + * + */ +class t_haxe_generator : public t_oop_generator { +public: + t_haxe_generator(t_program* program, + const std::map<std::string, std::string>& parsed_options, + const std::string& option_string) + : t_oop_generator(program) { + (void)option_string; + std::map<std::string, std::string>::const_iterator iter; + + callbacks_ = false; + rtti_ = false; + buildmacro_ = ""; + for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + if( iter->first.compare("callbacks") == 0) { + callbacks_ = true; + } else if( iter->first.compare("rtti") == 0) { + rtti_ = true; + } else if( iter->first.compare("buildmacro") == 0) { + buildmacro_ = (iter->second); + } else { + throw "unknown option haxe:" + iter->first; + } + } + + out_dir_base_ = "gen-haxe"; + } + + /** + * Init and close methods + */ + + void init_generator() override; + void close_generator() override; + + void generate_consts(std::vector<t_const*> consts) override; + + /** + * Program-level generation functions + */ + + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_struct(t_struct* tstruct) override; + void generate_xception(t_struct* txception) override; + void generate_service(t_service* tservice) override; + + void print_const_value(std::ostream& out, + std::string name, + t_type* type, + t_const_value* value, + bool in_static, + bool defval = false); + std::string render_const_value(ostream& out, + std::string name, + t_type* type, + t_const_value* value); + + /** + * Service-level generation functions + */ + + void generate_haxe_struct(t_struct* tstruct, bool is_exception, bool is_result = false); + + void generate_haxe_struct_definition(std::ostream& out, + t_struct* tstruct, + bool is_xception = false, + bool is_result = false); + // removed -- equality,compare_to + void generate_haxe_struct_reader(std::ostream& out, t_struct* tstruct); + void generate_haxe_validator(std::ostream& out, t_struct* tstruct); + void generate_haxe_struct_result_writer(std::ostream& out, t_struct* tstruct); + void generate_haxe_struct_writer(std::ostream& out, t_struct* tstruct); + void generate_haxe_struct_tostring(std::ostream& out, t_struct* tstruct); + void generate_haxe_meta_data_map(std::ostream& out, t_struct* tstruct); + void generate_field_value_meta_data(std::ostream& out, t_type* type); + std::string get_haxe_type_string(t_type* type); + void generate_reflection_setters(std::ostringstream& out, + t_type* type, + std::string field_name, + std::string cap_name); + void generate_reflection_getters(std::ostringstream& out, + t_type* type, + std::string field_name, + std::string cap_name); + void generate_generic_field_getters_setters(std::ostream& out, t_struct* tstruct); + void generate_generic_isset_method(std::ostream& out, t_struct* tstruct); + void generate_property_getters_setters(std::ostream& out, t_struct* tstruct); + + void generate_function_helpers(t_function* tfunction); + std::string get_cap_name(std::string name); + std::string generate_isset_check(t_field* field); + std::string generate_isset_check(std::string field); + void generate_isset_set(ostream& out, t_field* field); + // removed std::string isset_field_id(t_field* field); + + void generate_service_interface(t_service* tservice); + void generate_service_helpers(t_service* tservice); + void generate_service_client(t_service* tservice); + void generate_service_server(t_service* tservice); + void generate_process_function(t_service* tservice, t_function* tfunction); + void generate_service_method_signature(t_function* tfunction, bool is_interface); + + /** + * Serialization constructs + */ + + void generate_deserialize_field(std::ostream& out, t_field* tfield, std::string prefix = ""); + void generate_deserialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); + void generate_deserialize_container(std::ostream& out, t_type* ttype, std::string prefix = ""); + void generate_deserialize_set_element(std::ostream& out, t_set* tset, std::string prefix = ""); + void generate_deserialize_map_element(std::ostream& out, t_map* tmap, std::string prefix = ""); + void generate_deserialize_list_element(std::ostream& out, + t_list* tlist, + std::string prefix = ""); + + void generate_serialize_field(std::ostream& out, t_field* tfield, std::string prefix = ""); + void generate_serialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); + void generate_serialize_container(std::ostream& out, t_type* ttype, std::string prefix = ""); + void generate_serialize_set_element(std::ostream& out, t_set* tmap, std::string iter); + void generate_serialize_list_element(std::ostream& out, t_list* tlist, std::string iter); + void generate_serialize_map_element(std::ostream& out, + t_map* tmap, + std::string iter, + std::string map); + + void generate_haxe_doc(std::ostream& out, t_doc* tdoc); + void generate_haxe_doc(std::ostream& out, t_function* tdoc); + + void generate_rtti_decoration(std::ostream& out); + void generate_macro_decoration(std::ostream& out); + + /** + * Helper rendering functions + */ + + std::string haxe_package(); + std::string haxe_type_imports(); + std::string haxe_thrift_imports(); + std::string haxe_thrift_gen_imports(t_struct* tstruct, string& imports); + std::string haxe_thrift_gen_imports(t_service* tservice); + std::string type_name(t_type* ttype, bool in_container = false, bool in_init = false); + std::string base_type_name(t_base_type* tbase, bool in_container = false); + std::string declare_field(t_field* tfield, bool init = false); + std::string function_signature_callback(t_function* tfunction); + std::string function_signature_normal(t_function* tfunction); + std::string argument_list(t_struct* tstruct); + std::string type_to_enum(t_type* ttype); + std::string get_enum_class_name(t_type* type) override; + string generate_service_method_onsuccess(t_function* tfunction, bool as_type, bool omit_name); + void generate_service_method_signature_callback(t_function* tfunction, bool is_interface); + void generate_service_method_signature_normal(t_function* tfunction, bool is_interface); + + bool type_can_be_null(t_type* ttype) { + ttype = get_true_type(ttype); + + if (ttype->is_container() || ttype->is_struct() || ttype->is_xception() || ttype->is_string()) { + return true; + } + + if (ttype->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)ttype)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + // case t_base_type::TYPE_I64: - Int64 is not really nullable, even though it behaved that + // way before Haxe 3.2.0 + return true; + default: + return false; + } + } + + return false; + } + + std::string constant_name(std::string name); + +private: + bool callbacks_; + bool rtti_; + string buildmacro_; + + /** + * File streams + */ + + std::string package_name_; + ofstream_with_content_based_conditional_update f_service_; + std::string package_dir_; +}; + +/** + * Prepares for file generation by opening up the necessary file output + * streams. + * + * @param tprogram The program to generate + */ +void t_haxe_generator::init_generator() { + // Make output directory + MKDIR(get_out_dir().c_str()); + package_name_ = program_->get_namespace("haxe"); + + // Haxe package names are lowercase + if (package_name_.length() > 0) { + package_name_[0] = tolower(package_name_[0]); + size_t index = package_name_.find('.'); + while (index != std::string::npos) { + if (++index < package_name_.length()) { + package_name_[index] = tolower(package_name_[index]); + } + index = package_name_.find('.', index); + } + } + + string dir = package_name_; + string subdir = get_out_dir(); + string::size_type loc; + while ((loc = dir.find(".")) != string::npos) { + subdir = subdir + "/" + dir.substr(0, loc); + MKDIR(subdir.c_str()); + dir = dir.substr(loc + 1); + } + if (dir.size() > 0) { + subdir = subdir + "/" + dir; + MKDIR(subdir.c_str()); + } + + package_dir_ = subdir; +} + +/** + * Packages the generated file + * + * @return String of the package, i.e. "package org.apache.thriftdemo;" + */ +string t_haxe_generator::haxe_package() { + if (!package_name_.empty()) { + return string("package ") + package_name_; + } + return "package"; +} + +/** + * Prints standard haxe imports + * + * @return List of imports for haxe types that are used in here + */ +string t_haxe_generator::haxe_type_imports() { + return string() + "import org.apache.thrift.helper.*;\n" + "import haxe.io.Bytes;\n" + + "import haxe.ds.IntMap;\n" + "import haxe.ds.StringMap;\n" + + "import haxe.ds.ObjectMap;\n" + "\n" + "#if flash\n" + + "import flash.errors.ArgumentError;\n" + "#end\n" + "\n"; +} + +/** + * Prints standard haxe imports + * + * @return List of imports necessary for thrift + */ +string t_haxe_generator::haxe_thrift_imports() { + return string() + "import org.apache.thrift.*;\n" + "import org.apache.thrift.meta_data.*;\n" + + "import org.apache.thrift.protocol.*;\n" + "\n"; +} + +/** + * Prints imports needed for a given type + * + * @return List of imports necessary for a given t_struct + */ +string t_haxe_generator::haxe_thrift_gen_imports(t_struct* tstruct, string& imports) { + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + // For each type check if it is from a different namespace + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_program* program = (*m_iter)->get_type()->get_program(); + if (program != NULL && program != program_) { + string package = program->get_namespace("haxe"); + if (!package.empty()) { + if (imports.find(package + "." + (*m_iter)->get_type()->get_name()) == string::npos) { + imports.append("import " + package + "." + (*m_iter)->get_type()->get_name() + ";\n"); + } + } + } + } + return imports; +} + +/** + * Prints imports needed for a given type + * + * @return List of imports necessary for a given t_service + */ +string t_haxe_generator::haxe_thrift_gen_imports(t_service* tservice) { + string imports; + const vector<t_function*>& functions = tservice->get_functions(); + vector<t_function*>::const_iterator f_iter; + + // For each type check if it is from a different namespace + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_program* program = (*f_iter)->get_returntype()->get_program(); + if (program != NULL && program != program_) { + string package = program->get_namespace("haxe"); + if (!package.empty()) { + if (imports.find(package + "." + (*f_iter)->get_returntype()->get_name()) == string::npos) { + imports.append("import " + package + "." + (*f_iter)->get_returntype()->get_name() + + ";\n"); + } + } + } + + haxe_thrift_gen_imports((*f_iter)->get_arglist(), imports); + haxe_thrift_gen_imports((*f_iter)->get_xceptions(), imports); + } + + return imports; +} + +/** + * Nothing in haxe + */ +void t_haxe_generator::close_generator() { +} + +/** + * Generates a typedef. This is not done in haxe, since it does + * not support arbitrary name replacements, and it'd be a wacky waste + * of overhead to make wrapper classes. + * + * @param ttypedef The type definition + */ +void t_haxe_generator::generate_typedef(t_typedef* ttypedef) { + (void)ttypedef; +} + +/** + * Enums are a class with a set of static constants. + * + * @param tenum The enumeration + */ +void t_haxe_generator::generate_enum(t_enum* tenum) { + // Make output file + string f_enum_name = package_dir_ + "/" + get_cap_name(tenum->get_name()) + ".hx"; + ofstream_with_content_based_conditional_update f_enum; + f_enum.open(f_enum_name.c_str()); + + // Comment and package it + f_enum << autogen_comment() << haxe_package() << ";" << endl << endl; + + // Add haxe imports + f_enum << string() + "import org.apache.thrift.helper.*;" << endl << endl; + + generate_rtti_decoration(f_enum); + generate_macro_decoration(f_enum); + indent(f_enum) << "class " << get_cap_name(tenum->get_name()) << " "; + scope_up(f_enum); + + vector<t_enum_value*> constants = tenum->get_constants(); + vector<t_enum_value*>::iterator c_iter; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + int value = (*c_iter)->get_value(); + indent(f_enum) << "public static inline var " << (*c_iter)->get_name() << " : Int = " << value + << ";" << endl; + } + + // Create a static Set with all valid values for this enum + f_enum << endl; + + indent(f_enum) << "public static var VALID_VALUES = { new IntSet( ["; + indent_up(); + bool firstValue = true; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + // populate set + f_enum << (firstValue ? "" : ", ") << (*c_iter)->get_name(); + firstValue = false; + } + indent_down(); + f_enum << "]); };" << endl; + + indent(f_enum) << "public static var VALUES_TO_NAMES = { ["; + indent_up(); + firstValue = true; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + f_enum << (firstValue ? "" : ",") << endl; + indent(f_enum) << (*c_iter)->get_name() << " => \"" << (*c_iter)->get_name() << "\""; + firstValue = false; + } + f_enum << endl; + indent_down(); + indent(f_enum) << "]; };" << endl; + + scope_down(f_enum); // end class + + f_enum.close(); +} + +/** + * Generates a class that holds all the constants. + */ +void t_haxe_generator::generate_consts(std::vector<t_const*> consts) { + if (consts.empty()) { + return; + } + + string f_consts_name = package_dir_ + "/" + get_cap_name(program_name_) + "Constants.hx"; + ofstream_with_content_based_conditional_update f_consts; + f_consts.open(f_consts_name.c_str()); + + // Print header + f_consts << autogen_comment() << haxe_package() << ";" << endl << endl; + + f_consts << endl; + + f_consts << haxe_type_imports(); + + generate_rtti_decoration(f_consts); + generate_macro_decoration(f_consts); + indent(f_consts) << "class " << get_cap_name(program_name_) << "Constants {" << endl << endl; + indent_up(); + vector<t_const*>::iterator c_iter; + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + print_const_value(f_consts, + (*c_iter)->get_name(), + (*c_iter)->get_type(), + (*c_iter)->get_value(), + false); + } + indent_down(); + indent(f_consts) << "}" << endl; + f_consts.close(); +} + +void t_haxe_generator::print_const_value(std::ostream& out, + string name, + t_type* type, + t_const_value* value, + bool in_static, + bool defval) { + type = get_true_type(type); + + indent(out); + if (!defval) { + out << (in_static ? "var " : "public static inline var "); + } + if (type->is_base_type()) { + string v2 = render_const_value(out, name, type, value); + out << name; + if (!defval) { + out << ":" << type_name(type); + } + out << " = " << v2 << ";" << endl << endl; + } else if (type->is_enum()) { + out << name; + if (!defval) { + out << ":" << type_name(type); + } + out << " = " << value->get_integer() << ";" << endl << endl; + } else if (type->is_struct() || type->is_xception()) { + const vector<t_field*>& fields = ((t_struct*)type)->get_members(); + vector<t_field*>::const_iterator f_iter; + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + out << name << ":" << type_name(type) << " = new " << type_name(type, false, true) << "();" + << endl; + if (!in_static) { + indent(out) << "{" << endl; + indent_up(); + indent(out) << "new function() : Void {" << endl; + indent_up(); + } + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + t_type* field_type = NULL; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field_type = (*f_iter)->get_type(); + } + } + if (field_type == NULL) { + throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string(); + } + string val = render_const_value(out, name, field_type, v_iter->second); + indent(out) << name << "."; + out << v_iter->first->get_string() << " = " << val << ";" << endl; + } + if (!in_static) { + indent_down(); + indent(out) << "}();" << endl; + indent_down(); + indent(out) << "}" << endl; + } + out << endl; + } else if (type->is_map()) { + out << name; + if (!defval) { + out << ":" << type_name(type); + } + out << " = new " << type_name(type, false, true) << "();" << endl; + if (!in_static) { + indent(out) << "{" << endl; + indent_up(); + indent(out) << "new function() : Void {" << endl; + indent_up(); + } + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string key = render_const_value(out, name, ktype, v_iter->first); + string val = render_const_value(out, name, vtype, v_iter->second); + indent(out) << name << "[" << key << "] = " << val << ";" << endl; + } + if (!in_static) { + indent_down(); + indent(out) << "}();" << endl; + indent_down(); + indent(out) << "}" << endl; + } + out << endl; + } else if (type->is_list() || type->is_set()) { + out << name; + if (!defval) { + out << ":" << type_name(type); + } + out << " = new " << type_name(type, false, true) << "();" << endl; + if (!in_static) { + indent(out) << "{" << endl; + indent_up(); + indent(out) << "new function() : Void {" << endl; + indent_up(); + } + t_type* etype; + if (type->is_list()) { + etype = ((t_list*)type)->get_elem_type(); + } else { + etype = ((t_set*)type)->get_elem_type(); + } + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string val = render_const_value(out, name, etype, *v_iter); + indent(out) << name << "." << (type->is_list() ? "push" : "add") << "(" << val << ");" + << endl; + } + if (!in_static) { + indent_down(); + indent(out) << "}();" << endl; + indent_down(); + indent(out) << "}" << endl; + } + out << endl; + } else { + throw "compiler error: no const of type " + type->get_name(); + } +} + +string t_haxe_generator::render_const_value(ostream& out, + string name, + t_type* type, + t_const_value* value) { + (void)name; + type = get_true_type(type); + std::ostringstream render; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + render << '"' << get_escaped_string(value) << '"'; + break; + case t_base_type::TYPE_BOOL: + render << ((value->get_integer() > 0) ? "true" : "false"); + break; + case t_base_type::TYPE_I8: + render << "(byte)" << value->get_integer(); + break; + case t_base_type::TYPE_I16: + render << "(short)" << value->get_integer(); + break; + case t_base_type::TYPE_I32: + render << value->get_integer(); + break; + case t_base_type::TYPE_I64: + render << value->get_integer() << "L"; + break; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) { + render << "(double)" << value->get_integer(); + } else { + render << value->get_double(); + } + break; + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + render << value->get_integer(); + } else { + string t = tmp("tmp"); + print_const_value(out, t, type, value, true); + render << t; + } + + return render.str(); +} + +/** + * Generates a struct definition for a thrift data type. This is a class + * with data members, read(), write(), and an inner Isset class. + * + * @param tstruct The struct definition + */ +void t_haxe_generator::generate_struct(t_struct* tstruct) { + generate_haxe_struct(tstruct, false); +} + +/** + * Exceptions are structs, but they inherit from Exception + * + * @param tstruct The struct definition + */ +void t_haxe_generator::generate_xception(t_struct* txception) { + generate_haxe_struct(txception, true); +} + +/** + * Haxe struct definition. + * + * @param tstruct The struct definition + */ +void t_haxe_generator::generate_haxe_struct(t_struct* tstruct, bool is_exception, bool is_result) { + // Make output file + string f_struct_name = package_dir_ + "/" + get_cap_name(tstruct->get_name()) + ".hx"; + ofstream_with_content_based_conditional_update f_struct; + f_struct.open(f_struct_name.c_str()); + + f_struct << autogen_comment() << haxe_package() << ";" << endl; + + f_struct << endl; + + string imports; + + f_struct << haxe_type_imports() << haxe_thrift_imports() + << haxe_thrift_gen_imports(tstruct, imports) << endl; + + generate_haxe_struct_definition(f_struct, tstruct, is_exception, is_result); + + f_struct.close(); +} + +/** + * haxe struct definition. This has various parameters, as it could be + * generated standalone or inside another class as a helper. If it + * is a helper than it is a static class. + * + * @param tstruct The struct definition + * @param is_exception Is this an exception? + * @param in_class If inside a class, needs to be static class + * @param is_result If this is a result it needs a different writer + */ +void t_haxe_generator::generate_haxe_struct_definition(ostream& out, + t_struct* tstruct, + bool is_exception, + bool is_result) { + generate_haxe_doc(out, tstruct); + + string clsname = get_cap_name(tstruct->get_name()); + + generate_rtti_decoration(out); + generate_macro_decoration(out); + indent(out) << "class " << clsname << " "; + + if (is_exception) { + out << "extends TException "; + } + out << "implements TBase "; + + scope_up(out); + indent(out) << endl; + + indent(out) << "static var STRUCT_DESC = { new TStruct(\"" << tstruct->get_name() << "\"); };" + << endl; + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + indent(out) << "static var " << constant_name((*m_iter)->get_name()) + << "_FIELD_DESC = { new TField(\"" << (*m_iter)->get_name() << "\", " + << type_to_enum((*m_iter)->get_type()) << ", " << (*m_iter)->get_key() << "); };" + << endl; + } + out << endl; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + generate_haxe_doc(out, *m_iter); + // indent(out) << "private var _" << (*m_iter)->get_name() + " : " + + // type_name((*m_iter)->get_type()) << ";" << endl; + indent(out) << "@:isVar" << endl; + indent(out) << "public var " + << (*m_iter)->get_name() + "(get,set) : " + + get_cap_name(type_name((*m_iter)->get_type())) << ";" << endl; + } + + out << endl; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + indent(out) << "inline static var " << upcase_string((*m_iter)->get_name()) + << "_FIELD_ID : Int = " << (*m_iter)->get_key() << ";" << endl; + } + + out << endl; + + // Inner Isset class + if (members.size() > 0) { + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if (!type_can_be_null((*m_iter)->get_type())) { + indent(out) << "private var __isset_" << (*m_iter)->get_name() << " : Bool = false;" + << endl; + } + } + } + + out << endl; + + // Static initializer to populate global class to struct metadata map + if (false) { + // TODO: reactivate when needed + generate_haxe_meta_data_map(out, tstruct); + indent(out) << "{" << endl; + indent_up(); + indent(out) << "FieldMetaData.addStructMetaDataMap(" << type_name(tstruct) << ", metaDataMap);" + << endl; + indent_down(); + indent(out) << "}" << endl; + indent(out) << "}" << endl; + } + + // Default constructor + indent(out) << "public function new() {" << endl; + indent_up(); + if (is_exception) { + indent(out) << "super();" << endl; + } + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if ((*m_iter)->get_value() != NULL) { + indent(out) << "this." << (*m_iter)->get_name() << " = " + << (*m_iter)->get_value()->get_integer() << ";" << endl; + } + } + indent_down(); + indent(out) << "}" << endl << endl; + + generate_property_getters_setters(out, tstruct); + generate_generic_field_getters_setters(out, tstruct); + generate_generic_isset_method(out, tstruct); + + generate_haxe_struct_reader(out, tstruct); + if (is_result) { + generate_haxe_struct_result_writer(out, tstruct); + } else { + generate_haxe_struct_writer(out, tstruct); + } + generate_haxe_struct_tostring(out, tstruct); + generate_haxe_validator(out, tstruct); + scope_down(out); + out << endl; +} + +/** + * Generates a function to read all the fields of the struct. + * + * @param tstruct The struct definition + */ +void t_haxe_generator::generate_haxe_struct_reader(ostream& out, t_struct* tstruct) { + out << indent() << "public function read( iprot : TProtocol) : Void {" << endl; + indent_up(); + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + indent(out) << "iprot.IncrementRecursionDepth();" << endl; + indent(out) << "try" << endl; + scope_up(out); + + // Declare stack tmp variables and read struct header + out << indent() << "var field : TField;" << endl << indent() << "iprot.readStructBegin();" + << endl; + + // Loop over reading in fields + indent(out) << "while (true)" << endl; + scope_up(out); + + // Read beginning field marker + indent(out) << "field = iprot.readFieldBegin();" << endl; + + // Check for field STOP marker and break + indent(out) << "if (field.type == TType.STOP) { " << endl; + indent_up(); + indent(out) << "break;" << endl; + indent_down(); + indent(out) << "}" << endl; + + // Switch statement on the field we are reading + indent(out) << "switch (field.id)" << endl; + + scope_up(out); + + // Generate deserialization code for known cases + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + indent(out) << "case " << upcase_string((*f_iter)->get_name()) << "_FIELD_ID:" << endl; + indent_up(); + indent(out) << "if (field.type == " << type_to_enum((*f_iter)->get_type()) << ") {" << endl; + indent_up(); + + generate_deserialize_field(out, *f_iter, "this."); + generate_isset_set(out, *f_iter); + indent_down(); + out << indent() << "} else { " << endl << indent() << " TProtocolUtil.skip(iprot, field.type);" + << endl << indent() << "}" << endl; + indent_down(); + } + + // In the default case we skip the field + out << indent() << "default:" << endl << indent() << " TProtocolUtil.skip(iprot, field.type);" + << endl; + + scope_down(out); + + // Read field end marker + indent(out) << "iprot.readFieldEnd();" << endl; + + scope_down(out); + + out << indent() << "iprot.readStructEnd();" << endl << endl; + + indent(out) << "iprot.DecrementRecursionDepth();" << endl; + scope_down(out); + indent(out) << "catch(e:Dynamic)" << endl; + scope_up(out); + indent(out) << "iprot.DecrementRecursionDepth();" << endl; + indent(out) << "throw e;" << endl; + scope_down(out); + + // check for required fields of primitive type + // (which can be checked here but not in the general validate method) + out << endl << indent() << "// check for required fields of primitive type, which can't be " + "checked in the validate method" << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_REQUIRED && !type_can_be_null((*f_iter)->get_type())) { + out << indent() << "if (!__isset_" << (*f_iter)->get_name() << ") {" << endl << indent() + << " throw new TProtocolException(TProtocolException.UNKNOWN, \"Required field '" + << (*f_iter)->get_name() + << "' was not found in serialized data! Struct: \" + toString());" << endl << indent() + << "}" << endl; + } + } + + // performs various checks (e.g. check that all required fields are set) + indent(out) << "validate();" << endl; + + indent_down(); + out << indent() << "}" << endl << endl; +} + +// generates haxe method to perform various checks +// (e.g. check that all required fields are set) +void t_haxe_generator::generate_haxe_validator(ostream& out, t_struct* tstruct) { + indent(out) << "public function validate() : Void {" << endl; + indent_up(); + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + out << indent() << "// check for required fields" << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_REQUIRED) { + if (type_can_be_null((*f_iter)->get_type())) { + indent(out) << "if (" << (*f_iter)->get_name() << " == null) {" << endl; + indent(out) + << " throw new TProtocolException(TProtocolException.UNKNOWN, \"Required field '" + << (*f_iter)->get_name() << "' was not present! Struct: \" + toString());" << endl; + indent(out) << "}" << endl; + } else { + indent(out) << "// alas, we cannot check '" << (*f_iter)->get_name() + << "' because it's a primitive." << endl; + } + } + } + + // check that fields of type enum have valid values + out << indent() << "// check that fields of type enum have valid values" << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = (*f_iter); + t_type* type = field->get_type(); + // if field is an enum, check that its value is valid + if (type->is_enum()) { + indent(out) << "if (" << generate_isset_check(field) << " && !" + << get_cap_name(get_enum_class_name(type)) << ".VALID_VALUES.contains(" + << field->get_name() << ")){" << endl; + indent_up(); + indent(out) << "throw new TProtocolException(TProtocolException.UNKNOWN, \"The field '" + << field->get_name() << "' has been assigned the invalid value \" + " + << field->get_name() << ");" << endl; + indent_down(); + indent(out) << "}" << endl; + } + } + + indent_down(); + indent(out) << "}" << endl << endl; +} + +/** + * Generates a function to write all the fields of the struct + * + * @param tstruct The struct definition + */ +void t_haxe_generator::generate_haxe_struct_writer(ostream& out, t_struct* tstruct) { + out << indent() << "public function write(oprot:TProtocol) : Void {" << endl; + indent_up(); + + string name = tstruct->get_name(); + const vector<t_field*>& fields = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator f_iter; + + // performs various checks (e.g. check that all required fields are set) + indent(out) << "validate();" << endl; + indent(out) << "oprot.IncrementRecursionDepth();" << endl; + indent(out) << "try" << endl; + scope_up(out); + + indent(out) << "oprot.writeStructBegin(STRUCT_DESC);" << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + bool could_be_unset = (*f_iter)->get_req() == t_field::T_OPTIONAL; + if (could_be_unset) { + indent(out) << "if (" << generate_isset_check(*f_iter) << ") {" << endl; + indent_up(); + } + bool null_allowed = type_can_be_null((*f_iter)->get_type()); + if (null_allowed) { + out << indent() << "if (this." << (*f_iter)->get_name() << " != null) {" << endl; + indent_up(); + } + + indent(out) << "oprot.writeFieldBegin(" << constant_name((*f_iter)->get_name()) + << "_FIELD_DESC);" << endl; + + // Write field contents + generate_serialize_field(out, *f_iter, "this."); + + // Write field closer + indent(out) << "oprot.writeFieldEnd();" << endl; + + if (null_allowed) { + indent_down(); + indent(out) << "}" << endl; + } + if (could_be_unset) { + indent_down(); + indent(out) << "}" << endl; + } + } + + indent(out) << "oprot.writeFieldStop();" << endl; + indent(out) << "oprot.writeStructEnd();" << endl; + + indent(out) << "oprot.DecrementRecursionDepth();" << endl; + scope_down(out); + indent(out) << "catch(e:Dynamic)" << endl; + scope_up(out); + indent(out) << "oprot.DecrementRecursionDepth();" << endl; + indent(out) << "throw e;" << endl; + scope_down(out); + + indent_down(); + out << indent() << "}" << endl << endl; +} + +/** + * Generates a function to write all the fields of the struct, + * which is a function result. These fields are only written + * if they are set in the Isset array, and only one of them + * can be set at a time. + * + * @param tstruct The struct definition + */ +void t_haxe_generator::generate_haxe_struct_result_writer(ostream& out, t_struct* tstruct) { + out << indent() << "public function write(oprot:TProtocol) : Void {" << endl; + indent_up(); + + string name = tstruct->get_name(); + const vector<t_field*>& fields = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator f_iter; + + indent(out) << "oprot.IncrementRecursionDepth();" << endl; + indent(out) << "try" << endl; + scope_up(out); + + indent(out) << "oprot.writeStructBegin(STRUCT_DESC);" << endl; + + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + out << endl << indent() << "if "; + } else { + out << " else if "; + } + + out << "(this." << generate_isset_check(*f_iter) << ") {" << endl; + + indent_up(); + + indent(out) << "oprot.writeFieldBegin(" << constant_name((*f_iter)->get_name()) + << "_FIELD_DESC);" << endl; + + // Write field contents + generate_serialize_field(out, *f_iter, "this."); + + // Write field closer + indent(out) << "oprot.writeFieldEnd();" << endl; + + indent_down(); + indent(out) << "}"; + } + + indent(out) << endl; + indent(out) << "oprot.writeFieldStop();" << endl; + indent(out) << "oprot.writeStructEnd();" << endl; + + indent(out) << "oprot.DecrementRecursionDepth();" << endl; + scope_down(out); + indent(out) << "catch(e:Dynamic)" << endl; + scope_up(out); + indent(out) << "oprot.DecrementRecursionDepth();" << endl; + indent(out) << "throw e;" << endl; + scope_down(out); + + indent_down(); + out << indent() << "}" << endl << endl; +} + +void t_haxe_generator::generate_reflection_getters(ostringstream& out, + t_type* type, + string field_name, + string cap_name) { + (void)type; + (void)cap_name; + indent(out) << "case " << upcase_string(field_name) << "_FIELD_ID:" << endl; + indent_up(); + indent(out) << "return this." << field_name << ";" << endl; + indent_down(); +} + +void t_haxe_generator::generate_reflection_setters(ostringstream& out, + t_type* type, + string field_name, + string cap_name) { + (void)type; + (void)cap_name; + indent(out) << "case " << upcase_string(field_name) << "_FIELD_ID:" << endl; + indent_up(); + indent(out) << "if (value == null) {" << endl; + indent(out) << " unset" << get_cap_name(field_name) << "();" << endl; + indent(out) << "} else {" << endl; + indent(out) << " this." << field_name << " = value;" << endl; + indent(out) << "}" << endl << endl; + + indent_down(); +} + +void t_haxe_generator::generate_generic_field_getters_setters(std::ostream& out, + t_struct* tstruct) { + + std::ostringstream getter_stream; + std::ostringstream setter_stream; + + // build up the bodies of both the getter and setter at once + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = *f_iter; + t_type* type = get_true_type(field->get_type()); + std::string field_name = field->get_name(); + std::string cap_name = get_cap_name(field_name); + + indent_up(); + generate_reflection_setters(setter_stream, type, field_name, cap_name); + generate_reflection_getters(getter_stream, type, field_name, cap_name); + indent_down(); + } + + // create the setter + indent(out) << "public function setFieldValue(fieldID : Int, value : Dynamic) : Void {" << endl; + indent_up(); + + if (fields.size() > 0) { + indent(out) << "switch (fieldID) {" << endl; + out << setter_stream.str(); + indent(out) << "default:" << endl; + indent(out) << " throw new ArgumentError(\"Field \" + fieldID + \" doesn't exist!\");" << endl; + indent(out) << "}" << endl; + } else { + indent(out) << "throw new ArgumentError(\"Field \" + fieldID + \" doesn't exist!\");" << endl; + } + + indent_down(); + indent(out) << "}" << endl << endl; + + // create the getter + indent(out) << "public function getFieldValue(fieldID : Int) : Dynamic {" << endl; + indent_up(); + + if (fields.size() > 0) { + indent(out) << "switch (fieldID) {" << endl; + out << getter_stream.str(); + indent(out) << "default:" << endl; + indent(out) << " throw new ArgumentError(\"Field \" + fieldID + \" doesn't exist!\");" << endl; + indent(out) << "}" << endl; + } else { + indent(out) << "throw new ArgumentError(\"Field \" + fieldID + \" doesn't exist!\");" << endl; + } + + indent_down(); + + indent(out) << "}" << endl << endl; +} + +// Creates a generic isSet method that takes the field number as argument +void t_haxe_generator::generate_generic_isset_method(std::ostream& out, t_struct* tstruct) { + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + // create the isSet method + indent(out) << "// Returns true if field corresponding to fieldID is set (has been assigned a " + "value) and false otherwise" << endl; + indent(out) << "public function isSet(fieldID : Int) : Bool {" << endl; + indent_up(); + if (fields.size() > 0) { + indent(out) << "switch (fieldID) {" << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = *f_iter; + indent(out) << "case " << upcase_string(field->get_name()) << "_FIELD_ID:" << endl; + indent_up(); + indent(out) << "return " << generate_isset_check(field) << ";" << endl; + indent_down(); + } + + indent(out) << "default:" << endl; + indent(out) << " throw new ArgumentError(\"Field \" + fieldID + \" doesn't exist!\");" << endl; + indent(out) << "}" << endl; + } else { + indent(out) << "throw new ArgumentError(\"Field \" + fieldID + \" doesn't exist!\");" << endl; + } + + indent_down(); + indent(out) << "}" << endl << endl; +} + +/** + * Generates a set of property setters/getters for the given struct. + * + * @param tstruct The struct definition + */ +void t_haxe_generator::generate_property_getters_setters(ostream& out, t_struct* tstruct) { + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = *f_iter; + t_type* type = get_true_type(field->get_type()); + std::string field_name = field->get_name(); + std::string cap_name = get_cap_name(field_name); + + // Simple getter + generate_haxe_doc(out, field); + indent(out) << "public function get_" << field_name << "() : " << get_cap_name(type_name(type)) + << " {" << endl; + indent_up(); + indent(out) << "return this." << field_name << ";" << endl; + indent_down(); + indent(out) << "}" << endl << endl; + + // Simple setter + generate_haxe_doc(out, field); + indent(out) << "public function set_" << field_name << "(" << field_name << ":" + << get_cap_name(type_name(type)) << ") : " << get_cap_name(type_name(type)) << " {" + << endl; + indent_up(); + indent(out) << "this." << field_name << " = " << field_name << ";" << endl; + generate_isset_set(out, field); + indent(out) << "return this." << field_name << ";" << endl; + + indent_down(); + indent(out) << "}" << endl << endl; + + // Unsetter + indent(out) << "public function unset" << cap_name << "() : Void {" << endl; + indent_up(); + if (type_can_be_null(type)) { + indent(out) << "this." << field_name << " = null;" << endl; + } else { + indent(out) << "this.__isset_" << field_name << " = false;" << endl; + } + indent_down(); + indent(out) << "}" << endl << endl; + + // isSet method + indent(out) << "// Returns true if field " << field_name + << " is set (has been assigned a value) and false otherwise" << endl; + indent(out) << "public function is" << get_cap_name("set") << cap_name << "() : Bool {" << endl; + indent_up(); + if (type_can_be_null(type)) { + indent(out) << "return this." << field_name << " != null;" << endl; + } else { + indent(out) << "return this.__isset_" << field_name << ";" << endl; + } + indent_down(); + indent(out) << "}" << endl << endl; + } +} + +/** + * Generates a toString() method for the given struct + * + * @param tstruct The struct definition + */ +void t_haxe_generator::generate_haxe_struct_tostring(ostream& out, t_struct* tstruct) { + out << indent() << "public " + << "function toString() : String {" << endl; + indent_up(); + + out << indent() << "var ret : String = \"" << tstruct->get_name() << "(\";" << endl; + out << indent() << "var first : Bool = true;" << endl << endl; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + bool could_be_unset = (*f_iter)->get_req() == t_field::T_OPTIONAL; + if (could_be_unset) { + indent(out) << "if (" << generate_isset_check(*f_iter) << ") {" << endl; + indent_up(); + } + + t_field* field = (*f_iter); + + if (!first) { + indent(out) << "if (!first) ret += \", \";" << endl; + } + indent(out) << "ret += \"" << (*f_iter)->get_name() << ":\";" << endl; + bool can_be_null = type_can_be_null(field->get_type()); + if (can_be_null) { + indent(out) << "if (this." << (*f_iter)->get_name() << " == null) {" << endl; + indent(out) << " ret += \"null\";" << endl; + indent(out) << "} else {" << endl; + indent_up(); + } + + if (field->get_type()->is_binary()) { + indent(out) << " ret += \"BINARY\";" << endl; + } else if (field->get_type()->is_enum()) { + indent(out) << "var " << field->get_name() + << "_name : String = " << get_cap_name(get_enum_class_name(field->get_type())) + << ".VALUES_TO_NAMES[this." << (*f_iter)->get_name() << "];" << endl; + indent(out) << "if (" << field->get_name() << "_name != null) {" << endl; + indent(out) << " ret += " << field->get_name() << "_name;" << endl; + indent(out) << " ret += \" (\";" << endl; + indent(out) << "}" << endl; + indent(out) << "ret += this." << field->get_name() << ";" << endl; + indent(out) << "if (" << field->get_name() << "_name != null) {" << endl; + indent(out) << " ret += \")\";" << endl; + indent(out) << "}" << endl; + } else { + indent(out) << "ret += this." << (*f_iter)->get_name() << ";" << endl; + } + + if (can_be_null) { + indent_down(); + indent(out) << "}" << endl; + } + indent(out) << "first = false;" << endl; + + if (could_be_unset) { + indent_down(); + indent(out) << "}" << endl; + } + first = false; + } + out << indent() << "ret += \")\";" << endl << indent() << "return ret;" << endl; + + indent_down(); + indent(out) << "}" << endl << endl; +} + +/** + * Generates a static map with meta data to store information such as fieldID to + * fieldName mapping + * + * @param tstruct The struct definition + */ +void t_haxe_generator::generate_haxe_meta_data_map(ostream& out, t_struct* tstruct) { + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + // Static Map with fieldID -> FieldMetaData mappings + indent(out) << "inline static var metaDataMap : IntMap = new IntMap();" << endl; + + if (fields.size() > 0) { + // Populate map + scope_up(out); + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = *f_iter; + std::string field_name = field->get_name(); + indent(out) << "metaDataMap[" << upcase_string(field_name) + << "_FIELD_ID] = new FieldMetaData(\"" << field_name << "\", "; + + // Set field requirement type (required, optional, etc.) + if (field->get_req() == t_field::T_REQUIRED) { + out << "TFieldRequirementType.REQUIRED, "; + } else if (field->get_req() == t_field::T_OPTIONAL) { + out << "TFieldRequirementType.OPTIONAL, "; + } else { + out << "TFieldRequirementType.DEFAULT, "; + } + + // Create value meta data + generate_field_value_meta_data(out, field->get_type()); + out << ");" << endl; + } + scope_down(out); + } +} + +/** + * Returns a string with the haxe representation of the given thrift type + * (e.g. for the type struct it returns "TType.STRUCT") + */ +std::string t_haxe_generator::get_haxe_type_string(t_type* type) { + if (type->is_list()) { + return "TType.LIST"; + } else if (type->is_map()) { + return "TType.MAP"; + } else if (type->is_set()) { + return "TType.SET"; + } else if (type->is_struct() || type->is_xception()) { + return "TType.STRUCT"; + } else if (type->is_enum()) { + return "TType.I32"; + } else if (type->is_typedef()) { + return get_haxe_type_string(((t_typedef*)type)->get_type()); + } else if (type->is_base_type()) { + switch (((t_base_type*)type)->get_base()) { + case t_base_type::TYPE_VOID: + return "TType.VOID"; + break; + case t_base_type::TYPE_STRING: + return "TType.STRING"; + break; + case t_base_type::TYPE_BOOL: + return "TType.BOOL"; + break; + case t_base_type::TYPE_I8: + return "TType.BYTE"; + break; + case t_base_type::TYPE_I16: + return "TType.I16"; + break; + case t_base_type::TYPE_I32: + return "TType.I32"; + break; + case t_base_type::TYPE_I64: + return "TType.I64"; + break; + case t_base_type::TYPE_DOUBLE: + return "TType.DOUBLE"; + break; + default: + throw std::runtime_error("Unknown thrift type \"" + type->get_name() + + "\" passed to t_haxe_generator::get_haxe_type_string!"); + break; // This should never happen! + } + } else { + throw std::runtime_error( + "Unknown thrift type \"" + type->get_name() + + "\" passed to t_haxe_generator::get_haxe_type_string!"); // This should never happen! + } +} + +void t_haxe_generator::generate_field_value_meta_data(std::ostream& out, t_type* type) { + out << endl; + indent_up(); + indent_up(); + if (type->is_struct()) { + indent(out) << "new StructMetaData(TType.STRUCT, " << type_name(type); + } else if (type->is_container()) { + if (type->is_list()) { + indent(out) << "new ListMetaData(TType.LIST, "; + t_type* elem_type = ((t_list*)type)->get_elem_type(); + generate_field_value_meta_data(out, elem_type); + } else if (type->is_set()) { + indent(out) << "new SetMetaData(TType.SET, "; + t_type* elem_type = ((t_list*)type)->get_elem_type(); + generate_field_value_meta_data(out, elem_type); + } else { // map + indent(out) << "new MapMetaData(TType.MAP, "; + t_type* key_type = ((t_map*)type)->get_key_type(); + t_type* val_type = ((t_map*)type)->get_val_type(); + generate_field_value_meta_data(out, key_type); + out << ", "; + generate_field_value_meta_data(out, val_type); + } + } else { + indent(out) << "new FieldValueMetaData(" << get_haxe_type_string(type); + } + out << ")"; + indent_down(); + indent_down(); +} + +/** + * Generates a thrift service. In C++, this comprises an entirely separate + * header and source file. The header file defines the methods and includes + * the data types defined in the main header file, and the implementation + * file contains implementations of the basic printer and default interfaces. + * + * @param tservice The service definition + */ +void t_haxe_generator::generate_service(t_service* tservice) { + // Make interface file + string f_service_name = package_dir_ + "/" + get_cap_name(service_name_) + ".hx"; + f_service_.open(f_service_name.c_str()); + + f_service_ << autogen_comment() << haxe_package() << ";" << endl; + + f_service_ << endl << haxe_type_imports() << haxe_thrift_imports() + << haxe_thrift_gen_imports(tservice); + + if (tservice->get_extends() != NULL) { + t_type* parent = tservice->get_extends(); + string parent_namespace = parent->get_program()->get_namespace("haxe"); + if (!parent_namespace.empty() && parent_namespace != package_name_) { + f_service_ << "import " << type_name(parent) << ";" << endl; + } + } + + f_service_ << endl; + + generate_service_interface(tservice); + + f_service_.close(); + + // Now make the implementation/client file + f_service_name = package_dir_ + "/" + get_cap_name(service_name_) + "Impl.hx"; + f_service_.open(f_service_name.c_str()); + + f_service_ << autogen_comment() << haxe_package() << ";" << endl << endl << haxe_type_imports() + << haxe_thrift_imports() << haxe_thrift_gen_imports(tservice) << endl; + + if (tservice->get_extends() != NULL) { + t_type* parent = tservice->get_extends(); + string parent_namespace = parent->get_program()->get_namespace("haxe"); + if (!parent_namespace.empty() && parent_namespace != package_name_) { + f_service_ << "import " << type_name(parent) << "Impl;" << endl; + } + } + + f_service_ << endl; + + generate_service_client(tservice); + + f_service_.close(); + + // Now make the helper class files + generate_service_helpers(tservice); + + // Now make the processor/server file + f_service_name = package_dir_ + "/" + get_cap_name(service_name_) + "Processor.hx"; + f_service_.open(f_service_name.c_str()); + + f_service_ << autogen_comment() << haxe_package() << ";" << endl << endl << haxe_type_imports() + << haxe_thrift_imports() << haxe_thrift_gen_imports(tservice) << endl; + + if (!package_name_.empty()) { + f_service_ << "import " << package_name_ << ".*;" << endl; + f_service_ << "import " << package_name_ << "." << get_cap_name(service_name_).c_str() + << "Impl;" << endl; + f_service_ << endl; + } + + generate_service_server(tservice); + + f_service_.close(); +} + +/** + * Generates the code snippet for the onSuccess callbacks + * + * @param tfunction The service function to generate code for. + */ +string t_haxe_generator::generate_service_method_onsuccess(t_function* tfunction, + bool as_type, + bool omit_name) { + if (tfunction->is_oneway()) { + return ""; + } + + string name = ""; + if (!omit_name) { + name = "onSuccess"; + if (as_type) { + name += " : "; + } + } + + if (tfunction->get_returntype()->is_void()) { + if (as_type) { + return name + "Void->Void = null"; + } else { + return name + "() : Void"; + } + } + + if (as_type) { + return name + type_name(tfunction->get_returntype()) + "->Void = null"; + } else { + return name + "( retval : " + type_name(tfunction->get_returntype()) + ")"; + } +} + +/** + * Generates a service method header + * + * @param tfunction The service function to generate code for. + */ +void t_haxe_generator::generate_service_method_signature(t_function* tfunction, bool is_interface) { + if (callbacks_) { + generate_service_method_signature_callback(tfunction, is_interface); + } else { + generate_service_method_signature_normal(tfunction, is_interface); + } +} + +/** + * Generates a service method header in "normal" style + * + * @param tfunction The service function to generate code for. + */ +void t_haxe_generator::generate_service_method_signature_normal(t_function* tfunction, + bool is_interface) { + if (is_interface) { + indent(f_service_) << function_signature_normal(tfunction) << ";" << endl << endl; + } else { + indent(f_service_) << "public " << function_signature_normal(tfunction) << " {" << endl; + } +} + +/** + * Generates a service method header in "callback" style + * + * @param tfunction The service function to generate code for. + */ +void t_haxe_generator::generate_service_method_signature_callback(t_function* tfunction, + bool is_interface) { + if (!tfunction->is_oneway()) { + std::string on_success_impl = generate_service_method_onsuccess(tfunction, false, false); + indent(f_service_) << "// function onError(Dynamic) : Void;" << endl; + indent(f_service_) << "// function " << on_success_impl.c_str() << ";" << endl; + } + + if (is_interface) { + indent(f_service_) << function_signature_callback(tfunction) << ";" << endl << endl; + } else { + indent(f_service_) << "public " << function_signature_callback(tfunction) << " {" << endl; + } +} + +/** + * Generates a service interface definition. + * + * @param tservice The service to generate a header definition for + */ +void t_haxe_generator::generate_service_interface(t_service* tservice) { + string extends_iface = ""; + if (tservice->get_extends() != NULL) { + extends_iface = " extends " + tservice->get_extends()->get_name(); + } + + generate_haxe_doc(f_service_, tservice); + // generate_rtti_decoration(f_service_); - not yet, because of + // https://github.com/HaxeFoundation/haxe/issues/3626 + generate_macro_decoration(f_service_); + f_service_ << indent() << "interface " << get_cap_name(service_name_) << extends_iface << " {" + << endl << endl; + indent_up(); + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_haxe_doc(f_service_, *f_iter); + generate_service_method_signature(*f_iter, true); + } + indent_down(); + f_service_ << indent() << "}" << endl << endl; +} + +/** + * Generates structs for all the service args and return types + * + * @param tservice The service + */ +void t_haxe_generator::generate_service_helpers(t_service* tservice) { + f_service_ << endl << endl; + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* ts = (*f_iter)->get_arglist(); + generate_haxe_struct(ts, false); + generate_function_helpers(*f_iter); + } +} + +/** + * Generates a service client definition. + * + * @param tservice The service to generate a server for. + */ +void t_haxe_generator::generate_service_client(t_service* tservice) { + string extends = ""; + string extends_client = ""; + if (tservice->get_extends() != NULL) { + extends = get_cap_name(tservice->get_extends()->get_name()); + extends_client = " extends " + extends + "Impl"; + } + + generate_rtti_decoration(f_service_); + // build macro is inherited from interface + indent(f_service_) << "class " << get_cap_name(service_name_) << "Impl" << extends_client + << " implements " << get_cap_name(service_name_) << " {" << endl << endl; + indent_up(); + + indent(f_service_) << "public function new( iprot : TProtocol, oprot : TProtocol = null)" << endl; + scope_up(f_service_); + if (extends.empty()) { + f_service_ << indent() << "iprot_ = iprot;" << endl; + f_service_ << indent() << "if (oprot == null) {" << endl; + indent_up(); + f_service_ << indent() << "oprot_ = iprot;" << endl; + indent_down(); + f_service_ << indent() << "} else {" << endl; + indent_up(); + f_service_ << indent() << "oprot_ = oprot;" << endl; + indent_down(); + f_service_ << indent() << "}" << endl; + } else { + f_service_ << indent() << "super(iprot, oprot);" << endl; + } + scope_down(f_service_); + f_service_ << endl; + + if (extends.empty()) { + f_service_ << indent() << "private var iprot_ : TProtocol;" << endl << indent() + << "private var oprot_ : TProtocol;" << endl << indent() + << "private var seqid_ : Int;" << endl << endl; + + indent(f_service_) << "public function getInputProtocol() : TProtocol" << endl; + scope_up(f_service_); + indent(f_service_) << "return this.iprot_;" << endl; + scope_down(f_service_); + f_service_ << endl; + + indent(f_service_) << "public function getOutputProtocol() : TProtocol" << endl; + scope_up(f_service_); + indent(f_service_) << "return this.oprot_;" << endl; + scope_down(f_service_); + f_service_ << endl; + } + + // Generate client method implementations + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + string funname = (*f_iter)->get_name(); + + // Open function + generate_service_method_signature(*f_iter, false); + + indent_up(); + + // Get the struct of function call params + t_struct* arg_struct = (*f_iter)->get_arglist(); + + string argsname = get_cap_name((*f_iter)->get_name() + "_args"); + vector<t_field*>::const_iterator fld_iter; + const vector<t_field*>& fields = arg_struct->get_members(); + + // Serialize the request + string calltype = (*f_iter)->is_oneway() ? "ONEWAY" : "CALL"; + f_service_ << indent() << "oprot_.writeMessageBegin(new TMessage(\"" << funname + << "\", TMessageType." << calltype << ", seqid_));" << endl << indent() + << "var args : " << argsname << " = new " << argsname << "();" << endl; + + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + f_service_ << indent() << "args." << (*fld_iter)->get_name() << " = " + << (*fld_iter)->get_name() << ";" << endl; + } + + f_service_ << indent() << "args.write(oprot_);" << endl << indent() + << "oprot_.writeMessageEnd();" << endl; + + if (!((*f_iter)->is_oneway() || (*f_iter)->get_returntype()->is_void())) { + f_service_ << indent() << "var retval : " << type_name((*f_iter)->get_returntype()) << ";" + << endl; + } + + if ((*f_iter)->is_oneway()) { + f_service_ << indent() << "oprot_.getTransport().flush();" << endl; + } else { + indent(f_service_) << "oprot_.getTransport().flush(function(error:Dynamic) : Void {" << endl; + indent_up(); + if (callbacks_) { + indent(f_service_) << "try {" << endl; + indent_up(); + } + string resultname = get_cap_name((*f_iter)->get_name() + "_result"); + indent(f_service_) << "if (error != null) {" << endl; + indent_up(); + if (callbacks_) { + indent(f_service_) << "if (onError != null) onError(error);" << endl; + indent(f_service_) << "return;" << endl; + } else { + indent(f_service_) << "throw error;" << endl; + } + indent_down(); + indent(f_service_) << "}" << endl; + indent(f_service_) << "var msg : TMessage = iprot_.readMessageBegin();" << endl; + indent(f_service_) << "if (msg.type == TMessageType.EXCEPTION) {" << endl; + indent_up(); + indent(f_service_) << "var x = TApplicationException.read(iprot_);" << endl; + indent(f_service_) << "iprot_.readMessageEnd();" << endl; + if (callbacks_) { + indent(f_service_) << "if (onError != null) onError(x);" << endl; + indent(f_service_) << "return;" << endl; + } else { + indent(f_service_) << "throw x;" << endl; + } + indent_down(); + indent(f_service_) << "}" << endl; + indent(f_service_) << "var result : " << resultname << " = new " << resultname << "();" + << endl; + indent(f_service_) << "result.read(iprot_);" << endl; + indent(f_service_) << "iprot_.readMessageEnd();" << endl; + + // Careful, only return _result if not a void function + if (!(*f_iter)->get_returntype()->is_void()) { + indent(f_service_) << "if (result." << generate_isset_check("success") << ") {" << endl; + indent_up(); + if (callbacks_) { + indent(f_service_) << "if (onSuccess != null) onSuccess(result.success);" << endl; + indent(f_service_) << "return;" << endl; + } else { + indent(f_service_) << "retval = result.success;" << endl; + indent(f_service_) << "return;" << endl; + } + indent_down(); + indent(f_service_) << "}" << endl; + } + + t_struct* xs = (*f_iter)->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + indent(f_service_) << "if (result." << (*x_iter)->get_name() << " != null) {" << endl; + indent_up(); + if (callbacks_) { + indent(f_service_) << "if (onError != null) onError(result." << (*x_iter)->get_name() + << ");" << endl; + indent(f_service_) << "return;" << endl; + } else { + indent(f_service_) << "throw result." << (*x_iter)->get_name() << ";" << endl; + } + indent_down(); + indent(f_service_) << "}" << endl; + } + + // If you get here it's an exception, unless a void function + if ((*f_iter)->get_returntype()->is_void()) { + if (callbacks_) { + indent(f_service_) << "if (onSuccess != null) onSuccess();" << endl; + } + indent(f_service_) << "return;" << endl; + } else { + if (callbacks_) { + indent(f_service_) << "if (onError != null)" << endl; + indent_up(); + indent(f_service_) + << "onError( new TApplicationException(TApplicationException.MISSING_RESULT," << endl; + indent(f_service_) << " \"" << (*f_iter)->get_name() + << " failed: unknown result\"));" << endl; + indent_down(); + } else { + indent(f_service_) + << "throw new TApplicationException(TApplicationException.MISSING_RESULT," << endl; + indent(f_service_) << " \"" << (*f_iter)->get_name() + << " failed: unknown result\");" << endl; + } + } + + if (callbacks_) { + indent_down(); + indent(f_service_) << "} catch( e : TException) {" << endl; + indent_up(); + indent(f_service_) << "if (onError != null) onError(e);" << endl; + indent_down(); + indent(f_service_) << "}" << endl; + } + + indent_down(); + indent(f_service_) << "});" << endl; + } + + if (!((*f_iter)->is_oneway() || (*f_iter)->get_returntype()->is_void())) { + f_service_ << indent() << "return retval;" << endl; + } + + // Close function + scope_down(f_service_); + f_service_ << endl; + } + + indent_down(); + indent(f_service_) << "}" << endl; +} + +/** + * Generates a service server definition. + * + * @param tservice The service to generate a server for. + */ +void t_haxe_generator::generate_service_server(t_service* tservice) { + // Generate the dispatch methods + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + // Extends stuff + string extends = ""; + string extends_processor = ""; + if (tservice->get_extends() != NULL) { + extends = get_cap_name(type_name(tservice->get_extends())); + extends_processor = " extends " + extends + "Processor"; + } + + // Generate the header portion + generate_rtti_decoration(f_service_); + generate_macro_decoration(f_service_); + indent(f_service_) << "class " << get_cap_name(service_name_) << "Processor" << extends_processor + << " implements TProcessor {" << endl << endl; + indent_up(); + + f_service_ << indent() << "private var " << get_cap_name(service_name_) + << "_iface_ : " << get_cap_name(service_name_) << ";" << endl; + + if (extends.empty()) { + f_service_ << indent() + << "private var PROCESS_MAP = new StringMap< Int->TProtocol->TProtocol->Void >();" + << endl; + } + + f_service_ << endl; + + indent(f_service_) << "public function new( iface : " << get_cap_name(service_name_) << ")" + << endl; + scope_up(f_service_); + if (!extends.empty()) { + f_service_ << indent() << "super(iface);" << endl; + } + f_service_ << indent() << get_cap_name(service_name_) << "_iface_ = iface;" << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + f_service_ << indent() << "PROCESS_MAP.set(\"" << (*f_iter)->get_name() << "\", " + << (*f_iter)->get_name() << "());" << endl; + } + + scope_down(f_service_); + f_service_ << endl; + + // Generate the server implementation + string override = ""; + if (tservice->get_extends() != NULL) { + override = "override "; + } + indent(f_service_) << override + << "public function process( iprot : TProtocol, oprot : TProtocol) : Bool" + << endl; + scope_up(f_service_); + + f_service_ << indent() << "var msg : TMessage = iprot.readMessageBegin();" << endl; + + // TODO(mcslee): validate message, was the seqid etc. legit? + // AS- If all method is oneway: + // do you have an oprot? + // do you you need nullcheck? + f_service_ + << indent() << "var fn = PROCESS_MAP.get(msg.name);" << endl << indent() + << "if (fn == null) {" << endl << indent() << " TProtocolUtil.skip(iprot, TType.STRUCT);" + << endl << indent() << " iprot.readMessageEnd();" << endl << indent() + << " var x = new TApplicationException(TApplicationException.UNKNOWN_METHOD, \"Invalid " + "method name: '\"+msg.name+\"'\");" << endl << indent() + << " oprot.writeMessageBegin(new TMessage(msg.name, TMessageType.EXCEPTION, msg.seqid));" + << endl << indent() << " x.write(oprot);" << endl << indent() << " oprot.writeMessageEnd();" + << endl << indent() << " oprot.getTransport().flush();" << endl << indent() + << " return true;" << endl << indent() << "}" << endl << indent() + << "fn( msg.seqid, iprot, oprot);" << endl; + + f_service_ << indent() << "return true;" << endl; + + scope_down(f_service_); + f_service_ << endl; + + // Generate the process subfunctions + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_process_function(tservice, *f_iter); + } + + indent_down(); + indent(f_service_) << "}" << endl << endl; +} + +/** + * Generates a struct and helpers for a function. + * + * @param tfunction The function + */ +void t_haxe_generator::generate_function_helpers(t_function* tfunction) { + if (tfunction->is_oneway()) { + return; + } + + string resultname = get_cap_name(tfunction->get_name() + "_result"); + t_struct result(program_, resultname); + t_field success(tfunction->get_returntype(), "success", 0); + if (!tfunction->get_returntype()->is_void()) { + result.append(&success); + } + + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& fields = xs->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + result.append(*f_iter); + } + + generate_haxe_struct(&result, false, true); +} + +/** + * Generates a process function definition. + * + * @param tfunction The function to write a dispatcher for + */ +void t_haxe_generator::generate_process_function(t_service* tservice, t_function* tfunction) { + (void)tservice; + // Open class + indent(f_service_) << "private function " << tfunction->get_name() + << "() : Int->TProtocol->TProtocol->Void {" << endl; + indent_up(); + + // Open function + indent(f_service_) << "return function( seqid : Int, iprot : TProtocol, oprot : TProtocol) : Void" + << endl; + scope_up(f_service_); + + string argsname = get_cap_name(tfunction->get_name() + "_args"); + string resultname = get_cap_name(tfunction->get_name() + "_result"); + + f_service_ << indent() << "var args : " << argsname << " = new " << argsname << "();" << endl + << indent() << "args.read(iprot);" << endl << indent() << "iprot.readMessageEnd();" + << endl; + + t_struct* xs = tfunction->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + + // Declare result for non oneway function + if (!tfunction->is_oneway()) { + f_service_ << indent() << "var result : " << resultname << " = new " << resultname << "();" + << endl; + } + + // Try block for any function to catch (defined or undefined) exceptions + f_service_ << indent() << "try {" << endl; + indent_up(); + + if (callbacks_) { + // callback function style onError/onSuccess + + // Generate the function call + t_struct* arg_struct = tfunction->get_arglist(); + const std::vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + + f_service_ << indent(); + f_service_ << get_cap_name(service_name_) << "_iface_." << tfunction->get_name() << "("; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << "args." << (*f_iter)->get_name(); + } + + if (tfunction->is_oneway()) { + f_service_ << ");" << endl; + } else { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + string on_success = generate_service_method_onsuccess(tfunction, false, true); + indent_up(); + f_service_ << endl; + indent(f_service_) << "null, // errors are thrown by the handler" << endl; + if (tfunction->get_returntype()->is_void()) { + indent(f_service_) << "null); // no retval" << endl; + } else { + indent(f_service_) << "function" << on_success.c_str() << " {" << endl; + if (!tfunction->get_returntype()->is_void()) { + indent_up(); + indent(f_service_) << "result.success = retval;" << endl; + indent_down(); + } + indent(f_service_) << "});" << endl; + } + indent_down(); + } + + } else { + // normal function():result style + + // Generate the function call + t_struct* arg_struct = tfunction->get_arglist(); + const std::vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + + f_service_ << indent(); + if (!(tfunction->is_oneway() || tfunction->get_returntype()->is_void())) { + f_service_ << "result.success = "; + } + f_service_ << get_cap_name(service_name_) << "_iface_." << tfunction->get_name() << "("; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << "args." << (*f_iter)->get_name(); + } + f_service_ << ");" << endl; + } + + indent_down(); + f_service_ << indent() << "}"; + if (!tfunction->is_oneway()) { + // catch exceptions defined in the IDL + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + f_service_ << " catch (" << (*x_iter)->get_name() << ":" + << get_cap_name(type_name((*x_iter)->get_type(), false, false)) << ") {" << endl; + if (!tfunction->is_oneway()) { + indent_up(); + f_service_ << indent() << "result." << (*x_iter)->get_name() << " = " + << (*x_iter)->get_name() << ";" << endl; + indent_down(); + f_service_ << indent() << "}"; + } else { + f_service_ << "}"; + } + } + } + + // always catch all exceptions to prevent from service denial + f_service_ << " catch (th : Dynamic) {" << endl; + indent_up(); + indent(f_service_) << "trace(\"Internal error processing " << tfunction->get_name() << "\", th);" + << endl; + if (!tfunction->is_oneway()) { + indent(f_service_) << "var x = new TApplicationException(TApplicationException.INTERNAL_ERROR, " + "\"Internal error processing " << tfunction->get_name() << "\");" << endl; + indent(f_service_) << "oprot.writeMessageBegin(new TMessage(\"" << tfunction->get_name() + << "\", TMessageType.EXCEPTION, seqid));" << endl; + indent(f_service_) << "x.write(oprot);" << endl; + indent(f_service_) << "oprot.writeMessageEnd();" << endl; + indent(f_service_) << "oprot.getTransport().flush();" << endl; + } + indent(f_service_) << "return;" << endl; + indent_down(); + f_service_ << indent() << "}" << endl; + + // Shortcut out here for oneway functions + if (tfunction->is_oneway()) { + f_service_ << indent() << "return;" << endl; + scope_down(f_service_); + + // Close class + indent_down(); + f_service_ << indent() << "}" << endl << endl; + return; + } + + f_service_ << indent() << "oprot.writeMessageBegin(new TMessage(\"" << tfunction->get_name() + << "\", TMessageType.REPLY, seqid));" << endl << indent() << "result.write(oprot);" + << endl << indent() << "oprot.writeMessageEnd();" << endl << indent() + << "oprot.getTransport().flush();" << endl; + + // Close function + scope_down(f_service_); + f_service_ << endl; + + // Close class + indent_down(); + f_service_ << indent() << "}" << endl << endl; +} + +/** + * Deserializes a field of any type. + * + * @param tfield The field + * @param prefix The variable name or container for this field + */ +void t_haxe_generator::generate_deserialize_field(ostream& out, t_field* tfield, string prefix) { + t_type* type = get_true_type(tfield->get_type()); + + if (type->is_void()) { + throw "CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name(); + } + + string name = prefix + tfield->get_name(); + + if (type->is_struct() || type->is_xception()) { + generate_deserialize_struct(out, (t_struct*)type, name); + } else if (type->is_container()) { + generate_deserialize_container(out, type, name); + } else if (type->is_base_type() || type->is_enum()) { + + indent(out) << name << " = iprot."; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + out << "readBinary();"; + } else { + out << "readString();"; + } + break; + case t_base_type::TYPE_BOOL: + out << "readBool();"; + break; + case t_base_type::TYPE_I8: + out << "readByte();"; + break; + case t_base_type::TYPE_I16: + out << "readI16();"; + break; + case t_base_type::TYPE_I32: + out << "readI32();"; + break; + case t_base_type::TYPE_I64: + out << "readI64();"; + break; + case t_base_type::TYPE_DOUBLE: + out << "readDouble();"; + break; + default: + throw "compiler error: no Haxe name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "readI32();"; + } + out << endl; + } else { + printf("DO NOT KNOW HOW TO DESERIALIZE FIELD '%s' TYPE '%s'\n", + tfield->get_name().c_str(), + type_name(type).c_str()); + } +} + +/** + * Generates an unserializer for a struct, invokes read() + */ +void t_haxe_generator::generate_deserialize_struct(ostream& out, + t_struct* tstruct, + string prefix) { + out << indent() << prefix << " = new " << get_cap_name(type_name(tstruct)) << "();" << endl + << indent() << prefix << ".read(iprot);" << endl; +} + +/** + * Deserializes a container by reading its size and then iterating + */ +void t_haxe_generator::generate_deserialize_container(ostream& out, t_type* ttype, string prefix) { + scope_up(out); + + string obj; + + if (ttype->is_map()) { + obj = tmp("_map"); + } else if (ttype->is_set()) { + obj = tmp("_set"); + } else if (ttype->is_list()) { + obj = tmp("_list"); + } + + // Declare variables, read header + if (ttype->is_map()) { + indent(out) << "var " << obj << " = iprot.readMapBegin();" << endl; + } else if (ttype->is_set()) { + indent(out) << "var " << obj << " = iprot.readSetBegin();" << endl; + } else if (ttype->is_list()) { + indent(out) << "var " << obj << " = iprot.readListBegin();" << endl; + } + + indent(out) << prefix << " = new " << type_name(ttype, false, true) + // size the collection correctly + << "(" + << ");" << endl; + + // For loop iterates over elements + string i = tmp("_i"); + indent(out) << "for( " << i << " in 0 ... " << obj << ".size)" << endl; + + scope_up(out); + + if (ttype->is_map()) { + generate_deserialize_map_element(out, (t_map*)ttype, prefix); + } else if (ttype->is_set()) { + generate_deserialize_set_element(out, (t_set*)ttype, prefix); + } else if (ttype->is_list()) { + generate_deserialize_list_element(out, (t_list*)ttype, prefix); + } + + scope_down(out); + + // Read container end + if (ttype->is_map()) { + indent(out) << "iprot.readMapEnd();" << endl; + } else if (ttype->is_set()) { + indent(out) << "iprot.readSetEnd();" << endl; + } else if (ttype->is_list()) { + indent(out) << "iprot.readListEnd();" << endl; + } + + scope_down(out); +} + +/** + * Generates code to deserialize a map + */ +void t_haxe_generator::generate_deserialize_map_element(ostream& out, t_map* tmap, string prefix) { + string key = tmp("_key"); + string val = tmp("_val"); + t_field fkey(tmap->get_key_type(), key); + t_field fval(tmap->get_val_type(), val); + + indent(out) << declare_field(&fkey) << endl; + indent(out) << declare_field(&fval) << endl; + + generate_deserialize_field(out, &fkey); + generate_deserialize_field(out, &fval); + + indent(out) << prefix << ".set( " << key << ", " << val << ");" << endl; +} + +/** + * Deserializes a set element + */ +void t_haxe_generator::generate_deserialize_set_element(ostream& out, t_set* tset, string prefix) { + string elem = tmp("_elem"); + t_field felem(tset->get_elem_type(), elem); + + indent(out) << declare_field(&felem) << endl; + + generate_deserialize_field(out, &felem); + + indent(out) << prefix << ".add(" << elem << ");" << endl; +} + +/** + * Deserializes a list element + */ +void t_haxe_generator::generate_deserialize_list_element(ostream& out, + t_list* tlist, + string prefix) { + string elem = tmp("_elem"); + t_field felem(tlist->get_elem_type(), elem); + + indent(out) << declare_field(&felem) << endl; + + generate_deserialize_field(out, &felem); + + indent(out) << prefix << ".add(" << elem << ");" << endl; +} + +/** + * Serializes a field of any type. + * + * @param tfield The field to serialize + * @param prefix Name to prepend to field name + */ +void t_haxe_generator::generate_serialize_field(ostream& out, t_field* tfield, string prefix) { + t_type* type = get_true_type(tfield->get_type()); + + // Do nothing for void types + if (type->is_void()) { + throw "CANNOT GENERATE SERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name(); + } + + if (type->is_struct() || type->is_xception()) { + generate_serialize_struct(out, (t_struct*)type, prefix + tfield->get_name()); + } else if (type->is_container()) { + generate_serialize_container(out, type, prefix + tfield->get_name()); + } else if (type->is_base_type() || type->is_enum()) { + + string name = prefix + tfield->get_name(); + indent(out) << "oprot."; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + out << "writeBinary(" << name << ");"; + } else { + out << "writeString(" << name << ");"; + } + break; + case t_base_type::TYPE_BOOL: + out << "writeBool(" << name << ");"; + break; + case t_base_type::TYPE_I8: + out << "writeByte(" << name << ");"; + break; + case t_base_type::TYPE_I16: + out << "writeI16(" << name << ");"; + break; + case t_base_type::TYPE_I32: + out << "writeI32(" << name << ");"; + break; + case t_base_type::TYPE_I64: + out << "writeI64(" << name << ");"; + break; + case t_base_type::TYPE_DOUBLE: + out << "writeDouble(" << name << ");"; + break; + default: + throw "compiler error: no Haxe name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "writeI32(" << name << ");"; + } + out << endl; + } else { + printf("DO NOT KNOW HOW TO SERIALIZE FIELD '%s%s' TYPE '%s'\n", + prefix.c_str(), + tfield->get_name().c_str(), + type_name(type).c_str()); + } +} + +/** + * Serializes all the members of a struct. + * + * @param tstruct The struct to serialize + * @param prefix String prefix to attach to all fields + */ +void t_haxe_generator::generate_serialize_struct(ostream& out, t_struct* tstruct, string prefix) { + (void)tstruct; + out << indent() << prefix << ".write(oprot);" << endl; +} + +/** + * Serializes a container by writing its size then the elements. + * + * @param ttype The type of container + * @param prefix String prefix for fields + */ +void t_haxe_generator::generate_serialize_container(ostream& out, t_type* ttype, string prefix) { + scope_up(out); + + if (ttype->is_map()) { + string iter = tmp("_key"); + string counter = tmp("_sizeCounter"); + indent(out) << "var " << counter << " : Int = 0;" << endl; + indent(out) << "for( " << iter << " in " << prefix << ") {" << endl; + indent(out) << " " << counter << +"++;" << endl; + indent(out) << "}" << endl; + + indent(out) << "oprot.writeMapBegin(new TMap(" << type_to_enum(((t_map*)ttype)->get_key_type()) + << ", " << type_to_enum(((t_map*)ttype)->get_val_type()) << ", " << counter << "));" + << endl; + } else if (ttype->is_set()) { + indent(out) << "oprot.writeSetBegin(new TSet(" << type_to_enum(((t_set*)ttype)->get_elem_type()) + << ", " << prefix << ".size));" << endl; + } else if (ttype->is_list()) { + indent(out) << "oprot.writeListBegin(new TList(" + << type_to_enum(((t_list*)ttype)->get_elem_type()) << ", " << prefix << ".length));" + << endl; + } + + string iter = tmp("elem"); + if (ttype->is_map()) { + indent(out) << "for( " << iter << " in " << prefix << ".keys())" << endl; + } else if (ttype->is_set()) { + indent(out) << "for( " << iter << " in " << prefix << ".toArray())" << endl; + } else if (ttype->is_list()) { + indent(out) << "for( " << iter << " in " << prefix << ")" << endl; + } + + scope_up(out); + + if (ttype->is_map()) { + generate_serialize_map_element(out, (t_map*)ttype, iter, prefix); + } else if (ttype->is_set()) { + generate_serialize_set_element(out, (t_set*)ttype, iter); + } else if (ttype->is_list()) { + generate_serialize_list_element(out, (t_list*)ttype, iter); + } + + scope_down(out); + + if (ttype->is_map()) { + indent(out) << "oprot.writeMapEnd();" << endl; + } else if (ttype->is_set()) { + indent(out) << "oprot.writeSetEnd();" << endl; + } else if (ttype->is_list()) { + indent(out) << "oprot.writeListEnd();" << endl; + } + + scope_down(out); +} + +/** + * Serializes the members of a map. + */ +void t_haxe_generator::generate_serialize_map_element(ostream& out, + t_map* tmap, + string iter, + string map) { + t_field kfield(tmap->get_key_type(), iter); + generate_serialize_field(out, &kfield, ""); + t_field vfield(tmap->get_val_type(), map + ".get(" + iter + ")"); + generate_serialize_field(out, &vfield, ""); +} + +/** + * Serializes the members of a set. + */ +void t_haxe_generator::generate_serialize_set_element(ostream& out, t_set* tset, string iter) { + t_field efield(tset->get_elem_type(), iter); + generate_serialize_field(out, &efield, ""); +} + +/** + * Serializes the members of a list. + */ +void t_haxe_generator::generate_serialize_list_element(ostream& out, t_list* tlist, string iter) { + t_field efield(tlist->get_elem_type(), iter); + generate_serialize_field(out, &efield, ""); +} + +/** + * Returns a haxe type name + * + * @param ttype The type + * @param container Is the type going inside a container? + * @return haxe type name, i.e. HashMap<Key,Value> + */ +string t_haxe_generator::type_name(t_type* ttype, bool in_container, bool in_init) { + (void)in_init; + + // typedefs are just resolved to their real type + ttype = get_true_type(ttype); + string prefix; + + if (ttype->is_base_type()) { + return base_type_name((t_base_type*)ttype, in_container); + } + + if (ttype->is_enum()) { + return "Int"; + } + + if (ttype->is_map()) { + t_type* tkey = get_true_type(((t_map*)ttype)->get_key_type()); + t_type* tval = get_true_type(((t_map*)ttype)->get_val_type()); + if (tkey->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)tkey)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + if (!(tkey->is_binary())) { + return "StringMap< " + type_name(tval) + ">"; + } + break; // default to ObjectMap<> + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + return "IntMap< " + type_name(tval) + ">"; + case t_base_type::TYPE_I64: + return "Int64Map< " + type_name(tval) + ">"; + default: + break; // default to ObjectMap<> + } + } + if (tkey->is_enum()) { + return "IntMap< " + type_name(tval) + ">"; + } + return "ObjectMap< " + type_name(tkey) + ", " + type_name(tval) + ">"; + } + + if (ttype->is_set()) { + t_type* tkey = get_true_type(((t_set*)ttype)->get_elem_type()); + if (tkey->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)tkey)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + if (!(tkey->is_binary())) { + return "StringSet"; + } + break; // default to ObjectSet + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + return "IntSet"; + case t_base_type::TYPE_I64: + return "Int64Set"; + default: + break; // default to ObjectSet + } + } + if (tkey->is_enum()) { + return "IntSet"; + } + return "ObjectSet< " + type_name(tkey) + ">"; + } + + if (ttype->is_list()) { + t_type* telm = ((t_list*)ttype)->get_elem_type(); + return "List< " + type_name(telm) + ">"; + } + + // Check for namespacing + t_program* program = ttype->get_program(); + if (program != NULL && program != program_) { + string package = program->get_namespace("haxe"); + if (!package.empty()) { + return package + "." + ttype->get_name(); + } + } + + return ttype->get_name(); +} + +/** + * Returns the haxe type that corresponds to the thrift type. + * + * @param tbase The base type + * @param container Is it going in a haxe container? + */ +string t_haxe_generator::base_type_name(t_base_type* type, bool in_container) { + (void)in_container; + t_base_type::t_base tbase = type->get_base(); + + switch (tbase) { + case t_base_type::TYPE_VOID: + return "Void"; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + return "haxe.io.Bytes"; + } else { + return "String"; + } + case t_base_type::TYPE_BOOL: + return "Bool"; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + return "haxe.Int32"; + case t_base_type::TYPE_I64: + return "haxe.Int64"; + case t_base_type::TYPE_DOUBLE: + return "Float"; + default: + throw "compiler error: no Haxe name for base type " + t_base_type::t_base_name(tbase); + } +} + +/** + * Declares a field, which may include initialization as necessary. + * + * @param ttype The type + */ +string t_haxe_generator::declare_field(t_field* tfield, bool init) { + // TODO(mcslee): do we ever need to initialize the field? + string result = "var " + tfield->get_name() + " : " + type_name(tfield->get_type()); + if (init) { + t_type* ttype = get_true_type(tfield->get_type()); + if (ttype->is_base_type() && tfield->get_value() != NULL) { + std::ofstream dummy; + result += " = " + render_const_value(dummy, tfield->get_name(), ttype, tfield->get_value()); + } else if (ttype->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)ttype)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + result += " = null"; + break; + case t_base_type::TYPE_BOOL: + result += " = false"; + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + result += " = 0"; + break; + case t_base_type::TYPE_DOUBLE: + result += " = (double)0"; + break; + } + + } else if (ttype->is_enum()) { + result += " = 0"; + } else if (ttype->is_container()) { + result += " = new " + type_name(ttype, false, true) + "()"; + } else { + result += " = new " + type_name(ttype, false, true) + "()"; + } + } + return result + ";"; +} + +/** + * Renders a function signature of the form 'type name(args)' + * + * @param tfunction Function definition + * @return String of rendered function definition + */ +string t_haxe_generator::function_signature_callback(t_function* tfunction) { + std::string on_error_success = "onError : Dynamic->Void = null, " + + generate_service_method_onsuccess(tfunction, true, false); + + std::string arguments = argument_list(tfunction->get_arglist()); + if (!tfunction->is_oneway()) { + if (arguments != "") { + arguments += ", "; + } + arguments += on_error_success; //"onError : Function, onSuccess : Function"; + } + + std::string result = "function " + tfunction->get_name() + "(" + arguments + ") : Void"; + return result; +} + +/** + * Renders a function signature of the form 'type name(args)' + * + * @param tfunction Function definition + * @return String of rendered function definition + */ +string t_haxe_generator::function_signature_normal(t_function* tfunction) { + std::string arguments = argument_list(tfunction->get_arglist()); + + std::string resulttype; + if (tfunction->is_oneway() || tfunction->get_returntype()->is_void()) { + resulttype = "Void"; + } else { + resulttype = type_name(tfunction->get_returntype()); + } + + std::string result = "function " + tfunction->get_name() + "(" + arguments + ") : " + resulttype; + return result; +} + +/** + * Renders a comma separated field list, with type names + */ +string t_haxe_generator::argument_list(t_struct* tstruct) { + string result = ""; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + result += ", "; + } + result += (*f_iter)->get_name() + " : " + type_name((*f_iter)->get_type()); + } + return result; +} + +/** + * Converts the parse type to a C++ enum string for the given type. + */ +string t_haxe_generator::type_to_enum(t_type* type) { + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + return "TType.STRING"; + case t_base_type::TYPE_BOOL: + return "TType.BOOL"; + case t_base_type::TYPE_I8: + return "TType.BYTE"; + case t_base_type::TYPE_I16: + return "TType.I16"; + case t_base_type::TYPE_I32: + return "TType.I32"; + case t_base_type::TYPE_I64: + return "TType.I64"; + case t_base_type::TYPE_DOUBLE: + return "TType.DOUBLE"; + } + } else if (type->is_enum()) { + return "TType.I32"; + } else if (type->is_struct() || type->is_xception()) { + return "TType.STRUCT"; + } else if (type->is_map()) { + return "TType.MAP"; + } else if (type->is_set()) { + return "TType.SET"; + } else if (type->is_list()) { + return "TType.LIST"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +/** + * Haxe class names must start with uppercase letter, but Haxe namespaces must not. + */ +std::string t_haxe_generator::get_cap_name(std::string name) { + if (name.length() == 0) { + return name; + } + + // test.for.Generic< data.Type, or.the.Like> and handle it recursively + size_t generic_first = name.find('<'); + size_t generic_last = name.rfind('>'); + if ((generic_first != std::string::npos) && (generic_last != std::string::npos)) { + string outer_type = name.substr(0, generic_first); + string inner_types = name.substr(generic_first + 1, generic_last - generic_first - 1); + + string new_inner = ""; + size_t comma_start = 0; + while (comma_start < inner_types.length()) { + size_t comma_pos = comma_start; + int nested = 0; + + while (comma_pos < inner_types.length()) { + bool found = false; + switch (inner_types[comma_pos]) { + case '<': + ++nested; + break; + case '>': + --nested; + break; + case ',': + found = (nested == 0); + break; + } + if (found) { + break; + } + ++comma_pos; + } + + if (new_inner.length() > 0) { + new_inner += ","; + } + + string inner = inner_types.substr(comma_start, comma_pos - comma_start); + new_inner += get_cap_name(inner); + comma_start = ++comma_pos; + } + + return get_cap_name(outer_type) + "<" + new_inner + ">"; + } + + // package name + size_t index = name.find_first_not_of(" \n\r\t"); + if (index < name.length()) { + name[index] = tolower(name[index]); + index = name.find('.'); + while (index != std::string::npos) { + if (++index < name.length()) { + name[index] = tolower(name[index]); + } + index = name.find('.', index); + } + } + + // class name + index = name.rfind('.'); + if (index != std::string::npos) { + ++index; + } else { + index = name.find_first_not_of(" \n\r\t"); + } + + if (index < name.length()) { + name[index] = toupper(name[index]); + } + + return name; +} + +string t_haxe_generator::constant_name(string name) { + string constant_name; + + bool is_first = true; + bool was_previous_char_upper = false; + for (char character : name) { + bool is_upper = isupper(character); + + if (is_upper && !is_first && !was_previous_char_upper) { + constant_name += '_'; + } + constant_name += toupper(character); + + is_first = false; + was_previous_char_upper = is_upper; + } + + return constant_name; +} + +/** + * Enables RTTI for a class or interface + */ +void t_haxe_generator::generate_rtti_decoration(ostream& out) { + if (rtti_) { + out << "@:rtti" << endl; + } +} + +/** + * Adds build macros to a class or interface + */ +void t_haxe_generator::generate_macro_decoration(ostream& out) { + if (!buildmacro_.empty()) { + out << "#if ! macro" << endl; + out << "@:build( " << buildmacro_ << ")" << endl; // current class/interface + out << "@:autoBuild( " << buildmacro_ << ")" << endl; // inherited classes/interfaces + out << "#end" << endl; + } +} + +/** + * Emits a haxeDoc comment if the provided object has a doc in Thrift + */ +void t_haxe_generator::generate_haxe_doc(ostream& out, t_doc* tdoc) { + if (tdoc->has_doc()) { + generate_docstring_comment(out, "/**\n", " * ", tdoc->get_doc(), " */\n"); + } +} + +/** + * Emits a haxeDoc comment if the provided function object has a doc in Thrift + */ +void t_haxe_generator::generate_haxe_doc(ostream& out, t_function* tfunction) { + if (tfunction->has_doc()) { + stringstream ss; + ss << tfunction->get_doc(); + const vector<t_field*>& fields = tfunction->get_arglist()->get_members(); + vector<t_field*>::const_iterator p_iter; + for (p_iter = fields.begin(); p_iter != fields.end(); ++p_iter) { + t_field* p = *p_iter; + ss << "\n@param " << p->get_name(); + if (p->has_doc()) { + ss << " " << p->get_doc(); + } + } + generate_docstring_comment(out, "/**\n", " * ", ss.str(), " */\n"); + } +} + +std::string t_haxe_generator::generate_isset_check(t_field* field) { + return generate_isset_check(field->get_name()); +} + +std::string t_haxe_generator::generate_isset_check(std::string field_name) { + return "is" + get_cap_name("set") + get_cap_name(field_name) + "()"; +} + +void t_haxe_generator::generate_isset_set(ostream& out, t_field* field) { + if (!type_can_be_null(field->get_type())) { + indent(out) << "this.__isset_" << field->get_name() << " = true;" << endl; + } +} + +std::string t_haxe_generator::get_enum_class_name(t_type* type) { + string package = ""; + t_program* program = type->get_program(); + if (program != NULL /*&& program != program_*/) { + package = program->get_namespace("haxe") + "."; + } + return package + type->get_name(); +} + +THRIFT_REGISTER_GENERATOR( + haxe, + "Haxe", + " callbacks Use onError()/onSuccess() callbacks for service methods (like AS3)\n" + " rtti Enable @:rtti for generated classes and interfaces\n" + " buildmacro=my.macros.Class.method(args)\n" + " Add @:build macro calls to generated classes and interfaces\n") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_hs_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_hs_generator.cc new file mode 100644 index 000000000..a59dee5d2 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_hs_generator.cc @@ -0,0 +1,1717 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <string> +#include <fstream> +#include <iostream> +#include <vector> + +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sstream> + +#include "thrift/platform.h" +#include "thrift/version.h" + +#include "thrift/generate/t_oop_generator.h" + +using std::map; +using std::ostream; +using std::ostringstream; +using std::string; +using std::stringstream; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes + +/** + * Haskell code generator. + * + */ +class t_hs_generator : public t_oop_generator { +public: + t_hs_generator(t_program* program, + const map<string, string>& parsed_options, + const string& option_string) + : t_oop_generator(program) { + (void)option_string; + std::map<std::string, std::string>::const_iterator iter; + + /* no options yet */ + for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + throw "unknown option hs:" + iter->first; + } + + out_dir_base_ = "gen-hs"; + } + + /** + * Init and close methods + */ + + void init_generator() override; + void close_generator() override; + + /** + * Program-level generation functions + */ + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_const(t_const* tconst) override; + void generate_struct(t_struct* tstruct) override; + void generate_xception(t_struct* txception) override; + void generate_service(t_service* tservice) override; + + string render_const_value(t_type* type, t_const_value* value); + + /** + * Struct generation code + */ + + void generate_hs_struct(t_struct* tstruct, bool is_exception); + + void generate_hs_struct_definition(ostream& out, + t_struct* tstruct, + bool is_xception = false, + bool helper = false); + + void generate_hs_struct_reader(ostream& out, t_struct* tstruct); + + void generate_hs_struct_writer(ostream& out, t_struct* tstruct); + + void generate_hs_struct_arbitrary(ostream& out, t_struct* tstruct); + + void generate_hs_function_helpers(t_function* tfunction); + + void generate_hs_typemap(ostream& out, t_struct* tstruct); + + void generate_hs_default(ostream& out, t_struct* tstruct); + + /** + * Service-level generation functions + */ + + void generate_service_helpers(t_service* tservice); + void generate_service_interface(t_service* tservice); + void generate_service_client(t_service* tservice); + void generate_service_server(t_service* tservice); + void generate_process_function(t_service* tservice, t_function* tfunction); + + /** + * Serialization constructs + */ + + void generate_deserialize_field(ostream& out, t_field* tfield, string prefix); + + void generate_deserialize_struct(ostream& out, t_struct* tstruct, string name = ""); + + void generate_deserialize_container(ostream& out, t_type* ttype, string arg = ""); + + void generate_deserialize_set_element(ostream& out, t_set* tset); + + void generate_deserialize_list_element(ostream& out, t_list* tlist, string prefix = ""); + + void generate_deserialize_type(ostream& out, t_type* type, string arg = ""); + + void generate_serialize_type(ostream& out, t_type* type, string name = ""); + + void generate_serialize_struct(ostream& out, t_struct* tstruct, string prefix = ""); + + void generate_serialize_container(ostream& out, t_type* ttype, string prefix = ""); + + void generate_serialize_map_element(ostream& out, t_map* tmap, string kiter, string viter); + + void generate_serialize_set_element(ostream& out, t_set* tmap, string iter); + + void generate_serialize_list_element(ostream& out, t_list* tlist, string iter); + + /** + * Helper rendering functions + */ + + string hs_autogen_comment(); + string hs_language_pragma(); + string hs_imports(); + + string type_name(t_type* ttype, string function_prefix = ""); + + string field_name(string tname, string fname); + + string function_type(t_function* tfunc, + bool options = false, + bool io = false, + bool method = false); + + string type_to_enum(t_type* ttype); + + string type_to_default(t_type* ttype); + + string render_hs_type(t_type* type, bool needs_parens); + + string type_to_constructor(t_type* ttype); + + string render_hs_type_for_function_name(t_type* type); + +private: + ofstream_with_content_based_conditional_update f_types_; + ofstream_with_content_based_conditional_update f_consts_; + ofstream_with_content_based_conditional_update f_service_; + ofstream_with_content_based_conditional_update f_iface_; + ofstream_with_content_based_conditional_update f_client_; +}; + +/** + * Prepares for file generation by opening up the necessary file output + * streams. + * + * @param tprogram The program to generate + */ +void t_hs_generator::init_generator() { + // Make output directory + MKDIR(get_out_dir().c_str()); + + // Make output file + string pname = capitalize(program_name_); + string f_types_name = get_out_dir() + pname + "_Types.hs"; + f_types_.open(f_types_name.c_str()); + + string f_consts_name = get_out_dir() + pname + "_Consts.hs"; + f_consts_.open(f_consts_name.c_str()); + + // Print header + f_types_ << hs_language_pragma() << endl; + f_types_ << hs_autogen_comment() << endl; + f_types_ << "module " << pname << "_Types where" << endl; + f_types_ << hs_imports() << endl; + + f_consts_ << hs_language_pragma() << endl; + f_consts_ << hs_autogen_comment() << endl; + f_consts_ << "module " << pname << "_Consts where" << endl; + f_consts_ << hs_imports() << endl; + f_consts_ << "import " << pname << "_Types" << endl; +} + +string t_hs_generator::hs_language_pragma() { + return string( + "{-# LANGUAGE DeriveDataTypeable #-}\n" + "{-# LANGUAGE DeriveGeneric #-}\n" + "{-# LANGUAGE OverloadedStrings #-}\n" + "{-# OPTIONS_GHC -fno-warn-missing-fields #-}\n" + "{-# OPTIONS_GHC -fno-warn-missing-signatures #-}\n" + "{-# OPTIONS_GHC -fno-warn-name-shadowing #-}\n" + "{-# OPTIONS_GHC -fno-warn-unused-imports #-}\n" + "{-# OPTIONS_GHC -fno-warn-unused-matches #-}\n"); +} + +/** + * Autogen'd comment + */ +string t_hs_generator::hs_autogen_comment() { + return string("-----------------------------------------------------------------\n") + + "-- Autogenerated by Thrift Compiler (" + THRIFT_VERSION + ") --\n" + + "-- --\n" + + "-- DO NOT EDIT UNLESS YOU ARE SURE YOU KNOW WHAT YOU ARE DOING --\n" + + "-----------------------------------------------------------------\n"; +} + +/** + * Prints standard thrift imports + */ +string t_hs_generator::hs_imports() { + const vector<t_program*>& includes = program_->get_includes(); + string result = string( + "import Prelude (($), (.), (>>=), (==), (++))\n" + "import qualified Prelude as P\n" + "import qualified Control.Exception as X\n" + "import qualified Control.Monad as M ( liftM, ap, when )\n" + "import Data.Functor ( (<$>) )\n" + "import qualified Data.ByteString.Lazy as LBS\n" + "import qualified Data.Hashable as H\n" + "import qualified Data.Int as I\n" + "import qualified Data.Maybe as M (catMaybes)\n" + "import qualified Data.Text.Lazy.Encoding as E ( decodeUtf8, encodeUtf8 )\n" + "import qualified Data.Text.Lazy as LT\n" + "import qualified GHC.Generics as G (Generic)\n" + "import qualified Data.Typeable as TY ( Typeable )\n" + "import qualified Data.HashMap.Strict as Map\n" + "import qualified Data.HashSet as Set\n" + "import qualified Data.Vector as Vector\n" + "import qualified Test.QuickCheck.Arbitrary as QC ( Arbitrary(..) )\n" + "import qualified Test.QuickCheck as QC ( elements )\n" + "\n" + "import qualified Thrift as T\n" + "import qualified Thrift.Types as T\n" + "import qualified Thrift.Arbitraries as T\n" + "\n"); + + for (auto include : includes) + result += "import qualified " + capitalize(include->get_name()) + "_Types\n"; + + if (includes.size() > 0) + result += "\n"; + + return result; +} + +/** + * Closes the type files + */ +void t_hs_generator::close_generator() { + // Close types file + f_types_.close(); + f_consts_.close(); +} + +/** + * Generates a typedef. Ez. + * + * @param ttypedef The type definition + */ +void t_hs_generator::generate_typedef(t_typedef* ttypedef) { + string tname = capitalize(ttypedef->get_symbolic()); + string tdef = render_hs_type(ttypedef->get_type(), false); + indent(f_types_) << "type " << tname << " = " << tdef << endl; + f_types_ << endl; +} + +/** + * Generates code for an enumerated type. + * the values. + * + * @param tenum The enumeration + */ +void t_hs_generator::generate_enum(t_enum* tenum) { + indent(f_types_) << "data " << capitalize(tenum->get_name()) << " = "; + indent_up(); + vector<t_enum_value*> constants = tenum->get_constants(); + vector<t_enum_value*>::iterator c_iter; + + bool first = true; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + string name = capitalize((*c_iter)->get_name()); + f_types_ << (first ? "" : "|"); + f_types_ << name; + first = false; + } + indent(f_types_) << "deriving (P.Show, P.Eq, G.Generic, TY.Typeable, P.Ord, P.Bounded)" << endl; + indent_down(); + + string ename = capitalize(tenum->get_name()); + + indent(f_types_) << "instance P.Enum " << ename << " where" << endl; + indent_up(); + indent(f_types_) << "fromEnum t = case t of" << endl; + indent_up(); + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + int value = (*c_iter)->get_value(); + string name = capitalize((*c_iter)->get_name()); + indent(f_types_) << name << " -> " << value << endl; + } + indent_down(); + indent(f_types_) << "toEnum t = case t of" << endl; + indent_up(); + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + int value = (*c_iter)->get_value(); + string name = capitalize((*c_iter)->get_name()); + indent(f_types_) << value << " -> " << name << endl; + } + indent(f_types_) << "_ -> X.throw T.ThriftException" << endl; + indent_down(); + indent_down(); + + indent(f_types_) << "instance H.Hashable " << ename << " where" << endl; + indent_up(); + indent(f_types_) << "hashWithSalt salt = H.hashWithSalt salt P.. P.fromEnum" << endl; + indent_down(); + + indent(f_types_) << "instance QC.Arbitrary " << ename << " where" << endl; + indent_up(); + indent(f_types_) << "arbitrary = QC.elements (P.enumFromTo P.minBound P.maxBound)" << endl; + indent_down(); +} + +/** + * Generate a constant value + */ +void t_hs_generator::generate_const(t_const* tconst) { + t_type* type = tconst->get_type(); + string name = decapitalize(tconst->get_name()); + + t_const_value* value = tconst->get_value(); + + indent(f_consts_) << name << " :: " << render_hs_type(type, false) << endl; + indent(f_consts_) << name << " = " << render_const_value(type, value) << endl; + f_consts_ << endl; +} + +/** + * Prints the value of a constant with the given type. Note that type checking + * is NOT performed in this function as it is always run beforehand using the + * validate_types method in main.cc + */ +string t_hs_generator::render_const_value(t_type* type, t_const_value* value) { + if (value == NULL) + return type_to_default(type); + + type = get_true_type(type); + ostringstream out; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + + case t_base_type::TYPE_STRING: + out << '"' << get_escaped_string(value) << '"'; + break; + + case t_base_type::TYPE_BOOL: + out << (value->get_integer() > 0 ? "P.True" : "P.False"); + break; + + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + out << "(" << value->get_integer() << ")"; + break; + + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) { + out << "(" << value->get_integer() << ")"; + } else { + out << "(" << value->get_double() << ")"; + } + break; + + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + + } else if (type->is_enum()) { + t_enum* tenum = (t_enum*)type; + vector<t_enum_value*> constants = tenum->get_constants(); + for (auto & constant : constants) { + int val = constant->get_value(); + if (val == value->get_integer()) { + t_program* prog = type->get_program(); + if (prog != NULL && prog != program_) + out << capitalize(prog->get_name()) << "_Types."; + out << capitalize(constant->get_name()); + break; + } + } + + } else if (type->is_struct() || type->is_xception()) { + string cname = type_name(type); + out << "default_" << cname << "{"; + + const vector<t_field*>& fields = ((t_struct*)type)->get_members(); + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + + bool first = true; + for (auto v_iter : val) { + t_field* field = NULL; + + for (auto f_iter : fields) + if (f_iter->get_name() == v_iter.first->get_string()) + field = f_iter; + + if (field == NULL) + throw "type error: " + cname + " has no field " + v_iter.first->get_string(); + + string fname = v_iter.first->get_string(); + string const_value = render_const_value(field->get_type(), v_iter.second); + + out << (first ? "" : ", "); + out << field_name(cname, fname) << " = "; + if (field->get_req() == t_field::T_OPTIONAL || ((t_type*)field->get_type())->is_xception()) { + out << "P.Just "; + } + out << const_value; + first = false; + } + + out << "}"; + + } else if (type->is_map()) { + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + + out << "(Map.fromList ["; + + bool first = true; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string key = render_const_value(ktype, v_iter->first); + string val = render_const_value(vtype, v_iter->second); + out << (first ? "" : ","); + out << "(" << key << "," << val << ")"; + first = false; + } + out << "])"; + + } else if (type->is_list() || type->is_set()) { + t_type* etype = type->is_list() ? ((t_list*)type)->get_elem_type() + : ((t_set*)type)->get_elem_type(); + + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + + if (type->is_set()) + out << "(Set.fromList ["; + else + out << "(Vector.fromList ["; + + bool first = true; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + out << (first ? "" : ","); + out << render_const_value(etype, *v_iter); + first = false; + } + + out << "])"; + + } else { + throw "CANNOT GENERATE CONSTANT FOR TYPE: " + type->get_name(); + } + + return out.str(); +} + +/** + * Generates a "struct" + */ +void t_hs_generator::generate_struct(t_struct* tstruct) { + generate_hs_struct(tstruct, false); +} + +/** + * Generates a struct definition for a thrift exception. Basically the same + * as a struct, but also has an exception declaration. + * + * @param txception The struct definition + */ +void t_hs_generator::generate_xception(t_struct* txception) { + generate_hs_struct(txception, true); +} + +/** + * Generates a Haskell struct + */ +void t_hs_generator::generate_hs_struct(t_struct* tstruct, bool is_exception) { + generate_hs_struct_definition(f_types_, tstruct, is_exception, false); +} + +/** + * Generates a struct definition for a thrift data type. + * + * @param tstruct The struct definition + */ +void t_hs_generator::generate_hs_struct_definition(ostream& out, + t_struct* tstruct, + bool is_exception, + bool helper) { + (void)helper; + string tname = type_name(tstruct); + string name = tstruct->get_name(); + const vector<t_field*>& members = tstruct->get_members(); + + indent(out) << "data " << tname << " = " << tname; + if (members.size() > 0) { + indent_up(); + bool first = true; + for (auto member : members) { + if (first) { + indent(out) << "{ "; + first = false; + } else { + indent(out) << ", "; + } + string mname = member->get_name(); + out << field_name(tname, mname) << " :: "; + if (member->get_req() == t_field::T_OPTIONAL + || ((t_type*)member->get_type())->is_xception()) { + out << "P.Maybe "; + } + out << render_hs_type(member->get_type(), true) << endl; + } + indent(out) << "}"; + indent_down(); + } + + out << " deriving (P.Show,P.Eq,G.Generic,TY.Typeable)" << endl; + + if (is_exception) + out << "instance X.Exception " << tname << endl; + + indent(out) << "instance H.Hashable " << tname << " where" << endl; + indent_up(); + indent(out) << "hashWithSalt salt record = salt"; + for (auto member : members) { + string mname = member->get_name(); + indent(out) << " `H.hashWithSalt` " << field_name(tname, mname) << " record"; + } + indent(out) << endl; + indent_down(); + + generate_hs_struct_arbitrary(out, tstruct); + generate_hs_struct_writer(out, tstruct); + generate_hs_struct_reader(out, tstruct); + generate_hs_typemap(out, tstruct); + generate_hs_default(out, tstruct); +} + +void t_hs_generator::generate_hs_struct_arbitrary(ostream& out, t_struct* tstruct) { + string tname = type_name(tstruct); + string name = tstruct->get_name(); + const vector<t_field*>& members = tstruct->get_members(); + + indent(out) << "instance QC.Arbitrary " << tname << " where " << endl; + indent_up(); + if (members.size() > 0) { + indent(out) << "arbitrary = M.liftM " << tname; + indent_up(); + indent_up(); + indent_up(); + indent_up(); + bool first = true; + for (auto member : members) { + if (first) { + first = false; + out << " "; + } else { + indent(out) << "`M.ap`"; + } + out << "("; + if (member->get_req() == t_field::T_OPTIONAL + || ((t_type*)member->get_type())->is_xception()) { + out << "M.liftM P.Just "; + } + out << "QC.arbitrary)" << endl; + } + indent_down(); + indent_down(); + indent_down(); + indent_down(); + + // Shrink + indent(out) << "shrink obj | obj == default_" << tname << " = []" << endl; + indent(out) << " | P.otherwise = M.catMaybes" << endl; + indent_up(); + first = true; + for (auto member : members) { + if (first) { + first = false; + indent(out) << "[ "; + } else { + indent(out) << ", "; + } + string fname = field_name(tname, member->get_name()); + out << "if obj == default_" << tname; + out << "{" << fname << " = " << fname << " obj} "; + out << "then P.Nothing "; + out << "else P.Just $ default_" << tname; + out << "{" << fname << " = " << fname << " obj}" << endl; + } + indent(out) << "]" << endl; + indent_down(); + } else { /* 0 == members.size() */ + indent(out) << "arbitrary = QC.elements [" << tname << "]" << endl; + } + indent_down(); +} + +/** + * Generates the read method for a struct + */ +void t_hs_generator::generate_hs_struct_reader(ostream& out, t_struct* tstruct) { + const vector<t_field*>& fields = tstruct->get_members(); + + string sname = type_name(tstruct); + string id = tmp("_id"); + string val = tmp("_val"); + + indent(out) << "to_" << sname << " :: T.ThriftVal -> " << sname << endl; + indent(out) << "to_" << sname << " (T.TStruct fields) = " << sname << "{" << endl; + indent_up(); + + bool first = true; + + // Generate deserialization code for known cases + for (auto field : fields) { + int32_t key = field->get_key(); + string etype = type_to_enum(field->get_type()); + string fname = field->get_name(); + + if (first) { + first = false; + } else { + out << "," << endl; + } + + // Fill in Field + indent(out) << field_name(sname, fname) << " = "; + + out << "P.maybe ("; + if (field->get_req() == t_field::T_REQUIRED) { + out << "P.error \"Missing required field: " << fname << "\""; + } else { + if ((field->get_req() == t_field::T_OPTIONAL + || ((t_type*)field->get_type())->is_xception()) && field->get_value() == NULL) { + out << "P.Nothing"; + } else { + out << field_name(sname, fname) << " default_" << sname; + } + } + out << ") "; + + out << "(\\(_," << val << ") -> "; + if (field->get_req() == t_field::T_OPTIONAL + || ((t_type*)field->get_type())->is_xception()) + out << "P.Just "; + generate_deserialize_field(out, field, val); + out << ")"; + out << " (Map.lookup (" << key << ") fields)"; + } + + out << endl; + indent(out) << "}" << endl; + indent_down(); + + // read + string tmap = type_name(tstruct, "typemap_"); + indent(out) << "to_" << sname << " _ = P.error \"not a struct\"" << endl; + + indent(out) << "read_" << sname << " :: T.Protocol p => p -> P.IO " << sname + << endl; + indent(out) << "read_" << sname << " iprot = to_" << sname; + out << " <$> T.readVal iprot (T.T_STRUCT " << tmap << ")" << endl; + + indent(out) << "decode_" << sname + << " :: T.StatelessProtocol p => p -> LBS.ByteString -> " << sname << endl; + indent(out) << "decode_" << sname << " iprot bs = to_" << sname << " $ "; + out << "T.deserializeVal iprot (T.T_STRUCT " << tmap << ") bs" << endl; +} + +void t_hs_generator::generate_hs_struct_writer(ostream& out, t_struct* tstruct) { + string name = type_name(tstruct); + const vector<t_field*>& fields = tstruct->get_sorted_members(); + string str = tmp("_str"); + string f = tmp("_f"); + string v = tmp("_v"); + + indent(out) << "from_" << name << " :: " << name << " -> T.ThriftVal" << endl; + indent(out) << "from_" << name << " record = T.TStruct $ Map.fromList "; + indent_up(); + + // Get Exceptions + bool hasExn = false; + for (auto field : fields) { + if (((t_type*)field->get_type())->is_xception()) { + hasExn = true; + break; + } + } + + bool isfirst = true; + if (hasExn) { + out << endl; + indent(out) << "(let exns = M.catMaybes "; + indent_up(); + for (auto field : fields) { + if (((t_type*)field->get_type())->is_xception()) { + if (isfirst) { + out << "[ "; + isfirst = false; + } else { + out << ", "; + } + string mname = field->get_name(); + int32_t key = field->get_key(); + out << "(\\" << v << " -> (" << key << ", (\"" << mname << "\","; + generate_serialize_type(out, field->get_type(), v); + out << "))) <$> " << field_name(name, mname) << " record"; + } + } + if (!isfirst) { + out << "]" << endl; + } + indent_down(); + indent(out) << "in if P.not (P.null exns) then exns else "; + indent_up(); + } else { + out << "$ "; + } + + out << "M.catMaybes" << endl; + // Get the Rest + isfirst = true; + for (auto field : fields) { + // Write field header + if (isfirst) { + indent(out) << "[ "; + isfirst = false; + } else { + indent(out) << ", "; + } + string mname = field->get_name(); + int32_t key = field->get_key(); + out << "(\\"; + out << v << " -> "; + if (field->get_req() != t_field::T_OPTIONAL + && !((t_type*)field->get_type())->is_xception()) { + out << "P.Just "; + } + out << "(" << key << ", (\"" << mname << "\","; + generate_serialize_type(out, field->get_type(), v); + out << "))) "; + if (field->get_req() != t_field::T_OPTIONAL + && !((t_type*)field->get_type())->is_xception()) { + out << "$"; + } else { + out << "<$>"; + } + out << " " << field_name(name, mname) << " record" << endl; + } + + // Write the struct map + if (isfirst) { + indent(out) << "[]" << endl; + } else { + indent(out) << "]" << endl; + } + if (hasExn) { + indent(out) << ")" << endl; + indent_down(); + } + indent_down(); + + // write + indent(out) << "write_" << name << " :: T.Protocol p => p -> " << name + << " -> P.IO ()" << endl; + indent(out) << "write_" << name << " oprot record = T.writeVal oprot $ from_"; + out << name << " record" << endl; + + // encode + indent(out) << "encode_" << name << " :: T.StatelessProtocol p => p -> " << name + << " -> LBS.ByteString" << endl; + indent(out) << "encode_" << name << " oprot record = T.serializeVal oprot $ "; + out << "from_" << name << " record" << endl; +} + +/** + * Generates a thrift service. + * + * @param tservice The service definition + */ +void t_hs_generator::generate_service(t_service* tservice) { + string f_service_name = get_out_dir() + capitalize(service_name_) + ".hs"; + f_service_.open(f_service_name.c_str()); + + f_service_ << hs_language_pragma() << endl; + f_service_ << hs_autogen_comment() << endl; + f_service_ << "module " << capitalize(service_name_) << " where" << endl; + f_service_ << hs_imports() << endl; + + if (tservice->get_extends()) { + f_service_ << "import qualified " << capitalize(tservice->get_extends()->get_name()) << endl; + } + + f_service_ << "import " << capitalize(program_name_) << "_Types" << endl; + f_service_ << "import qualified " << capitalize(service_name_) << "_Iface as Iface" << endl; + + // Generate the three main parts of the service + generate_service_helpers(tservice); + generate_service_interface(tservice); + generate_service_client(tservice); + generate_service_server(tservice); + + // Close service file + f_service_.close(); +} + +/** + * Generates helper functions for a service. + * + * @param tservice The service to generate a header definition for + */ +void t_hs_generator::generate_service_helpers(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + indent(f_service_) << "-- HELPER FUNCTIONS AND STRUCTURES --" << endl; + indent(f_service_) << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* ts = (*f_iter)->get_arglist(); + generate_hs_struct_definition(f_service_, ts, false); + generate_hs_function_helpers(*f_iter); + } +} + +/** + * Generates a struct and helpers for a function. + * + * @param tfunction The function + */ +void t_hs_generator::generate_hs_function_helpers(t_function* tfunction) { + t_struct result(program_, field_name(tfunction->get_name(), "result")); + t_field success(tfunction->get_returntype(), "success", 0); + + if (!tfunction->get_returntype()->is_void()) + result.append(&success); + + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& fields = xs->get_members(); + + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + result.append(*f_iter); + + generate_hs_struct_definition(f_service_, &result, false); +} + +/** + * Generate the map from field names to (type, id) + * @param tstruct the Struct + */ +void t_hs_generator::generate_hs_typemap(ostream& out, t_struct* tstruct) { + string name = type_name(tstruct); + const vector<t_field*>& fields = tstruct->get_sorted_members(); + + indent(out) << "typemap_" << name << " :: T.TypeMap" << endl; + indent(out) << "typemap_" << name << " = Map.fromList ["; + bool first = true; + for (auto field : fields) { + string mname = field->get_name(); + if (!first) { + out << ","; + } + + t_type* type = get_true_type(field->get_type()); + int32_t key = field->get_key(); + out << "(" << key << ",(\"" << mname << "\"," << type_to_enum(type) << "))"; + first = false; + } + out << "]" << endl; +} + +/** + * generate the struct with default values filled in + * @param tstruct the Struct + */ +void t_hs_generator::generate_hs_default(ostream& out, t_struct* tstruct) { + string name = type_name(tstruct); + string fname = type_name(tstruct, "default_"); + const vector<t_field*>& fields = tstruct->get_sorted_members(); + + indent(out) << fname << " :: " << name << endl; + indent(out) << fname << " = " << name << "{" << endl; + indent_up(); + bool first = true; + for (auto field : fields) { + string mname = field->get_name(); + if (first) { + first = false; + } else { + out << "," << endl; + } + + t_type* type = get_true_type(field->get_type()); + t_const_value* value = field->get_value(); + indent(out) << field_name(name, mname) << " = "; + if (field->get_req() == t_field::T_OPTIONAL + || ((t_type*)field->get_type())->is_xception()) { + if (value == NULL) { + out << "P.Nothing"; + } else { + out << "P.Just " << render_const_value(type, value); + } + } else { + out << render_const_value(type, value); + } + } + out << "}" << endl; + indent_down(); +} + +/** + * Generates a service interface definition. + * + * @param tservice The service to generate a header definition for + */ +void t_hs_generator::generate_service_interface(t_service* tservice) { + string f_iface_name = get_out_dir() + capitalize(service_name_) + "_Iface.hs"; + f_iface_.open(f_iface_name.c_str()); + + f_iface_ << hs_language_pragma() << endl; + f_iface_ << hs_autogen_comment() << endl; + + f_iface_ << "module " << capitalize(service_name_) << "_Iface where" << endl; + + f_iface_ << hs_imports() << endl; + f_iface_ << "import " << capitalize(program_name_) << "_Types" << endl; + f_iface_ << endl; + + string sname = capitalize(service_name_); + if (tservice->get_extends() != NULL) { + string extends = type_name(tservice->get_extends()); + + indent(f_iface_) << "import " << extends << "_Iface" << endl; + indent(f_iface_) << "class " << extends << "_Iface a => " << sname << "_Iface a where" << endl; + + } else { + indent(f_iface_) << "class " << sname << "_Iface a where" << endl; + } + + indent_up(); + + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + string ft = function_type(*f_iter, true, true, true); + indent(f_iface_) << decapitalize((*f_iter)->get_name()) << " :: a -> " << ft << endl; + } + + indent_down(); + f_iface_.close(); +} + +/** + * Generates a service client definition. Note that in Haskell, the client doesn't implement iface. + *This is because + * The client does not (and should not have to) deal with arguments being Nothing. + * + * @param tservice The service to generate a server for. + */ +void t_hs_generator::generate_service_client(t_service* tservice) { + string f_client_name = get_out_dir() + capitalize(service_name_) + "_Client.hs"; + f_client_.open(f_client_name.c_str()); + f_client_ << hs_language_pragma() << endl; + f_client_ << hs_autogen_comment() << endl; + + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator f_iter; + + string extends = ""; + string exports = ""; + + bool first = true; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + exports += (first ? "" : ","); + string funname = (*f_iter)->get_name(); + exports += decapitalize(funname); + first = false; + } + + string sname = capitalize(service_name_); + indent(f_client_) << "module " << sname << "_Client(" << exports << ") where" << endl; + + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + indent(f_client_) << "import " << extends << "_Client" << endl; + } + + indent(f_client_) << "import qualified Data.IORef as R" << endl; + indent(f_client_) << hs_imports() << endl; + indent(f_client_) << "import " << capitalize(program_name_) << "_Types" << endl; + indent(f_client_) << "import " << capitalize(service_name_) << endl; + + // DATS RITE A GLOBAL VAR + indent(f_client_) << "seqid = R.newIORef 0" << endl; + + // Generate client method implementations + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* arg_struct = (*f_iter)->get_arglist(); + const vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator fld_iter; + string funname = (*f_iter)->get_name(); + + string fargs = ""; + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) + fargs += " arg_" + (*fld_iter)->get_name(); + + // Open function + indent(f_client_) << decapitalize(funname) << " (ip,op)" << fargs << " = do" << endl; + indent_up(); + indent(f_client_) << "send_" << funname << " op" << fargs; + + f_client_ << endl; + + if (!(*f_iter)->is_oneway()) + indent(f_client_) << "recv_" << funname << " ip" << endl; + + indent_down(); + + indent(f_client_) << "send_" << funname << " op" << fargs << " = do" << endl; + indent_up(); + + indent(f_client_) << "seq <- seqid" << endl; + indent(f_client_) << "seqn <- R.readIORef seq" << endl; + string argsname = capitalize((*f_iter)->get_name() + "_args"); + + // Serialize the request header + string fname = (*f_iter)->get_name(); + string msgType = (*f_iter)->is_oneway() ? "T.M_ONEWAY" : "T.M_CALL"; + indent(f_client_) << "T.writeMessage op (\"" << fname << "\", " << msgType << ", seqn) $" + << endl; + indent_up(); + indent(f_client_) << "write_" << argsname << " op (" << argsname << "{"; + + bool first = true; + for (auto field : fields) { + string fieldname = field->get_name(); + f_client_ << (first ? "" : ","); + f_client_ << field_name(argsname, fieldname) << "="; + if (field->get_req() == t_field::T_OPTIONAL + || ((t_type*)field->get_type())->is_xception()) + f_client_ << "P.Just "; + f_client_ << "arg_" << fieldname; + first = false; + } + f_client_ << "})" << endl; + indent_down(); + indent_down(); + + if (!(*f_iter)->is_oneway()) { + string resultname = capitalize((*f_iter)->get_name() + "_result"); + t_struct noargs(program_); + + string funname = string("recv_") + (*f_iter)->get_name(); + t_function recv_function((*f_iter)->get_returntype(), funname, &noargs); + + // Open function + indent(f_client_) << funname << " ip = do" << endl; + indent_up(); + + indent(f_client_) << "T.readMessage ip $ \\(fname, mtype, rseqid) -> do" << endl; + indent_up(); + indent(f_client_) << "M.when (mtype == T.M_EXCEPTION) $ do { exn <- T.readAppExn ip ; " + "X.throw exn }" << endl; + + indent(f_client_) << "res <- read_" << resultname << " ip" << endl; + + t_struct* xs = (*f_iter)->get_xceptions(); + const vector<t_field*>& xceptions = xs->get_members(); + + for (auto xception : xceptions) { + indent(f_client_) << "P.maybe (P.return ()) X.throw (" + << field_name(resultname, xception->get_name()) << " res)" << endl; + } + + if (!(*f_iter)->get_returntype()->is_void()) + indent(f_client_) << "P.return $ " << field_name(resultname, "success") << " res" << endl; + else + indent(f_client_) << "P.return ()" << endl; + + // Close function + indent_down(); + indent_down(); + } + } + + f_client_.close(); +} + +/** + * Generates a service server definition. + * + * @param tservice The service to generate a server for. + */ +void t_hs_generator::generate_service_server(t_service* tservice) { + // Generate the dispatch methods + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + // Generate the process subfunctions + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) + generate_process_function(tservice, *f_iter); + + indent(f_service_) << "proc_ handler (iprot,oprot) (name,typ,seqid) = case name of" << endl; + indent_up(); + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + string fname = (*f_iter)->get_name(); + indent(f_service_) << "\"" << fname << "\" -> process_" << decapitalize(fname) + << " (seqid,iprot,oprot,handler)" << endl; + } + + indent(f_service_) << "_ -> "; + if (tservice->get_extends() != NULL) { + f_service_ << type_name(tservice->get_extends()) + << ".proc_ handler (iprot,oprot) (name,typ,seqid)" << endl; + + } else { + f_service_ << "do" << endl; + indent_up(); + indent(f_service_) << "_ <- T.readVal iprot (T.T_STRUCT Map.empty)" << endl; + indent(f_service_) << "T.writeMessage oprot (name,T.M_EXCEPTION,seqid) $" << endl; + indent_up(); + indent(f_service_) << "T.writeAppExn oprot (T.AppExn T.AE_UNKNOWN_METHOD (\"Unknown function " + "\" ++ LT.unpack name))" << endl; + indent_down(); + indent_down(); + } + + indent_down(); + + // Generate the server implementation + indent(f_service_) << "process handler (iprot, oprot) = do" << endl; + indent_up(); + + indent(f_service_) << "T.readMessage iprot (" << endl; + indent(f_service_) << " proc_ handler (iprot,oprot))" << endl; + indent(f_service_) << "P.return P.True" << endl; + indent_down(); +} + +bool hasNoArguments(t_function* func) { + return (func->get_arglist()->get_members().empty()); +} + +string t_hs_generator::render_hs_type_for_function_name(t_type* type) { + string type_str = render_hs_type(type, false); + std::string::size_type found = -1; + + while (true) { + found = type_str.find_first_of("[]. ", found + 1); + if (string::npos == size_t(found)) { + break; + } + + if (type_str[found] == '.') + type_str[found] = '_'; + else + type_str[found] = 'Z'; + } + return type_str; +} + +/** + * Generates a process function definition. + * + * @param tfunction The function to write a dispatcher for + */ +void t_hs_generator::generate_process_function(t_service* tservice, t_function* tfunction) { + (void)tservice; + // Open function + string funname = decapitalize(tfunction->get_name()); + indent(f_service_) << "process_" << funname << " (seqid, iprot, oprot, handler) = do" << endl; + indent_up(); + + string argsname = capitalize(tfunction->get_name()) + "_args"; + string resultname = capitalize(tfunction->get_name()) + "_result"; + + // Generate the function call + t_struct* arg_struct = tfunction->get_arglist(); + const vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + + indent(f_service_) << "args <- read_" << argsname << " iprot" << endl; + + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + + size_t n = xceptions.size() + 1; + // Try block for a function with exceptions + if (n > 0) { + for (size_t i = 0; i < n; i++) { + indent(f_service_) << "(X.catch" << endl; + indent_up(); + } + } + + if (n > 0) { + indent(f_service_) << "(do" << endl; + indent_up(); + } + indent(f_service_); + + if (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void()) + f_service_ << "val <- "; + + f_service_ << "Iface." << decapitalize(tfunction->get_name()) << " handler"; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + f_service_ << " (" << field_name(argsname, (*f_iter)->get_name()) << " args)"; + + if (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void()) { + f_service_ << endl; + indent(f_service_) << "let res = default_" << resultname << "{" + << field_name(resultname, "success") << " = val}"; + + } else if (!tfunction->is_oneway()) { + f_service_ << endl; + indent(f_service_) << "let res = default_" << resultname; + } + f_service_ << endl; + + // Shortcut out here for oneway functions + if (tfunction->is_oneway()) { + indent(f_service_) << "P.return ()"; + } else { + indent(f_service_) << "T.writeMessage oprot (\"" << tfunction->get_name() + << "\", T.M_REPLY, seqid) $" << endl; + indent_up(); + indent(f_service_) << "write_" << resultname << " oprot res"; + indent_down(); + } + if (n > 0) { + f_service_ << ")"; + indent_down(); + } + f_service_ << endl; + + if (n > 0) { + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + indent(f_service_) << "(\\e -> do" << endl; + indent_up(); + + if (!tfunction->is_oneway()) { + indent(f_service_) << "let res = default_" << resultname << "{" + << field_name(resultname, (*x_iter)->get_name()) << " = P.Just e}" + << endl; + indent(f_service_) << "T.writeMessage oprot (\"" << tfunction->get_name() + << "\", T.M_REPLY, seqid) $" << endl; + indent_up(); + indent(f_service_) << "write_" << resultname << " oprot res"; + indent_down(); + } else { + indent(f_service_) << "P.return ()"; + } + + f_service_ << "))" << endl; + indent_down(); + indent_down(); + } + indent(f_service_) << "((\\_ -> do" << endl; + indent_up(); + + if (!tfunction->is_oneway()) { + indent(f_service_) << "T.writeMessage oprot (\"" << tfunction->get_name() + << "\", T.M_EXCEPTION, seqid) $" << endl; + indent_up(); + indent(f_service_) << "T.writeAppExn oprot (T.AppExn T.AE_UNKNOWN \"\")"; + indent_down(); + } else { + indent(f_service_) << "P.return ()"; + } + + f_service_ << ") :: X.SomeException -> P.IO ()))" << endl; + indent_down(); + indent_down(); + } + // Close function + indent_down(); +} + +/** + * Deserializes a field of any type. + */ +void t_hs_generator::generate_deserialize_field(ostream& out, t_field* tfield, string prefix) { + (void)prefix; + t_type* type = tfield->get_type(); + generate_deserialize_type(out, type, prefix); +} + +/** + * Deserializes a field of any type. + */ +void t_hs_generator::generate_deserialize_type(ostream& out, t_type* type, string arg) { + type = get_true_type(type); + string val = tmp("_val"); + out << "(case " << arg << " of {" << type_to_constructor(type) << " " << val << " -> "; + + if (type->is_void()) + throw "CANNOT GENERATE DESERIALIZE CODE FOR void TYPE"; + + if (type->is_struct() || type->is_xception()) { + generate_deserialize_struct(out, (t_struct*)type, val); + + } else if (type->is_container()) { + generate_deserialize_container(out, type, val); + + } else if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + if (tbase == t_base_type::TYPE_STRING && !type->is_binary()) { + out << "E.decodeUtf8 "; + } + out << val; + if (type->is_binary()) { + // Since wire type of binary is the same as string, we actually receive T.TString not + // T.TBinary + out << "; T.TString " << val << " -> " << val; + } + } else if (type->is_enum()) { + out << "P.toEnum $ P.fromIntegral " << val; + + } else { + throw "DO NOT KNOW HOW TO DESERIALIZE TYPE " + type->get_name(); + } + out << "; _ -> P.error \"wrong type\"})"; +} + +/** + * Generates an unserializer for a struct, calling read() + */ +void t_hs_generator::generate_deserialize_struct(ostream& out, t_struct* tstruct, string name) { + + out << "(" << type_name(tstruct, "to_") << " (T.TStruct " << name << "))"; +} + +/** + * Serialize a container by writing out the header followed by + * data and then a footer. + */ +void t_hs_generator::generate_deserialize_container(ostream& out, t_type* ttype, string arg) { + + string val = tmp("_v"); + // Declare variables, read header + if (ttype->is_map()) { + string key = tmp("_k"); + out << "(Map.fromList $ P.map (\\(" << key << "," << val << ") -> ("; + generate_deserialize_type(out, ((t_map*)ttype)->get_key_type(), key); + + out << ","; + generate_deserialize_type(out, ((t_map*)ttype)->get_val_type(), val); + + out << ")) " << arg << ")"; + + } else if (ttype->is_set()) { + out << "(Set.fromList $ P.map (\\" << val << " -> "; + generate_deserialize_type(out, ((t_set*)ttype)->get_elem_type(), val); + out << ") " << arg << ")"; + + } else if (ttype->is_list()) { + out << "(Vector.fromList $ P.map (\\" << val << " -> "; + generate_deserialize_type(out, ((t_list*)ttype)->get_elem_type(), val); + out << ") " << arg << ")"; + } +} + +/** + * Serializes a field of any type. + * + * @param tfield The field to serialize + * @param prefix Name to prepend to field name + */ +void t_hs_generator::generate_serialize_type(ostream& out, t_type* type, string name) { + + type = get_true_type(type); + // Do nothing for void types + if (type->is_void()) + throw "CANNOT GENERATE SERIALIZE CODE FOR void TYPE"; + + if (type->is_struct() || type->is_xception()) { + generate_serialize_struct(out, (t_struct*)type, name); + + } else if (type->is_container()) { + generate_serialize_container(out, type, name); + + } else if (type->is_base_type() || type->is_enum()) { + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + out << type_to_constructor(type) << " "; + if (tbase == t_base_type::TYPE_STRING && !type->is_binary()) { + out << "$ E.encodeUtf8 "; + } + out << name; + + } else if (type->is_enum()) { + string ename = capitalize(type->get_name()); + out << "T.TI32 $ P.fromIntegral $ P.fromEnum " << name; + } + + } else { + throw "DO NOT KNOW HOW TO SERIALIZE FIELD OF TYPE " + type->get_name(); + } +} + +/** + * Serializes all the members of a struct. + * + * @param tstruct The struct to serialize + * @param prefix String prefix to attach to all fields + */ +void t_hs_generator::generate_serialize_struct(ostream& out, t_struct* tstruct, string prefix) { + out << type_name(tstruct, "from_") << " " << prefix; +} + +void t_hs_generator::generate_serialize_container(ostream& out, t_type* ttype, string prefix) { + string k = tmp("_k"); + string v = tmp("_v"); + + if (ttype->is_map()) { + t_type* ktype = ((t_map*)ttype)->get_key_type(); + t_type* vtype = ((t_map*)ttype)->get_val_type(); + out << "T.TMap " << type_to_enum(ktype) << " " << type_to_enum(vtype); + out << " $ P.map (\\(" << k << "," << v << ") -> ("; + generate_serialize_type(out, ktype, k); + out << ", "; + generate_serialize_type(out, vtype, v); + out << ")) $ Map.toList " << prefix; + + } else if (ttype->is_set()) { + out << "T.TSet " << type_to_enum(((t_set*)ttype)->get_elem_type()); + out << " $ P.map (\\" << v << " -> "; + generate_serialize_type(out, ((t_set*)ttype)->get_elem_type(), v); + out << ") $ Set.toList " << prefix; + + } else if (ttype->is_list()) { + out << "T.TList " << type_to_enum(((t_list*)ttype)->get_elem_type()); + out << " $ P.map (\\" << v << " -> "; + generate_serialize_type(out, ((t_list*)ttype)->get_elem_type(), v); + out << ") $ Vector.toList " << prefix; + } +} + +string t_hs_generator::function_type(t_function* tfunc, bool options, bool io, bool method) { + string result = ""; + + const vector<t_field*>& fields = tfunc->get_arglist()->get_members(); + for (auto field : fields) { + if (field->get_req() == t_field::T_OPTIONAL + || ((t_type*)field->get_type())->is_xception()) + result += "P.Maybe "; + result += render_hs_type(field->get_type(), options); + result += " -> "; + } + + if (fields.empty() && !method) + result += "() -> "; + + if (io) + result += "P.IO "; + + result += render_hs_type(tfunc->get_returntype(), io); + return result; +} + +string t_hs_generator::type_name(t_type* ttype, string function_prefix) { + string prefix = ""; + t_program* program = ttype->get_program(); + + if (program != NULL && program != program_) + if (!ttype->is_service()) + prefix = capitalize(program->get_name()) + "_Types."; + + return prefix + function_prefix + capitalize(ttype->get_name()); +} + +string t_hs_generator::field_name(string tname, string fname) { + return decapitalize(tname) + "_" + fname; +} + +/** + * Converts the parse type to a Protocol.t_type enum + */ +string t_hs_generator::type_to_enum(t_type* type) { + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + return "T.T_VOID"; + case t_base_type::TYPE_STRING: + return type->is_binary() ? "T.T_BINARY" : "T.T_STRING"; + case t_base_type::TYPE_BOOL: + return "T.T_BOOL"; + case t_base_type::TYPE_I8: + return "T.T_BYTE"; + case t_base_type::TYPE_I16: + return "T.T_I16"; + case t_base_type::TYPE_I32: + return "T.T_I32"; + case t_base_type::TYPE_I64: + return "T.T_I64"; + case t_base_type::TYPE_DOUBLE: + return "T.T_DOUBLE"; + } + + } else if (type->is_enum()) { + return "T.T_I32"; + + } else if (type->is_struct() || type->is_xception()) { + return "(T.T_STRUCT " + type_name((t_struct*)type, "typemap_") + ")"; + + } else if (type->is_map()) { + string ktype = type_to_enum(((t_map*)type)->get_key_type()); + string vtype = type_to_enum(((t_map*)type)->get_val_type()); + return "(T.T_MAP " + ktype + " " + vtype + ")"; + + } else if (type->is_set()) { + return "(T.T_SET " + type_to_enum(((t_set*)type)->get_elem_type()) + ")"; + + } else if (type->is_list()) { + return "(T.T_LIST " + type_to_enum(((t_list*)type)->get_elem_type()) + ")"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +/** + * Converts the parse type to a default value + */ +string t_hs_generator::type_to_default(t_type* type) { + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + return "P.error \"No default value for type T_VOID\""; + case t_base_type::TYPE_STRING: + return "\"\""; + case t_base_type::TYPE_BOOL: + return "P.False"; + case t_base_type::TYPE_I8: + return "0"; + case t_base_type::TYPE_I16: + return "0"; + case t_base_type::TYPE_I32: + return "0"; + case t_base_type::TYPE_I64: + return "0"; + case t_base_type::TYPE_DOUBLE: + return "0"; + } + + } else if (type->is_enum()) { + return "(P.toEnum 0)"; + + } else if (type->is_struct() || type->is_xception()) { + return type_name((t_struct*)type, "default_"); + + } else if (type->is_map()) { + return "Map.empty"; + + } else if (type->is_set()) { + return "Set.empty"; + + } else if (type->is_list()) { + return "Vector.empty"; + } + + throw "INVALID TYPE IN type_to_default: " + type->get_name(); +} + +/** + * Converts the parse type to an haskell type + */ +string t_hs_generator::render_hs_type(t_type* type, bool needs_parens) { + type = get_true_type(type); + string type_repr; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + return "()"; + case t_base_type::TYPE_STRING: + return (type->is_binary() ? "LBS.ByteString" : "LT.Text"); + case t_base_type::TYPE_BOOL: + return "P.Bool"; + case t_base_type::TYPE_I8: + return "I.Int8"; + case t_base_type::TYPE_I16: + return "I.Int16"; + case t_base_type::TYPE_I32: + return "I.Int32"; + case t_base_type::TYPE_I64: + return "I.Int64"; + case t_base_type::TYPE_DOUBLE: + return "P.Double"; + } + + } else if (type->is_enum()) { + return type_name((t_enum*)type); + + } else if (type->is_struct() || type->is_xception()) { + return type_name((t_struct*)type); + + } else if (type->is_map()) { + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + type_repr = "Map.HashMap " + render_hs_type(ktype, true) + " " + render_hs_type(vtype, true); + + } else if (type->is_set()) { + t_type* etype = ((t_set*)type)->get_elem_type(); + type_repr = "Set.HashSet " + render_hs_type(etype, true); + + } else if (type->is_list()) { + t_type* etype = ((t_list*)type)->get_elem_type(); + type_repr = "Vector.Vector " + render_hs_type(etype, true); + + } else { + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); + } + + return needs_parens ? "(" + type_repr + ")" : type_repr; +} + +/** + * Converts the parse type to a haskell constructor + */ +string t_hs_generator::type_to_constructor(t_type* type) { + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "invalid type: T_VOID"; + case t_base_type::TYPE_STRING: + return type->is_binary() ? "T.TBinary" : "T.TString"; + case t_base_type::TYPE_BOOL: + return "T.TBool"; + case t_base_type::TYPE_I8: + return "T.TByte"; + case t_base_type::TYPE_I16: + return "T.TI16"; + case t_base_type::TYPE_I32: + return "T.TI32"; + case t_base_type::TYPE_I64: + return "T.TI64"; + case t_base_type::TYPE_DOUBLE: + return "T.TDouble"; + } + + } else if (type->is_enum()) { + return "T.TI32"; + + } else if (type->is_struct() || type->is_xception()) { + return "T.TStruct"; + + } else if (type->is_map()) { + return "T.TMap _ _"; + + } else if (type->is_set()) { + return "T.TSet _"; + + } else if (type->is_list()) { + return "T.TList _"; + } + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +THRIFT_REGISTER_GENERATOR(hs, "Haskell", "") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_html_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_html_generator.cc new file mode 100644 index 000000000..b61a712e3 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_html_generator.cc @@ -0,0 +1,1084 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <string> +#include <fstream> +#include <iostream> +#include <vector> +#include <map> + +#include <stdlib.h> +#include <sys/stat.h> +#include <sstream> +#include "thrift/platform.h" +#include "thrift/generate/t_generator.h" +#include "thrift/generate/t_html_generator.h" + +using std::map; +using std::ofstream; +using std::ostringstream; +using std::pair; +using std::string; +using std::stringstream; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes + +enum input_type { INPUT_UNKNOWN, INPUT_UTF8, INPUT_PLAIN }; + +/** + * HTML code generator + * + * mostly copy/pasting/tweaking from mcslee's work. + */ +class t_html_generator : public t_generator { +public: + t_html_generator(t_program* program, + const std::map<std::string, std::string>& parsed_options, + const std::string& option_string) + : t_generator(program) { + (void)option_string; + std::map<std::string, std::string>::const_iterator iter; + + standalone_ = false; + unsafe_ = false; + for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + if( iter->first.compare("standalone") == 0) { + standalone_ = true; + } else if( iter->first.compare("noescape") == 0) { + unsafe_ = true; + } else { + throw "unknown option html:" + iter->first; + } + } + + + out_dir_base_ = "gen-html"; + input_type_ = INPUT_UNKNOWN; + + escape_.clear(); + escape_['&'] = "&"; + escape_['<'] = "<"; + escape_['>'] = ">"; + escape_['"'] = """; + escape_['\''] = "'"; + + init_allowed__markup(); + } + + void generate_program() override; + void generate_program_toc(); + void generate_program_toc_row(t_program* tprog); + void generate_program_toc_rows(t_program* tprog, std::vector<t_program*>& finished); + void generate_index(); + std::string escape_html(std::string const& str); + std::string escape_html_tags(std::string const& str); + void generate_css(); + void generate_css_content(std::ostream& f_target); + void generate_style_tag(); + std::string make_file_link(std::string name); + bool is_utf8_sequence(std::string const& str, size_t firstpos); + void detect_input_encoding(std::string const& str, size_t firstpos); + void init_allowed__markup(); + + /** + * Program-level generation functions + */ + + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_const(t_const* tconst) override; + void generate_struct(t_struct* tstruct) override; + void generate_service(t_service* tservice) override; + void generate_xception(t_struct* txception) override; + + void print_doc(t_doc* tdoc); + int print_type(t_type* ttype); + void print_const_value(t_type* type, t_const_value* tvalue); + void print_fn_args_doc(t_function* tfunction); + +private: + ofstream_with_content_based_conditional_update f_out_; + std::string current_file_; + input_type input_type_; + std::map<std::string, int> allowed_markup; + bool standalone_; + bool unsafe_; +}; + +/** + * Emits the Table of Contents links at the top of the module's page + */ +void t_html_generator::generate_program_toc() { + f_out_ << "<table class=\"table-bordered table-striped " + "table-condensed\"><thead><tr><th>Module</th><th>Services</th>" + << "<th>Data types</th><th>Constants</th></tr></thead><tbody>" << endl; + generate_program_toc_row(program_); + f_out_ << "</tbody></table>" << endl; +} + +/** + * Recurses through from the provided program and generates a ToC row + * for each discovered program exactly once by maintaining the list of + * completed rows in 'finished' + */ +void t_html_generator::generate_program_toc_rows(t_program* tprog, + std::vector<t_program*>& finished) { + for (auto & iter : finished) { + if (tprog->get_path() == iter->get_path()) { + return; + } + } + finished.push_back(tprog); + generate_program_toc_row(tprog); + vector<t_program*> includes = tprog->get_includes(); + for (auto & include : includes) { + generate_program_toc_rows(include, finished); + } +} + +/** + * Emits the Table of Contents links at the top of the module's page + */ +void t_html_generator::generate_program_toc_row(t_program* tprog) { + string fname = tprog->get_name() + ".html"; + f_out_ << "<tr>" << endl << "<td>" << tprog->get_name() << "</td><td>"; + if (!tprog->get_services().empty()) { + vector<t_service*> services = tprog->get_services(); + vector<t_service*>::iterator sv_iter; + for (sv_iter = services.begin(); sv_iter != services.end(); ++sv_iter) { + string name = get_service_name(*sv_iter); + f_out_ << "<a href=\"" << make_file_link(fname) << "#Svc_" << name << "\">" << name + << "</a><br/>" << endl; + f_out_ << "<ul>" << endl; + map<string, string> fn_html; + vector<t_function*> functions = (*sv_iter)->get_functions(); + vector<t_function*>::iterator fn_iter; + for (fn_iter = functions.begin(); fn_iter != functions.end(); ++fn_iter) { + string fn_name = (*fn_iter)->get_name(); + string html = "<li><a href=\"" + make_file_link(fname) + "#Fn_" + name + "_" + fn_name + + "\">" + fn_name + "</a></li>"; + fn_html.insert(pair<string, string>(fn_name, html)); + } + for (auto & html_iter : fn_html) { + f_out_ << html_iter.second << endl; + } + f_out_ << "</ul>" << endl; + } + } + f_out_ << "</td>" << endl << "<td>"; + map<string, string> data_types; + if (!tprog->get_enums().empty()) { + vector<t_enum*> enums = tprog->get_enums(); + vector<t_enum*>::iterator en_iter; + for (en_iter = enums.begin(); en_iter != enums.end(); ++en_iter) { + string name = (*en_iter)->get_name(); + // f_out_ << "<a href=\"" << make_file_link(fname) << "#Enum_" << name << "\">" << name + // << "</a><br/>" << endl; + string html = "<a href=\"" + make_file_link(fname) + "#Enum_" + name + "\">" + name + "</a>"; + data_types.insert(pair<string, string>(name, html)); + } + } + if (!tprog->get_typedefs().empty()) { + vector<t_typedef*> typedefs = tprog->get_typedefs(); + vector<t_typedef*>::iterator td_iter; + for (td_iter = typedefs.begin(); td_iter != typedefs.end(); ++td_iter) { + string name = (*td_iter)->get_symbolic(); + // f_out_ << "<a href=\"" << make_file_link(fname) << "#Typedef_" << name << "\">" << name + // << "</a><br/>" << endl; + string html = "<a href=\"" + make_file_link(fname) + "#Typedef_" + name + "\">" + name + + "</a>"; + data_types.insert(pair<string, string>(name, html)); + } + } + if (!tprog->get_objects().empty()) { + vector<t_struct*> objects = tprog->get_objects(); + vector<t_struct*>::iterator o_iter; + for (o_iter = objects.begin(); o_iter != objects.end(); ++o_iter) { + string name = (*o_iter)->get_name(); + // f_out_ << "<a href=\"" << make_file_link(fname) << "#Struct_" << name << "\">" << name + //<< "</a><br/>" << endl; + string html = "<a href=\"" + make_file_link(fname) + "#Struct_" + name + "\">" + name + + "</a>"; + data_types.insert(pair<string, string>(name, html)); + } + } + for (auto & data_type : data_types) { + f_out_ << data_type.second << "<br/>" << endl; + } + f_out_ << "</td>" << endl << "<td>"; + if (!tprog->get_consts().empty()) { + map<string, string> const_html; + vector<t_const*> consts = tprog->get_consts(); + vector<t_const*>::iterator con_iter; + for (con_iter = consts.begin(); con_iter != consts.end(); ++con_iter) { + string name = (*con_iter)->get_name(); + string html = "<code><a href=\"" + make_file_link(fname) + "#Const_" + name + "\">" + name + + "</a></code>"; + const_html.insert(pair<string, string>(name, html)); + } + for (auto & con_iter : const_html) { + f_out_ << con_iter.second << "<br/>" << endl; + } + } + f_out_ << "</td>" << endl << "</tr>"; +} + +/** + * Prepares for file generation by opening up the necessary file output + * stream. + */ +void t_html_generator::generate_program() { + // Make output directory + MKDIR(get_out_dir().c_str()); + current_file_ = program_->get_name() + ".html"; + string fname = get_out_dir() + current_file_; + f_out_.open(fname.c_str()); + f_out_ << "<!DOCTYPE html>" << endl; + f_out_ << "<html lang=\"en\">" << endl; + f_out_ << "<head>" << endl; + f_out_ << "<meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\" />" << endl; + generate_style_tag(); + f_out_ << "<title>Thrift module: " << program_->get_name() << "</title></head><body>" << endl + << "<div class=\"container-fluid\">" << endl + << "<h1>Thrift module: " << program_->get_name() << "</h1>" << endl; + + print_doc(program_); + + generate_program_toc(); + + if (!program_->get_consts().empty()) { + f_out_ << "<hr/><h2 id=\"Constants\">Constants</h2>" << endl; + vector<t_const*> consts = program_->get_consts(); + f_out_ << "<table class=\"table-bordered table-striped table-condensed\">"; + f_out_ << "<thead><tr><th>Constant</th><th>Type</th><th>Value</th></tr></thead><tbody>" << endl; + generate_consts(consts); + f_out_ << "</tbody></table>"; + } + + if (!program_->get_enums().empty()) { + f_out_ << "<hr/><h2 id=\"Enumerations\">Enumerations</h2>" << endl; + // Generate enums + vector<t_enum*> enums = program_->get_enums(); + vector<t_enum*>::iterator en_iter; + for (en_iter = enums.begin(); en_iter != enums.end(); ++en_iter) { + generate_enum(*en_iter); + } + } + + if (!program_->get_typedefs().empty()) { + f_out_ << "<hr/><h2 id=\"Typedefs\">Type declarations</h2>" << endl; + // Generate typedefs + vector<t_typedef*> typedefs = program_->get_typedefs(); + vector<t_typedef*>::iterator td_iter; + for (td_iter = typedefs.begin(); td_iter != typedefs.end(); ++td_iter) { + generate_typedef(*td_iter); + } + } + + if (!program_->get_objects().empty()) { + f_out_ << "<hr/><h2 id=\"Structs\">Data structures</h2>" << endl; + // Generate structs and exceptions in declared order + vector<t_struct*> objects = program_->get_objects(); + vector<t_struct*>::iterator o_iter; + for (o_iter = objects.begin(); o_iter != objects.end(); ++o_iter) { + if ((*o_iter)->is_xception()) { + generate_xception(*o_iter); + } else { + generate_struct(*o_iter); + } + } + } + + if (!program_->get_services().empty()) { + f_out_ << "<hr/><h2 id=\"Services\">Services</h2>" << endl; + // Generate services + vector<t_service*> services = program_->get_services(); + vector<t_service*>::iterator sv_iter; + for (sv_iter = services.begin(); sv_iter != services.end(); ++sv_iter) { + service_name_ = get_service_name(*sv_iter); + generate_service(*sv_iter); + } + } + + f_out_ << "</div></body></html>" << endl; + f_out_.close(); + + generate_index(); + generate_css(); +} + +/** + * Emits the index.html file for the recursive set of Thrift programs + */ +void t_html_generator::generate_index() { + current_file_ = "index.html"; + string index_fname = get_out_dir() + current_file_; + f_out_.open(index_fname.c_str()); + f_out_ << "<!DOCTYPE html>" << endl << "<html lang=\"en\"><head>" << endl; + generate_style_tag(); + f_out_ << "<title>All Thrift declarations</title></head><body>" << endl + << "<div class=\"container-fluid\">" << endl << "<h1>All Thrift declarations</h1>" << endl; + f_out_ << "<table class=\"table-bordered table-striped " + "table-condensed\"><thead><tr><th>Module</th><th>Services</th><th>Data types</th>" + << "<th>Constants</th></tr></thead><tbody>" << endl; + vector<t_program*> programs; + generate_program_toc_rows(program_, programs); + f_out_ << "</tbody></table>" << endl; + f_out_ << "</div></body></html>" << endl; + f_out_.close(); +} + +void t_html_generator::generate_css() { + if (!standalone_) { + current_file_ = "style.css"; + string css_fname = get_out_dir() + current_file_; + f_out_.open(css_fname.c_str()); + generate_css_content(f_out_); + f_out_.close(); + } +} + +void t_html_generator::generate_css_content(std::ostream& f_target) { + f_target << BOOTSTRAP_CSS() << endl; + f_target << "/* Auto-generated CSS for generated Thrift docs */" << endl; + f_target << "h3, h4 { margin-bottom: 6px; }" << endl; + f_target << "div.definition { border: 1px solid #CCC; margin-bottom: 10px; padding: 10px; }" + << endl; + f_target << "div.extends { margin: -0.5em 0 1em 5em }" << endl; + f_target << "td { vertical-align: top; }" << endl; + f_target << "table { empty-cells: show; }" << endl; + f_target << "code { line-height: 20px; }" << endl; + f_target << ".table-bordered th, .table-bordered td { border-bottom: 1px solid #DDDDDD; }" + << endl; +} + +/** + * Generates the CSS tag. + * Depending on "standalone", either a CSS file link (default), or the entire CSS is embedded + * inline. + */ +void t_html_generator::generate_style_tag() { + if (!standalone_) { + f_out_ << "<link href=\"style.css\" rel=\"stylesheet\" type=\"text/css\"/>" << endl; + } else { + f_out_ << "<style type=\"text/css\"/><!--" << endl; + generate_css_content(f_out_); + f_out_ << "--></style>" << endl; + } +} + +/** + * Returns the target file for a <a href> link + * The returned string is empty, whenever filename refers to the current file. + */ +std::string t_html_generator::make_file_link(std::string filename) { + return (current_file_.compare(filename) != 0) ? filename : ""; +} + +/** + * If the provided documentable object has documentation attached, this + * will emit it to the output stream in HTML format. + */ +void t_html_generator::print_doc(t_doc* tdoc) { + if (tdoc->has_doc()) { + if (unsafe_) { + f_out_ << tdoc->get_doc() << "<br/>"; + } else { + f_out_ << "<pre>" << escape_html(tdoc->get_doc()) << "</pre><br/>"; + } + } +} + +bool t_html_generator::is_utf8_sequence(std::string const& str, size_t firstpos) { + // leading char determines the length of the sequence + unsigned char c = str.at(firstpos); + int count = 0; + if ((c & 0xE0) == 0xC0) { + count = 1; + } else if ((c & 0xF0) == 0xE0) { + count = 2; + } else if ((c & 0xF8) == 0xF0) { + count = 3; + } else if ((c & 0xFC) == 0xF8) { + count = 4; + } else if ((c & 0xFE) == 0xFC) { + count = 5; + } else { + // pdebug("UTF-8 test: char '%c' (%d) is not a valid UTF-8 leading byte", c, int(c)); + return false; // no UTF-8 + } + + // following chars + size_t pos = firstpos + 1; + while ((pos < str.length()) && (0 < count)) { + c = str.at(pos); + if ((c & 0xC0) != 0x80) { + // pdebug("UTF-8 test: char '%c' (%d) is not a valid UTF-8 following byte", c, int(c)); + return false; // no UTF-8 + } + --count; + ++pos; + } + + // true if the sequence is complete + return (0 == count); +} + +void t_html_generator::detect_input_encoding(std::string const& str, size_t firstpos) { + if (is_utf8_sequence(str, firstpos)) { + pdebug("Input seems to be already UTF-8 encoded"); + input_type_ = INPUT_UTF8; + return; + } + + // fallback + pwarning(1, "Input is not UTF-8, treating as plain ANSI"); + input_type_ = INPUT_PLAIN; +} + +void t_html_generator::init_allowed__markup() { + allowed_markup.clear(); + // standalone tags + allowed_markup["br"] = 1; + allowed_markup["br/"] = 1; + allowed_markup["img"] = 1; + // paired tags + allowed_markup["b"] = 1; + allowed_markup["/b"] = 1; + allowed_markup["u"] = 1; + allowed_markup["/u"] = 1; + allowed_markup["i"] = 1; + allowed_markup["/i"] = 1; + allowed_markup["s"] = 1; + allowed_markup["/s"] = 1; + allowed_markup["big"] = 1; + allowed_markup["/big"] = 1; + allowed_markup["small"] = 1; + allowed_markup["/small"] = 1; + allowed_markup["sup"] = 1; + allowed_markup["/sup"] = 1; + allowed_markup["sub"] = 1; + allowed_markup["/sub"] = 1; + allowed_markup["pre"] = 1; + allowed_markup["/pre"] = 1; + allowed_markup["tt"] = 1; + allowed_markup["/tt"] = 1; + allowed_markup["ul"] = 1; + allowed_markup["/ul"] = 1; + allowed_markup["ol"] = 1; + allowed_markup["/ol"] = 1; + allowed_markup["li"] = 1; + allowed_markup["/li"] = 1; + allowed_markup["a"] = 1; + allowed_markup["/a"] = 1; + allowed_markup["p"] = 1; + allowed_markup["/p"] = 1; + allowed_markup["code"] = 1; + allowed_markup["/code"] = 1; + allowed_markup["dl"] = 1; + allowed_markup["/dl"] = 1; + allowed_markup["dt"] = 1; + allowed_markup["/dt"] = 1; + allowed_markup["dd"] = 1; + allowed_markup["/dd"] = 1; + allowed_markup["h1"] = 1; + allowed_markup["/h1"] = 1; + allowed_markup["h2"] = 1; + allowed_markup["/h2"] = 1; + allowed_markup["h3"] = 1; + allowed_markup["/h3"] = 1; + allowed_markup["h4"] = 1; + allowed_markup["/h4"] = 1; + allowed_markup["h5"] = 1; + allowed_markup["/h5"] = 1; + allowed_markup["h6"] = 1; + allowed_markup["/h6"] = 1; +} + +std::string t_html_generator::escape_html_tags(std::string const& str) { + std::ostringstream result; + + unsigned char c = '?'; + size_t lastpos; + size_t firstpos = 0; + while (firstpos < str.length()) { + + // look for non-ASCII char + lastpos = firstpos; + while (lastpos < str.length()) { + c = str.at(lastpos); + if (('<' == c) || ('>' == c)) { + break; + } + ++lastpos; + } + + // copy what we got so far + if (lastpos > firstpos) { + result << str.substr(firstpos, lastpos - firstpos); + firstpos = lastpos; + } + + // reached the end? + if (firstpos >= str.length()) { + break; + } + + // tag end without corresponding begin + ++firstpos; + if ('>' == c) { + result << ">"; + continue; + } + + // extract the tag + std::ostringstream tagstream; + while (firstpos < str.length()) { + c = str.at(firstpos); + ++firstpos; + if ('<' == c) { + tagstream << "<"; // nested begin? + } else if ('>' == c) { + break; + } else { + tagstream << c; // not very efficient, but tags should be quite short + } + } + + // we allow for several markup in docstrings, all else will become escaped + string tag_content = tagstream.str(); + string tag_key = tag_content; + size_t first_white = tag_key.find_first_of(" \t\f\v\n\r"); + if (first_white != string::npos) { + tag_key.erase(first_white); + } + for (char & i : tag_key) { + i = tolower(i); + } + if (allowed_markup.find(tag_key) != allowed_markup.end()) { + result << "<" << tag_content << ">"; + } else { + result << "<" << tagstream.str() << ">"; + pverbose("illegal markup <%s> in doc-comment\n", tag_key.c_str()); + } + } + + return result.str(); +} + +std::string t_html_generator::escape_html(std::string const& str) { + // the generated HTML header says it is UTF-8 encoded + // if UTF-8 input has been detected before, we don't need to change anything + if (input_type_ == INPUT_UTF8) { + return escape_html_tags(str); + } + + // convert unsafe chars to their &#<num>; equivalent + std::ostringstream result; + unsigned char c = '?'; + unsigned int ic = 0; + size_t lastpos; + size_t firstpos = 0; + while (firstpos < str.length()) { + + // look for non-ASCII char + lastpos = firstpos; + while (lastpos < str.length()) { + c = str.at(lastpos); + ic = c; + if ((32 > ic) || (127 < ic)) { + break; + } + ++lastpos; + } + + // copy what we got so far + if (lastpos > firstpos) { + result << str.substr(firstpos, lastpos - firstpos); + firstpos = lastpos; + } + + // reached the end? + if (firstpos >= str.length()) { + break; + } + + // some control code? + if (ic <= 31) { + switch (c) { + case '\r': + case '\n': + case '\t': + result << c; + break; + default: // silently consume all other ctrl chars + break; + } + ++firstpos; + continue; + } + + // reached the end? + if (firstpos >= str.length()) { + break; + } + + // try to detect input encoding + if (input_type_ == INPUT_UNKNOWN) { + detect_input_encoding(str, firstpos); + if (input_type_ == INPUT_UTF8) { + lastpos = str.length(); + result << str.substr(firstpos, lastpos - firstpos); + break; + } + } + + // convert the character to something useful based on the detected encoding + switch (input_type_) { + case INPUT_PLAIN: + result << "&#" << ic << ";"; + ++firstpos; + break; + default: + throw "Unexpected or unrecognized input encoding"; + } + } + + return escape_html_tags(result.str()); +} + +/** + * Prints out the provided type in HTML + */ +int t_html_generator::print_type(t_type* ttype) { + std::string::size_type len = 0; + f_out_ << "<code>"; + if (ttype->is_container()) { + if (ttype->is_list()) { + f_out_ << "list<"; + len = 6 + print_type(((t_list*)ttype)->get_elem_type()); + f_out_ << ">"; + } else if (ttype->is_set()) { + f_out_ << "set<"; + len = 5 + print_type(((t_set*)ttype)->get_elem_type()); + f_out_ << ">"; + } else if (ttype->is_map()) { + f_out_ << "map<"; + len = 5 + print_type(((t_map*)ttype)->get_key_type()); + f_out_ << ", "; + len += print_type(((t_map*)ttype)->get_val_type()); + f_out_ << ">"; + } + } else if (ttype->is_base_type()) { + f_out_ << (ttype->is_binary() ? "binary" : ttype->get_name()); + len = ttype->get_name().size(); + } else { + string prog_name = ttype->get_program()->get_name(); + string type_name = ttype->get_name(); + f_out_ << "<a href=\"" << make_file_link(prog_name + ".html") << "#"; + if (ttype->is_typedef()) { + f_out_ << "Struct_"; + } else if (ttype->is_struct() || ttype->is_xception()) { + f_out_ << "Struct_"; + } else if (ttype->is_enum()) { + f_out_ << "Enum_"; + } else if (ttype->is_service()) { + f_out_ << "Svc_"; + } + f_out_ << type_name << "\">"; + len = type_name.size(); + if (ttype->get_program() != program_) { + f_out_ << prog_name << "."; + len += prog_name.size() + 1; + } + f_out_ << type_name << "</a>"; + } + f_out_ << "</code>"; + return (int)len; +} + +/** + * Prints out an HTML representation of the provided constant value + */ +void t_html_generator::print_const_value(t_type* type, t_const_value* tvalue) { + + // if tvalue is an identifier, the constant content is already shown elsewhere + if (tvalue->get_type() == t_const_value::CV_IDENTIFIER) { + string fname = program_->get_name() + ".html"; + string name = escape_html(tvalue->get_identifier()); + f_out_ << "<code><a href=\"" + make_file_link(fname) + "#Const_" + name + "\">" + name + + "</a></code>"; + return; + } + + t_type* truetype = type; + while (truetype->is_typedef()) { + truetype = ((t_typedef*)truetype)->get_type(); + } + + bool first = true; + if (truetype->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)truetype)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + f_out_ << '"' << escape_html(get_escaped_string(tvalue)) << '"'; + break; + case t_base_type::TYPE_BOOL: + f_out_ << ((tvalue->get_integer() != 0) ? "true" : "false"); + break; + case t_base_type::TYPE_I8: + f_out_ << tvalue->get_integer(); + break; + case t_base_type::TYPE_I16: + f_out_ << tvalue->get_integer(); + break; + case t_base_type::TYPE_I32: + f_out_ << tvalue->get_integer(); + break; + case t_base_type::TYPE_I64: + f_out_ << tvalue->get_integer(); + break; + case t_base_type::TYPE_DOUBLE: + if (tvalue->get_type() == t_const_value::CV_INTEGER) { + f_out_ << tvalue->get_integer(); + } else { + f_out_ << tvalue->get_double(); + } + break; + default: + f_out_ << "UNKNOWN BASE TYPE"; + break; + } + } else if (truetype->is_enum()) { + f_out_ << escape_html(truetype->get_name()) << "." + << escape_html(tvalue->get_identifier_name()); + } else if (truetype->is_struct() || truetype->is_xception()) { + f_out_ << "{ "; + const vector<t_field*>& fields = ((t_struct*)truetype)->get_members(); + vector<t_field*>::const_iterator f_iter; + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = tvalue->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + t_type* field_type = NULL; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field_type = (*f_iter)->get_type(); + } + } + if (field_type == NULL) { + throw "type error: " + truetype->get_name() + " has no field " + + v_iter->first->get_string(); + } + if (!first) { + f_out_ << ", "; + } + first = false; + f_out_ << escape_html(v_iter->first->get_string()) << " = "; + print_const_value(field_type, v_iter->second); + } + f_out_ << " }"; + } else if (truetype->is_map()) { + f_out_ << "{ "; + map<t_const_value*, t_const_value*, t_const_value::value_compare> map_elems = tvalue->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::iterator map_iter; + for (map_iter = map_elems.begin(); map_iter != map_elems.end(); map_iter++) { + if (!first) { + f_out_ << ", "; + } + first = false; + print_const_value(((t_map*)truetype)->get_key_type(), map_iter->first); + f_out_ << " = "; + print_const_value(((t_map*)truetype)->get_val_type(), map_iter->second); + } + f_out_ << " }"; + } else if (truetype->is_list()) { + f_out_ << "{ "; + vector<t_const_value*> list_elems = tvalue->get_list(); + ; + vector<t_const_value*>::iterator list_iter; + for (list_iter = list_elems.begin(); list_iter != list_elems.end(); list_iter++) { + if (!first) { + f_out_ << ", "; + } + first = false; + print_const_value(((t_list*)truetype)->get_elem_type(), *list_iter); + } + f_out_ << " }"; + } else if (truetype->is_set()) { + f_out_ << "{ "; + vector<t_const_value*> list_elems = tvalue->get_list(); + ; + vector<t_const_value*>::iterator list_iter; + for (list_iter = list_elems.begin(); list_iter != list_elems.end(); list_iter++) { + if (!first) { + f_out_ << ", "; + } + first = false; + print_const_value(((t_set*)truetype)->get_elem_type(), *list_iter); + } + f_out_ << " }"; + } else { + f_out_ << "UNKNOWN TYPE"; + } +} + +/** + * Prints out documentation for arguments/exceptions of a function, if any documentation has been + * supplied. + */ +void t_html_generator::print_fn_args_doc(t_function* tfunction) { + bool has_docs = false; + vector<t_field*> args = tfunction->get_arglist()->get_members(); + vector<t_field*>::iterator arg_iter = args.begin(); + if (arg_iter != args.end()) { + for (; arg_iter != args.end(); arg_iter++) { + if ((*arg_iter)->has_doc() && !(*arg_iter)->get_doc().empty()) + has_docs = true; + } + if (has_docs) { + arg_iter = args.begin(); + f_out_ << "<br/><h4 id=\"Parameters_" << service_name_ << "_" << tfunction->get_name() + << "\">Parameters</h4>" << endl; + f_out_ << "<table class=\"table-bordered table-striped table-condensed\">"; + f_out_ << "<thead><tr><th>Name</th><th>Description</th></tr></thead><tbody>"; + for (; arg_iter != args.end(); arg_iter++) { + f_out_ << "<tr><td>" << (*arg_iter)->get_name(); + f_out_ << "</td><td>"; + f_out_ << escape_html((*arg_iter)->get_doc()); + f_out_ << "</td></tr>" << endl; + } + f_out_ << "</tbody></table>"; + } + } + + has_docs = false; + vector<t_field*> excepts = tfunction->get_xceptions()->get_members(); + vector<t_field*>::iterator ex_iter = excepts.begin(); + if (ex_iter != excepts.end()) { + for (; ex_iter != excepts.end(); ex_iter++) { + if ((*ex_iter)->has_doc() && !(*ex_iter)->get_doc().empty()) + has_docs = true; + } + if (has_docs) { + ex_iter = excepts.begin(); + f_out_ << "<br/><h4 id=\"Exceptions_" << service_name_ << "_" << tfunction->get_name() + << "\">Exceptions</h4>" << endl; + f_out_ << "<table class=\"table-bordered table-striped table-condensed\">"; + f_out_ << "<thead><tr><th>Type</th><th>Description</th></tr></thead><tbody>"; + for (; ex_iter != excepts.end(); ex_iter++) { + f_out_ << "<tr><td>" << (*ex_iter)->get_type()->get_name(); + f_out_ << "</td><td>"; + f_out_ << escape_html((*ex_iter)->get_doc()); + f_out_ << "</td></tr>" << endl; + } + f_out_ << "</tbody></table>"; + } + } +} + +/** + * Generates a typedef. + * + * @param ttypedef The type definition + */ +void t_html_generator::generate_typedef(t_typedef* ttypedef) { + string name = ttypedef->get_name(); + f_out_ << "<div class=\"definition\">"; + f_out_ << "<h3 id=\"Typedef_" << name << "\">Typedef: " << name << "</h3>" << endl; + f_out_ << "<p><strong>Base type:</strong> "; + print_type(ttypedef->get_type()); + f_out_ << "</p>" << endl; + print_doc(ttypedef); + f_out_ << "</div>" << endl; +} + +/** + * Generates code for an enumerated type. + * + * @param tenum The enumeration + */ +void t_html_generator::generate_enum(t_enum* tenum) { + string name = tenum->get_name(); + f_out_ << "<div class=\"definition\">"; + f_out_ << "<h3 id=\"Enum_" << name << "\">Enumeration: " << name << "</h3>" << endl; + print_doc(tenum); + vector<t_enum_value*> values = tenum->get_constants(); + vector<t_enum_value*>::iterator val_iter; + f_out_ << "<br/><table class=\"table-bordered table-striped table-condensed\">" << endl; + for (val_iter = values.begin(); val_iter != values.end(); ++val_iter) { + f_out_ << "<tr><td><code>"; + f_out_ << (*val_iter)->get_name(); + f_out_ << "</code></td><td><code>"; + f_out_ << (*val_iter)->get_value(); + f_out_ << "</code></td><td>" << endl; + print_doc((*val_iter)); + f_out_ << "</td></tr>" << endl; + } + f_out_ << "</table></div>" << endl; +} + +/** + * Generates a constant value + */ +void t_html_generator::generate_const(t_const* tconst) { + string name = tconst->get_name(); + f_out_ << "<tr id=\"Const_" << name << "\"><td><code>" << name << "</code></td><td>"; + print_type(tconst->get_type()); + f_out_ << "</td><td><code>"; + print_const_value(tconst->get_type(), tconst->get_value()); + f_out_ << "</code></td></tr>"; + if (tconst->has_doc()) { + f_out_ << "<tr><td colspan=\"3\"><blockquote>"; + print_doc(tconst); + f_out_ << "</blockquote></td></tr>"; + } +} + +/** + * Generates a struct definition for a thrift data type. + * + * @param tstruct The struct definition + */ +void t_html_generator::generate_struct(t_struct* tstruct) { + string name = tstruct->get_name(); + f_out_ << "<div class=\"definition\">"; + f_out_ << "<h3 id=\"Struct_" << name << "\">"; + if (tstruct->is_xception()) { + f_out_ << "Exception: "; + } else if (tstruct->is_union()) { + f_out_ << "Union: "; + } else { + f_out_ << "Struct: "; + } + f_out_ << name << "</h3>" << endl; + vector<t_field*> members = tstruct->get_members(); + vector<t_field*>::iterator mem_iter = members.begin(); + f_out_ << "<table class=\"table-bordered table-striped table-condensed\">"; + f_out_ << "<thead><tr><th>Key</th><th>Field</th><th>Type</th><th>Description</th><th>Requiredness</" + "th><th>Default value</th></tr></thead><tbody>" << endl; + for (; mem_iter != members.end(); mem_iter++) { + f_out_ << "<tr><td>" << (*mem_iter)->get_key() << "</td><td>"; + f_out_ << (*mem_iter)->get_name(); + f_out_ << "</td><td>"; + print_type((*mem_iter)->get_type()); + f_out_ << "</td><td>"; + f_out_ << escape_html((*mem_iter)->get_doc()); + f_out_ << "</td><td>"; + if ((*mem_iter)->get_req() == t_field::T_OPTIONAL) { + f_out_ << "optional"; + } else if ((*mem_iter)->get_req() == t_field::T_REQUIRED) { + f_out_ << "required"; + } else { + f_out_ << "default"; + } + f_out_ << "</td><td>"; + t_const_value* default_val = (*mem_iter)->get_value(); + if (default_val != NULL) { + f_out_ << "<code>"; + print_const_value((*mem_iter)->get_type(), default_val); + f_out_ << "</code>"; + } + f_out_ << "</td></tr>" << endl; + } + f_out_ << "</tbody></table><br/>"; + print_doc(tstruct); + f_out_ << "</div>"; +} + +/** + * Exceptions are special structs + * + * @param tstruct The struct definition + */ +void t_html_generator::generate_xception(t_struct* txception) { + generate_struct(txception); +} + +/** + * Generates the HTML block for a Thrift service. + * + * @param tservice The service definition + */ +void t_html_generator::generate_service(t_service* tservice) { + f_out_ << "<h3 id=\"Svc_" << service_name_ << "\">Service: " << service_name_ << "</h3>" << endl; + + if (tservice->get_extends()) { + f_out_ << "<div class=\"extends\"><em>extends</em> "; + print_type(tservice->get_extends()); + f_out_ << "</div>\n"; + } + print_doc(tservice); + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator fn_iter = functions.begin(); + for (; fn_iter != functions.end(); fn_iter++) { + string fn_name = (*fn_iter)->get_name(); + f_out_ << "<div class=\"definition\">"; + f_out_ << "<h4 id=\"Fn_" << service_name_ << "_" << fn_name << "\">Function: " << service_name_ + << "." << fn_name << "</h4>" << endl; + f_out_ << "<pre>"; + std::string::size_type offset = print_type((*fn_iter)->get_returntype()); + bool first = true; + f_out_ << " " << fn_name << "("; + offset += fn_name.size() + 2; + vector<t_field*> args = (*fn_iter)->get_arglist()->get_members(); + vector<t_field*>::iterator arg_iter = args.begin(); + for (; arg_iter != args.end(); arg_iter++) { + if (!first) { + f_out_ << "," << endl; + for (std::string::size_type i = 0; i < offset; ++i) { + f_out_ << " "; + } + } + first = false; + print_type((*arg_iter)->get_type()); + f_out_ << " " << (*arg_iter)->get_name(); + if ((*arg_iter)->get_value() != NULL) { + f_out_ << " = "; + print_const_value((*arg_iter)->get_type(), (*arg_iter)->get_value()); + } + } + f_out_ << ")" << endl; + first = true; + vector<t_field*> excepts = (*fn_iter)->get_xceptions()->get_members(); + vector<t_field*>::iterator ex_iter = excepts.begin(); + if (ex_iter != excepts.end()) { + f_out_ << " throws "; + for (; ex_iter != excepts.end(); ex_iter++) { + if (!first) { + f_out_ << ", "; + } + first = false; + print_type((*ex_iter)->get_type()); + } + f_out_ << endl; + } + f_out_ << "</pre>"; + print_doc(*fn_iter); + print_fn_args_doc(*fn_iter); + f_out_ << "</div>"; + } +} + +THRIFT_REGISTER_GENERATOR( + html, + "HTML", + " standalone: Self-contained mode, includes all CSS in the HTML files.\n" + " Generates no style.css file, but HTML files will be larger.\n" + " noescape: Do not escape html in doc text.\n") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_html_generator.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_html_generator.h new file mode 100644 index 000000000..600b17f17 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_html_generator.h @@ -0,0 +1,240 @@ +#define BOOTSTRAP_CSS() \ + "/*!\n" \ + " * Bootstrap v2.0.3\n" \ + " *\n" \ + " * Copyright 2012 Twitter, Inc\n" \ + " * Licensed under the Apache License v2.0\n" \ + " * http://www.apache.org/licenses/LICENSE-2.0\n" \ + " *\n" \ + " * Designed and built with all the love in the world @twitter by @mdo and @fat.\n" \ + " */\n" \ + ".clearfix{*zoom:1;}.clearfix:before,.clearfix:after{display:table;content:\"\";}\n" \ + ".clearfix:after{clear:both;}\n" \ + ".hide-text{font:0/0 " \ + "a;color:transparent;text-shadow:none;background-color:transparent;border:0;}\n" \ + ".input-block-level{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-" \ + "moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;}\n" \ + "article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;}\n" \ + "audio,canvas,video{display:inline-block;*display:inline;*zoom:1;}\n" \ + "audio:not([controls]){display:none;}\n" \ + "html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}\n" \ + "a:focus{outline:thin dotted #333;outline:5px auto " \ + "-webkit-focus-ring-color;outline-offset:-2px;}\n" \ + "a:hover,a:active{outline:0;}\n" \ + "sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline;}\n" \ + "sup{top:-0.5em;}\n" \ + "sub{bottom:-0.25em;}\n" \ + "img{max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic;}\n" \ + "button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle;}\n" \ + "button,input{*overflow:visible;line-height:normal;}\n" \ + "button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0;}\n" \ + "button,input[type=\"button\"],input[type=\"reset\"],input[type=\"submit\"]{cursor:pointer;-" \ + "webkit-appearance:button;}\n" \ + "input[type=\"search\"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:" \ + "content-box;-webkit-appearance:textfield;}\n" \ + "input[type=\"search\"]::-webkit-search-decoration,input[type=\"search\"]::-webkit-search-" \ + "cancel-button{-webkit-appearance:none;}\n" \ + "textarea{overflow:auto;vertical-align:top;}\n" \ + "body{margin:0;font-family:\"Helvetica " \ + "Neue\",Helvetica,Arial,sans-serif;font-size:13px;line-height:18px;color:#333333;background-" \ + "color:#ffffff;}\n" \ + "a{color:#0088cc;text-decoration:none;}\n" \ + "a:hover{color:#005580;text-decoration:underline;}\n" \ + ".row{margin-left:-20px;*zoom:1;}.row:before,.row:after{display:table;content:\"\";}\n" \ + ".row:after{clear:both;}\n" \ + "[class*=\"span\"]{float:left;margin-left:20px;}\n" \ + ".container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;}\n" \ + ".span12{width:940px;}\n" \ + ".span11{width:860px;}\n" \ + ".span10{width:780px;}\n" \ + ".span9{width:700px;}\n" \ + ".span8{width:620px;}\n" \ + ".span7{width:540px;}\n" \ + ".span6{width:460px;}\n" \ + ".span5{width:380px;}\n" \ + ".span4{width:300px;}\n" \ + ".span3{width:220px;}\n" \ + ".span2{width:140px;}\n" \ + ".span1{width:60px;}\n" \ + ".offset12{margin-left:980px;}\n" \ + ".offset11{margin-left:900px;}\n" \ + ".offset10{margin-left:820px;}\n" \ + ".offset9{margin-left:740px;}\n" \ + ".offset8{margin-left:660px;}\n" \ + ".offset7{margin-left:580px;}\n" \ + ".offset6{margin-left:500px;}\n" \ + ".offset5{margin-left:420px;}\n" \ + ".offset4{margin-left:340px;}\n" \ + ".offset3{margin-left:260px;}\n" \ + ".offset2{margin-left:180px;}\n" \ + ".offset1{margin-left:100px;}\n" \ + ".row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:\"\";}" \ + "\n" \ + ".row-fluid:after{clear:both;}\n" \ + ".row-fluid " \ + "[class*=\"span\"]{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-" \ + "box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:" \ + "2.127659574%;*margin-left:2.0744680846382977%;}\n" \ + ".row-fluid [class*=\"span\"]:first-child{margin-left:0;}\n" \ + ".row-fluid .span12{width:99.99999998999999%;*width:99.94680850063828%;}\n" \ + ".row-fluid .span11{width:91.489361693%;*width:91.4361702036383%;}\n" \ + ".row-fluid .span10{width:82.97872339599999%;*width:82.92553190663828%;}\n" \ + ".row-fluid .span9{width:74.468085099%;*width:74.4148936096383%;}\n" \ + ".row-fluid .span8{width:65.95744680199999%;*width:65.90425531263828%;}\n" \ + ".row-fluid .span7{width:57.446808505%;*width:57.3936170156383%;}\n" \ + ".row-fluid .span6{width:48.93617020799999%;*width:48.88297871863829%;}\n" \ + ".row-fluid .span5{width:40.425531911%;*width:40.3723404216383%;}\n" \ + ".row-fluid .span4{width:31.914893614%;*width:31.8617021246383%;}\n" \ + ".row-fluid .span3{width:23.404255317%;*width:23.3510638276383%;}\n" \ + ".row-fluid .span2{width:14.89361702%;*width:14.8404255306383%;}\n" \ + ".row-fluid .span1{width:6.382978723%;*width:6.329787233638298%;}\n" \ + ".container{margin-right:auto;margin-left:auto;*zoom:1;}.container:before,.container:after{" \ + "display:table;content:\"\";}\n" \ + ".container:after{clear:both;}\n" \ + ".container-fluid{padding-right:20px;padding-left:20px;*zoom:1;}.container-fluid:before,." \ + "container-fluid:after{display:table;content:\"\";}\n" \ + ".container-fluid:after{clear:both;}\n" \ + "p{margin:0 0 9px;font-family:\"Helvetica " \ + "Neue\",Helvetica,Arial,sans-serif;font-size:13px;line-height:18px;}p " \ + "small{font-size:11px;color:#999999;}\n" \ + ".lead{margin-bottom:18px;font-size:20px;font-weight:200;line-height:27px;}\n" \ + "h1,h2,h3,h4,h5,h6{margin:0;font-family:inherit;font-weight:bold;color:inherit;text-rendering:" \ + "optimizelegibility;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 " \ + "small{font-weight:normal;color:#999999;}\n" \ + "h1{font-size:30px;line-height:36px;}h1 small{font-size:18px;}\n" \ + "h2{font-size:24px;line-height:36px;}h2 small{font-size:18px;}\n" \ + "h3{font-size:18px;line-height:27px;}h3 small{font-size:14px;}\n" \ + "h4,h5,h6{line-height:18px;}\n" \ + "h4{font-size:14px;}h4 small{font-size:12px;}\n" \ + "h5{font-size:12px;}\n" \ + "h6{font-size:11px;color:#999999;text-transform:uppercase;}\n" \ + ".page-header{padding-bottom:17px;margin:18px 0;border-bottom:1px solid #eeeeee;}\n" \ + ".page-header h1{line-height:1;}\n" \ + "ul,ol{padding:0;margin:0 0 9px 25px;}\n" \ + "ul ul,ul ol,ol ol,ol ul{margin-bottom:0;}\n" \ + "ul{list-style:disc;}\n" \ + "ol{list-style:decimal;}\n" \ + "li{line-height:18px;}\n" \ + "ul.unstyled,ol.unstyled{margin-left:0;list-style:none;}\n" \ + "dl{margin-bottom:18px;}\n" \ + "dt,dd{line-height:18px;}\n" \ + "dt{font-weight:bold;line-height:17px;}\n" \ + "dd{margin-left:9px;}\n" \ + ".dl-horizontal " \ + "dt{float:left;width:120px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;" \ + "white-space:nowrap;}\n" \ + ".dl-horizontal dd{margin-left:130px;}\n" \ + "hr{margin:18px 0;border:0;border-top:1px solid #eeeeee;border-bottom:1px solid #ffffff;}\n" \ + "strong{font-weight:bold;}\n" \ + "em{font-style:italic;}\n" \ + ".muted{color:#999999;}\n" \ + "abbr[title]{cursor:help;border-bottom:1px dotted #ddd;}\n" \ + "abbr.initialism{font-size:90%;text-transform:uppercase;}\n" \ + "blockquote{padding:0 0 0 15px;margin:0 0 18px;border-left:5px solid #eeeeee;}blockquote " \ + "p{margin-bottom:0;font-size:16px;font-weight:300;line-height:22.5px;}\n" \ + "blockquote small{display:block;line-height:18px;color:#999999;}blockquote " \ + "small:before{content:'\\2014 \\00A0';}\n" \ + "blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid " \ + "#eeeeee;border-left:0;}blockquote.pull-right p,blockquote.pull-right " \ + "small{text-align:right;}\n" \ + "q:before,q:after,blockquote:before,blockquote:after{content:\"\";}\n" \ + "address{display:block;margin-bottom:18px;font-style:normal;line-height:18px;}\n" \ + "small{font-size:100%;}\n" \ + "cite{font-style:normal;}\n" \ + "code,pre{padding:0 3px 2px;font-family:Menlo,Monaco,Consolas,\"Courier " \ + "New\",monospace;font-size:12px;color:#333333;-webkit-border-radius:3px;-moz-border-radius:3px;" \ + "border-radius:3px;}\n" \ + "code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8;}\n" \ + "pre{display:block;padding:8.5px;margin:0 0 " \ + "9px;font-size:12.025px;line-height:18px;word-break:break-all;word-wrap:break-word;white-space:" \ + "pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid " \ + "rgba(0, 0, 0, " \ + "0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}pre.prettyprint{" \ + "margin-bottom:18px;}\n" \ + "pre code{padding:0;color:inherit;background-color:transparent;border:0;}\n" \ + ".pre-scrollable{max-height:340px;overflow-y:scroll;}\n" \ + ".label,.badge{font-size:10.998px;font-weight:bold;line-height:14px;color:#ffffff;vertical-" \ + "align:baseline;white-space:nowrap;text-shadow:0 -1px 0 rgba(0, 0, 0, " \ + "0.25);background-color:#999999;}\n" \ + ".label{padding:1px 4px " \ + "2px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}\n" \ + ".badge{padding:1px 9px " \ + "2px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px;}\n" \ + "a.label:hover,a.badge:hover{color:#ffffff;text-decoration:none;cursor:pointer;}\n" \ + ".label-important,.badge-important{background-color:#b94a48;}\n" \ + ".label-important[href],.badge-important[href]{background-color:#953b39;}\n" \ + ".label-warning,.badge-warning{background-color:#f89406;}\n" \ + ".label-warning[href],.badge-warning[href]{background-color:#c67605;}\n" \ + ".label-success,.badge-success{background-color:#468847;}\n" \ + ".label-success[href],.badge-success[href]{background-color:#356635;}\n" \ + ".label-info,.badge-info{background-color:#3a87ad;}\n" \ + ".label-info[href],.badge-info[href]{background-color:#2d6987;}\n" \ + ".label-inverse,.badge-inverse{background-color:#333333;}\n" \ + ".label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a;}\n" \ + "table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0;}" \ + "\n" \ + ".table{width:100%;margin-bottom:18px;}.table th,.table " \ + "td{padding:8px;line-height:18px;text-align:left;vertical-align:top;border-top:1px solid " \ + "#dddddd;}\n" \ + ".table th{font-weight:bold;}\n" \ + ".table thead th{vertical-align:bottom;}\n" \ + ".table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table " \ + "colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table " \ + "thead:first-child tr:first-child th,.table thead:first-child tr:first-child " \ + "td{border-top:0;}\n" \ + ".table tbody+tbody{border-top:2px solid #dddddd;}\n" \ + ".table-condensed th,.table-condensed td{padding:4px 5px;}\n" \ + ".table-bordered{border:1px solid " \ + "#dddddd;border-collapse:separate;*border-collapse:collapsed;border-left:0;-webkit-border-" \ + "radius:4px;-moz-border-radius:4px;border-radius:4px;}.table-bordered th,.table-bordered " \ + "td{border-left:1px solid #dddddd;}\n" \ + ".table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child " \ + "th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead " \ + "tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered " \ + "colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child " \ + "th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child " \ + "tr:first-child td{border-top:0;}\n" \ + ".table-bordered thead:first-child tr:first-child th:first-child,.table-bordered " \ + "tbody:first-child tr:first-child " \ + "td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-" \ + "radius-topleft:4px;}\n" \ + ".table-bordered thead:first-child tr:first-child th:last-child,.table-bordered " \ + "tbody:first-child tr:first-child " \ + "td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-" \ + "radius-topright:4px;}\n" \ + ".table-bordered thead:last-child tr:last-child th:first-child,.table-bordered " \ + "tbody:last-child tr:last-child td:first-child{-webkit-border-radius:0 0 0 " \ + "4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 " \ + "4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-" \ + "bottomleft:4px;}\n" \ + ".table-bordered thead:last-child tr:last-child th:last-child,.table-bordered tbody:last-child " \ + "tr:last-child " \ + "td:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-" \ + "border-radius-bottomright:4px;}\n" \ + ".table-striped tbody tr:nth-child(odd) td,.table-striped tbody tr:nth-child(odd) " \ + "th{background-color:#f9f9f9;}\n" \ + ".table tbody tr:hover td,.table tbody tr:hover th{background-color:#f5f5f5;}\n" \ + "table .span1{float:none;width:44px;margin-left:0;}\n" \ + "table .span2{float:none;width:124px;margin-left:0;}\n" \ + "table .span3{float:none;width:204px;margin-left:0;}\n" \ + "table .span4{float:none;width:284px;margin-left:0;}\n" \ + "table .span5{float:none;width:364px;margin-left:0;}\n" \ + "table .span6{float:none;width:444px;margin-left:0;}\n" \ + "table .span7{float:none;width:524px;margin-left:0;}\n" \ + "table .span8{float:none;width:604px;margin-left:0;}\n" \ + "table .span9{float:none;width:684px;margin-left:0;}\n" \ + "table .span10{float:none;width:764px;margin-left:0;}\n" \ + "table .span11{float:none;width:844px;margin-left:0;}\n" \ + "table .span12{float:none;width:924px;margin-left:0;}\n" \ + "table .span13{float:none;width:1004px;margin-left:0;}\n" \ + "table .span14{float:none;width:1084px;margin-left:0;}\n" \ + "table .span15{float:none;width:1164px;margin-left:0;}\n" \ + "table .span16{float:none;width:1244px;margin-left:0;}\n" \ + "table .span17{float:none;width:1324px;margin-left:0;}\n" \ + "table .span18{float:none;width:1404px;margin-left:0;}\n" \ + "table .span19{float:none;width:1484px;margin-left:0;}\n" \ + "table .span20{float:none;width:1564px;margin-left:0;}\n" \ + "table .span21{float:none;width:1644px;margin-left:0;}\n" \ + "table .span22{float:none;width:1724px;margin-left:0;}\n" \ + "table .span23{float:none;width:1804px;margin-left:0;}\n" \ + "table .span24{float:none;width:1884px;margin-left:0;}" diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_java_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_java_generator.cc new file mode 100644 index 000000000..7254e12b1 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_java_generator.cc @@ -0,0 +1,5456 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <cassert> +#include <ctime> + +#include <sstream> +#include <string> +#include <fstream> +#include <iomanip> +#include <iostream> +#include <limits> +#include <vector> +#include <cctype> + +#include <sys/stat.h> +#include <stdexcept> + +#include "thrift/platform.h" +#include "thrift/generate/t_oop_generator.h" + +using std::map; +using std::ostream; +using std::ostringstream; +using std::setfill; +using std::setw; +using std::string; +using std::stringstream; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes + +/** + * Java code generator. + * + */ +class t_java_generator : public t_oop_generator { +public: + t_java_generator(t_program* program, + const std::map<std::string, std::string>& parsed_options, + const std::string& option_string) + : t_oop_generator(program) { + (void)option_string; + std::map<std::string, std::string>::const_iterator iter; + + bean_style_ = false; + android_style_ = false; + private_members_ = false; + nocamel_style_ = false; + fullcamel_style_ = false; + android_legacy_ = false; + sorted_containers_ = false; + java5_ = false; + reuse_objects_ = false; + use_option_type_ = false; + undated_generated_annotations_ = false; + suppress_generated_annotations_ = false; + rethrow_unhandled_exceptions_ = false; + unsafe_binaries_ = false; + for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + if( iter->first.compare("beans") == 0) { + bean_style_ = true; + } else if( iter->first.compare("android") == 0) { + android_style_ = true; + } else if( iter->first.compare("private-members") == 0) { + private_members_ = true; + } else if( iter->first.compare("nocamel") == 0) { + nocamel_style_ = true; + } else if( iter->first.compare("fullcamel") == 0) { + fullcamel_style_ = true; + } else if( iter->first.compare("android_legacy") == 0) { + android_legacy_ = true; + } else if( iter->first.compare("sorted_containers") == 0) { + sorted_containers_ = true; + } else if( iter->first.compare("java5") == 0) { + java5_ = true; + } else if( iter->first.compare("reuse-objects") == 0) { + reuse_objects_ = true; + } else if( iter->first.compare("option_type") == 0) { + use_option_type_ = true; + } else if( iter->first.compare("rethrow_unhandled_exceptions") == 0) { + rethrow_unhandled_exceptions_ = true; + } else if( iter->first.compare("generated_annotations") == 0) { + if( iter->second.compare("undated") == 0) { + undated_generated_annotations_ = true; + } else if(iter->second.compare("suppress") == 0) { + suppress_generated_annotations_ = true; + } else { + throw "unknown option java:" + iter->first + "=" + iter->second; + } + } else if( iter->first.compare("unsafe_binaries") == 0) { + unsafe_binaries_ = true; + } else { + throw "unknown option java:" + iter->first; + } + } + + if (java5_) { + android_legacy_ = true; + } + + out_dir_base_ = (bean_style_ ? "gen-javabean" : "gen-java"); + } + + /** + * Init and close methods + */ + + void init_generator() override; + void close_generator() override; + + void generate_consts(std::vector<t_const*> consts) override; + + /** + * Program-level generation functions + */ + + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_struct(t_struct* tstruct) override; + void generate_union(t_struct* tunion); + void generate_xception(t_struct* txception) override; + void generate_service(t_service* tservice) override; + + void print_const_value(std::ostream& out, + std::string name, + t_type* type, + t_const_value* value, + bool in_static, + bool defval = false); + std::string render_const_value(std::ostream& out, t_type* type, t_const_value* value); + + /** + * Service-level generation functions + */ + + void generate_java_struct(t_struct* tstruct, bool is_exception); + + void generate_java_struct_definition(std::ostream& out, + t_struct* tstruct, + bool is_xception = false, + bool in_class = false, + bool is_result = false); + void generate_java_struct_parcelable(std::ostream& out, t_struct* tstruct); + void generate_java_struct_equality(std::ostream& out, t_struct* tstruct); + void generate_java_struct_compare_to(std::ostream& out, t_struct* tstruct); + void generate_java_struct_reader(std::ostream& out, t_struct* tstruct); + void generate_java_validator(std::ostream& out, t_struct* tstruct); + void generate_java_struct_result_writer(std::ostream& out, t_struct* tstruct); + void generate_java_struct_writer(std::ostream& out, t_struct* tstruct); + void generate_java_struct_tostring(std::ostream& out, t_struct* tstruct); + void generate_java_struct_clear(std::ostream& out, t_struct* tstruct); + void generate_java_struct_write_object(std::ostream& out, t_struct* tstruct); + void generate_java_struct_read_object(std::ostream& out, t_struct* tstruct); + void generate_java_meta_data_map(std::ostream& out, t_struct* tstruct); + void generate_field_value_meta_data(std::ostream& out, t_type* type); + std::string get_java_type_string(t_type* type); + void generate_java_struct_field_by_id(ostream& out, t_struct* tstruct); + void generate_reflection_setters(std::ostringstream& out, + t_type* type, + std::string field_name, + std::string cap_name); + void generate_reflection_getters(std::ostringstream& out, + t_type* type, + std::string field_name, + std::string cap_name); + void generate_generic_field_getters_setters(std::ostream& out, t_struct* tstruct); + void generate_generic_isset_method(std::ostream& out, t_struct* tstruct); + void generate_java_bean_boilerplate(std::ostream& out, t_struct* tstruct); + + void generate_function_helpers(t_function* tfunction); + std::string as_camel_case(std::string name, bool ucfirst = true); + std::string get_rpc_method_name(std::string name); + std::string get_cap_name(std::string name); + std::string generate_isset_check(t_field* field); + std::string generate_isset_check(std::string field); + void generate_isset_set(ostream& out, t_field* field, std::string prefix); + std::string isset_field_id(t_field* field); + + void generate_service_interface(t_service* tservice); + void generate_service_async_interface(t_service* tservice); + void generate_service_helpers(t_service* tservice); + void generate_service_client(t_service* tservice); + void generate_service_async_client(t_service* tservice); + void generate_service_server(t_service* tservice); + void generate_service_async_server(t_service* tservice); + void generate_process_function(t_service* tservice, t_function* tfunction); + void generate_process_async_function(t_service* tservice, t_function* tfunction); + + void generate_java_union(t_struct* tstruct); + void generate_union_constructor(ostream& out, t_struct* tstruct); + void generate_union_getters_and_setters(ostream& out, t_struct* tstruct); + void generate_union_is_set_methods(ostream& out, t_struct* tstruct); + void generate_union_abstract_methods(ostream& out, t_struct* tstruct); + void generate_check_type(ostream& out, t_struct* tstruct); + void generate_standard_scheme_read_value(ostream& out, t_struct* tstruct); + void generate_standard_scheme_write_value(ostream& out, t_struct* tstruct); + void generate_tuple_scheme_read_value(ostream& out, t_struct* tstruct); + void generate_tuple_scheme_write_value(ostream& out, t_struct* tstruct); + void generate_get_field_desc(ostream& out, t_struct* tstruct); + void generate_get_struct_desc(ostream& out, t_struct* tstruct); + void generate_get_field_name(ostream& out, t_struct* tstruct); + + void generate_union_comparisons(ostream& out, t_struct* tstruct); + void generate_union_hashcode(ostream& out, t_struct* tstruct); + + void generate_scheme_map(ostream& out, t_struct* tstruct); + void generate_standard_writer(ostream& out, t_struct* tstruct, bool is_result); + void generate_standard_reader(ostream& out, t_struct* tstruct); + void generate_java_struct_standard_scheme(ostream& out, t_struct* tstruct, bool is_result); + + void generate_java_struct_tuple_scheme(ostream& out, t_struct* tstruct); + void generate_java_struct_tuple_reader(ostream& out, t_struct* tstruct); + void generate_java_struct_tuple_writer(ostream& out, t_struct* tstruct); + + void generate_java_scheme_lookup(ostream& out); + + void generate_javax_generated_annotation(ostream& out); + /** + * Serialization constructs + */ + + void generate_deserialize_field(std::ostream& out, + t_field* tfield, + std::string prefix = "", + bool has_metadata = true); + + void generate_deserialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); + + void generate_deserialize_container(std::ostream& out, + t_type* ttype, + std::string prefix = "", + bool has_metadata = true); + + void generate_deserialize_set_element(std::ostream& out, + t_set* tset, + std::string prefix = "", + std::string obj = "", + bool has_metadata = true); + + void generate_deserialize_map_element(std::ostream& out, + t_map* tmap, + std::string prefix = "", + std::string obj = "", + bool has_metadata = true); + + void generate_deserialize_list_element(std::ostream& out, + t_list* tlist, + std::string prefix = "", + std::string obj = "", + bool has_metadata = true); + + void generate_serialize_field(std::ostream& out, + t_field* tfield, + std::string prefix = "", + bool has_metadata = true); + + void generate_serialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); + + void generate_serialize_container(std::ostream& out, + t_type* ttype, + std::string prefix = "", + bool has_metadata = true); + + void generate_serialize_map_element(std::ostream& out, + t_map* tmap, + std::string iter, + std::string map, + bool has_metadata = true); + + void generate_serialize_set_element(std::ostream& out, + t_set* tmap, + std::string iter, + bool has_metadata = true); + + void generate_serialize_list_element(std::ostream& out, + t_list* tlist, + std::string iter, + bool has_metadata = true); + + void generate_deep_copy_container(std::ostream& out, + std::string source_name_p1, + std::string source_name_p2, + std::string result_name, + t_type* type); + void generate_deep_copy_non_container(std::ostream& out, + std::string source_name, + std::string dest_name, + t_type* type); + + enum isset_type { ISSET_NONE, ISSET_PRIMITIVE, ISSET_BITSET }; + isset_type needs_isset(t_struct* tstruct, std::string* outPrimitiveType = NULL); + + /** + * Helper rendering functions + */ + + std::string java_package(); + std::string java_suppressions(); + std::string java_nullable_annotation(); + std::string type_name(t_type* ttype, + bool in_container = false, + bool in_init = false, + bool skip_generic = false, + bool force_namespace = false); + std::string base_type_name(t_base_type* tbase, bool in_container = false); + std::string declare_field(t_field* tfield, bool init = false, bool comment = false); + std::string function_signature(t_function* tfunction, std::string prefix = ""); + std::string function_signature_async(t_function* tfunction, + bool use_base_method = false, + std::string prefix = ""); + std::string argument_list(t_struct* tstruct, bool include_types = true); + std::string async_function_call_arglist(t_function* tfunc, + bool use_base_method = true, + bool include_types = true); + std::string async_argument_list(t_function* tfunct, + t_struct* tstruct, + t_type* ttype, + bool include_types = false); + std::string type_to_enum(t_type* ttype); + void generate_struct_desc(ostream& out, t_struct* tstruct); + void generate_field_descs(ostream& out, t_struct* tstruct); + void generate_field_name_constants(ostream& out, t_struct* tstruct); + + std::string make_valid_java_filename(std::string const& fromName); + std::string make_valid_java_identifier(std::string const& fromName); + + bool type_can_be_null(t_type* ttype) { + ttype = get_true_type(ttype); + + return ttype->is_container() || ttype->is_struct() || ttype->is_xception() || ttype->is_string() + || ttype->is_enum(); + } + + bool is_deprecated(const std::map<std::string, std::string>& annotations) { + return annotations.find("deprecated") != annotations.end(); + } + + bool is_enum_set(t_type* ttype) { + if (!sorted_containers_) { + ttype = get_true_type(ttype); + if (ttype->is_set()) { + t_set* tset = (t_set*)ttype; + t_type* elem_type = get_true_type(tset->get_elem_type()); + return elem_type->is_enum(); + } + } + return false; + } + + bool is_enum_map(t_type* ttype) { + if (!sorted_containers_) { + ttype = get_true_type(ttype); + if (ttype->is_map()) { + t_map* tmap = (t_map*)ttype; + t_type* key_type = get_true_type(tmap->get_key_type()); + return key_type->is_enum(); + } + } + return false; + } + + std::string inner_enum_type_name(t_type* ttype) { + ttype = get_true_type(ttype); + if (ttype->is_map()) { + t_map* tmap = (t_map*)ttype; + t_type* key_type = get_true_type(tmap->get_key_type()); + return type_name(key_type, true) + ".class"; + } else if (ttype->is_set()) { + t_set* tset = (t_set*)ttype; + t_type* elem_type = get_true_type(tset->get_elem_type()); + return type_name(elem_type, true) + ".class"; + } + return ""; + } + + std::string constant_name(std::string name); + +private: + /** + * File streams + */ + + std::string package_name_; + ofstream_with_content_based_conditional_update f_service_; + std::string package_dir_; + + bool bean_style_; + bool android_style_; + bool private_members_; + bool nocamel_style_; + bool fullcamel_style_; + bool android_legacy_; + bool java5_; + bool sorted_containers_; + bool reuse_objects_; + bool use_option_type_; + bool undated_generated_annotations_; + bool suppress_generated_annotations_; + bool rethrow_unhandled_exceptions_; + bool unsafe_binaries_; + +}; + +/** + * Prepares for file generation by opening up the necessary file output + * streams. + * + * @param tprogram The program to generate + */ +void t_java_generator::init_generator() { + // Make output directory + MKDIR(get_out_dir().c_str()); + package_name_ = program_->get_namespace("java"); + + string dir = package_name_; + string subdir = get_out_dir(); + string::size_type loc; + while ((loc = dir.find(".")) != string::npos) { + subdir = subdir + "/" + dir.substr(0, loc); + MKDIR(subdir.c_str()); + dir = dir.substr(loc + 1); + } + if (dir.size() > 0) { + subdir = subdir + "/" + dir; + MKDIR(subdir.c_str()); + } + + package_dir_ = subdir; +} + +/** + * Packages the generated file + * + * @return String of the package, i.e. "package org.apache.thriftdemo;" + */ +string t_java_generator::java_package() { + if (!package_name_.empty()) { + return string("package ") + package_name_ + ";\n\n"; + } + return ""; +} + +string t_java_generator::java_suppressions() { + return "@SuppressWarnings({\"cast\", \"rawtypes\", \"serial\", \"unchecked\", \"unused\"})\n"; +} + +string t_java_generator::java_nullable_annotation() { + return "@org.apache.thrift.annotation.Nullable"; +} + +/** + * Nothing in Java + */ +void t_java_generator::close_generator() { +} + +/** + * Generates a typedef. This is not done in Java, since it does + * not support arbitrary name replacements, and it'd be a wacky waste + * of overhead to make wrapper classes. + * + * @param ttypedef The type definition + */ +void t_java_generator::generate_typedef(t_typedef* ttypedef) { + (void)ttypedef; +} + +/** + * Enums are a class with a set of static constants. + * + * @param tenum The enumeration + */ +void t_java_generator::generate_enum(t_enum* tenum) { + bool is_deprecated = this->is_deprecated(tenum->annotations_); + // Make output file + string f_enum_name = package_dir_ + "/" + make_valid_java_filename(tenum->get_name()) + ".java"; + ofstream_with_content_based_conditional_update f_enum; + f_enum.open(f_enum_name.c_str()); + + // Comment and package it + f_enum << autogen_comment() << java_package() << endl; + + generate_java_doc(f_enum, tenum); + + if (!suppress_generated_annotations_) { + generate_javax_generated_annotation(f_enum); + } + + if (is_deprecated) { + indent(f_enum) << "@Deprecated" << endl; + } + indent(f_enum) << "public enum " << tenum->get_name() << " implements org.apache.thrift.TEnum "; + scope_up(f_enum); + + vector<t_enum_value*> constants = tenum->get_constants(); + vector<t_enum_value*>::iterator c_iter; + bool first = true; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + int value = (*c_iter)->get_value(); + + if (first) { + first = false; + } else { + f_enum << "," << endl; + } + + generate_java_doc(f_enum, *c_iter); + if (this->is_deprecated((*c_iter)->annotations_)) { + indent(f_enum) << "@Deprecated" << endl; + } + indent(f_enum) << (*c_iter)->get_name() << "(" << value << ")"; + } + f_enum << ";" << endl << endl; + + // Field for thriftCode + indent(f_enum) << "private final int value;" << endl << endl; + + indent(f_enum) << "private " << tenum->get_name() << "(int value) {" << endl; + indent(f_enum) << " this.value = value;" << endl; + indent(f_enum) << "}" << endl << endl; + + indent(f_enum) << "/**" << endl; + indent(f_enum) << " * Get the integer value of this enum value, as defined in the Thrift IDL." + << endl; + indent(f_enum) << " */" << endl; + indent(f_enum) << "public int getValue() {" << endl; + indent(f_enum) << " return value;" << endl; + indent(f_enum) << "}" << endl << endl; + + indent(f_enum) << "/**" << endl; + indent(f_enum) << " * Find a the enum type by its integer value, as defined in the Thrift IDL." + << endl; + indent(f_enum) << " * @return null if the value is not found." << endl; + indent(f_enum) << " */" << endl; + indent(f_enum) << java_nullable_annotation() << endl; + indent(f_enum) << "public static " + tenum->get_name() + " findByValue(int value) { " << endl; + + indent_up(); + + indent(f_enum) << "switch (value) {" << endl; + indent_up(); + + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + int value = (*c_iter)->get_value(); + indent(f_enum) << "case " << value << ":" << endl; + indent(f_enum) << " return " << (*c_iter)->get_name() << ";" << endl; + } + + indent(f_enum) << "default:" << endl; + indent(f_enum) << " return null;" << endl; + + indent_down(); + + indent(f_enum) << "}" << endl; + + indent_down(); + + indent(f_enum) << "}" << endl; + + scope_down(f_enum); + + f_enum.close(); +} + +/** + * Generates a class that holds all the constants. + */ +void t_java_generator::generate_consts(std::vector<t_const*> consts) { + if (consts.empty()) { + return; + } + + string f_consts_name = package_dir_ + '/' + make_valid_java_filename(program_name_) + + "Constants.java"; + ofstream_with_content_based_conditional_update f_consts; + f_consts.open(f_consts_name.c_str()); + + // Print header + f_consts << autogen_comment() << java_package() << java_suppressions(); + + f_consts << "public class " << make_valid_java_identifier(program_name_) << "Constants {" << endl + << endl; + indent_up(); + vector<t_const*>::iterator c_iter; + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + generate_java_doc(f_consts, (*c_iter)); + print_const_value(f_consts, + (*c_iter)->get_name(), + (*c_iter)->get_type(), + (*c_iter)->get_value(), + false); + } + indent_down(); + indent(f_consts) << "}" << endl; + f_consts.close(); +} + + +/** + * Prints the value of a constant with the given type. Note that type checking + * is NOT performed in this function as it is always run beforehand using the + * validate_types method in main.cc + */ +void t_java_generator::print_const_value(std::ostream& out, + string name, + t_type* type, + t_const_value* value, + bool in_static, + bool defval) { + type = get_true_type(type); + + indent(out); + if (!defval) { + out << (in_static ? "" : "public static final ") << type_name(type) << " "; + } + if (type->is_base_type()) { + string v2 = render_const_value(out, type, value); + out << name << " = " << v2 << ";" << endl << endl; + } else if (type->is_enum()) { + out << name << " = " << render_const_value(out, type, value) << ";" << endl << endl; + } else if (type->is_struct() || type->is_xception()) { + const vector<t_field*>& unsorted_fields = ((t_struct*)type)->get_members(); + vector<t_field*> fields = unsorted_fields; + std::sort(fields.begin(), fields.end(), t_field::key_compare()); + vector<t_field*>::const_iterator f_iter; + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + out << name << " = new " << type_name(type, false, true) << "();" << endl; + if (!in_static) { + indent(out) << "static {" << endl; + indent_up(); + } + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + t_type* field_type = NULL; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field_type = (*f_iter)->get_type(); + } + } + if (field_type == NULL) { + throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string(); + } + string val = render_const_value(out, field_type, v_iter->second); + indent(out) << name << "."; + std::string cap_name = get_cap_name(v_iter->first->get_string()); + out << "set" << cap_name << "(" << val << ");" << endl; + } + if (!in_static) { + indent_down(); + indent(out) << "}" << endl; + } + out << endl; + } else if (type->is_map()) { + std::string constructor_args; + if (is_enum_map(type)) { + constructor_args = inner_enum_type_name(type); + } + out << name << " = new " << type_name(type, false, true) << "(" << constructor_args << ");" << endl; + if (!in_static) { + indent(out) << "static {" << endl; + indent_up(); + } + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string key = render_const_value(out, ktype, v_iter->first); + string val = render_const_value(out, vtype, v_iter->second); + indent(out) << name << ".put(" << key << ", " << val << ");" << endl; + } + if (!in_static) { + indent_down(); + indent(out) << "}" << endl; + } + out << endl; + } else if (type->is_list() || type->is_set()) { + if (is_enum_set(type)) { + out << name << " = " << type_name(type, false, true, true) << ".noneOf(" << inner_enum_type_name(type) << ");" << endl; + } else { + out << name << " = new " << type_name(type, false, true) << "();" << endl; + } + if (!in_static) { + indent(out) << "static {" << endl; + indent_up(); + } + t_type* etype; + if (type->is_list()) { + etype = ((t_list*)type)->get_elem_type(); + } else { + etype = ((t_set*)type)->get_elem_type(); + } + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string val = render_const_value(out, etype, *v_iter); + indent(out) << name << ".add(" << val << ");" << endl; + } + if (!in_static) { + indent_down(); + indent(out) << "}" << endl; + } + out << endl; + } else { + throw "compiler error: no const of type " + type->get_name(); + } +} + +string t_java_generator::render_const_value(ostream& out, t_type* type, t_const_value* value) { + type = get_true_type(type); + std::ostringstream render; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + if (((t_base_type*)type)->is_binary()) { + render << "java.nio.ByteBuffer.wrap(\"" << get_escaped_string(value) << "\".getBytes())"; + } else { + render << '"' << get_escaped_string(value) << '"'; + } + break; + case t_base_type::TYPE_BOOL: + render << ((value->get_integer() > 0) ? "true" : "false"); + break; + case t_base_type::TYPE_I8: + render << "(byte)" << value->get_integer(); + break; + case t_base_type::TYPE_I16: + render << "(short)" << value->get_integer(); + break; + case t_base_type::TYPE_I32: + render << value->get_integer(); + break; + case t_base_type::TYPE_I64: + render << value->get_integer() << "L"; + break; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) { + render << value->get_integer() << "d"; + } else { + render << emit_double_as_string(value->get_double()); + } + break; + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + std::string namespace_prefix = type->get_program()->get_namespace("java"); + if (namespace_prefix.length() > 0) { + namespace_prefix += "."; + } + render << namespace_prefix << value->get_identifier_with_parent(); + } else { + string t = tmp("tmp"); + print_const_value(out, t, type, value, true); + render << t; + } + + return render.str(); +} + +/** + * Generates a struct definition for a thrift data type. This will be a org.apache.thrift.TBase + * implementor. + * + * @param tstruct The struct definition + */ +void t_java_generator::generate_struct(t_struct* tstruct) { + if (tstruct->is_union()) { + generate_java_union(tstruct); + } else { + generate_java_struct(tstruct, false); + } +} + +/** + * Exceptions are structs, but they inherit from Exception + * + * @param tstruct The struct definition + */ +void t_java_generator::generate_xception(t_struct* txception) { + generate_java_struct(txception, true); +} + +/** + * Java struct definition. + * + * @param tstruct The struct definition + */ +void t_java_generator::generate_java_struct(t_struct* tstruct, bool is_exception) { + // Make output file + string f_struct_name = package_dir_ + "/" + make_valid_java_filename(tstruct->get_name()) + + ".java"; + ofstream_with_content_based_conditional_update f_struct; + f_struct.open(f_struct_name.c_str()); + + f_struct << autogen_comment() << java_package() << java_suppressions(); + + generate_java_struct_definition(f_struct, tstruct, is_exception); + f_struct.close(); +} + +/** + * Java union definition. + * + * @param tstruct The struct definition + */ +void t_java_generator::generate_java_union(t_struct* tstruct) { + // Make output file + string f_struct_name = package_dir_ + "/" + make_valid_java_filename(tstruct->get_name()) + + ".java"; + ofstream_with_content_based_conditional_update f_struct; + f_struct.open(f_struct_name.c_str()); + + f_struct << autogen_comment() << java_package() << java_suppressions(); + + generate_java_doc(f_struct, tstruct); + + bool is_final = (tstruct->annotations_.find("final") != tstruct->annotations_.end()); + bool is_deprecated = this->is_deprecated(tstruct->annotations_); + + if (!suppress_generated_annotations_) { + generate_javax_generated_annotation(f_struct); + } + + if (is_deprecated) { + indent(f_struct) << "@Deprecated" << endl; + } + indent(f_struct) << "public " << (is_final ? "final " : "") << "class " << tstruct->get_name() + << " extends org.apache.thrift.TUnion<" << tstruct->get_name() << ", " + << tstruct->get_name() << "._Fields> "; + + scope_up(f_struct); + + generate_struct_desc(f_struct, tstruct); + generate_field_descs(f_struct, tstruct); + + f_struct << endl; + + generate_field_name_constants(f_struct, tstruct); + + f_struct << endl; + + generate_java_meta_data_map(f_struct, tstruct); + + generate_union_constructor(f_struct, tstruct); + + f_struct << endl; + + generate_union_abstract_methods(f_struct, tstruct); + + f_struct << endl; + + generate_java_struct_field_by_id(f_struct, tstruct); + + f_struct << endl; + + generate_union_getters_and_setters(f_struct, tstruct); + + f_struct << endl; + + generate_union_is_set_methods(f_struct, tstruct); + + f_struct << endl; + + generate_union_comparisons(f_struct, tstruct); + + f_struct << endl; + + generate_union_hashcode(f_struct, tstruct); + + f_struct << endl; + + generate_java_struct_write_object(f_struct, tstruct); + + f_struct << endl; + + generate_java_struct_read_object(f_struct, tstruct); + + f_struct << endl; + + scope_down(f_struct); + + f_struct.close(); +} + +void t_java_generator::generate_union_constructor(ostream& out, t_struct* tstruct) { + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + indent(out) << "public " << type_name(tstruct) << "() {" << endl; + indent_up(); + bool default_value = false; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* type = get_true_type((*m_iter)->get_type()); + if ((*m_iter)->get_value() != NULL) { + indent(out) << "super(_Fields." << constant_name((*m_iter)->get_name()) << ", " + << render_const_value(out, type, (*m_iter)->get_value()) << ");" << endl; + default_value = true; + break; + } + } + if (default_value == false) { + indent(out) << "super();" << endl; + } + indent_down(); + indent(out) << "}" << endl << endl; + + indent(out) << "public " << type_name(tstruct) << "(_Fields setField, java.lang.Object value) {" << endl; + indent(out) << " super(setField, value);" << endl; + indent(out) << "}" << endl << endl; + + indent(out) << "public " << type_name(tstruct) << "(" << type_name(tstruct) << " other) {" + << endl; + indent(out) << " super(other);" << endl; + indent(out) << "}" << endl; + + indent(out) << "public " << tstruct->get_name() << " deepCopy() {" << endl; + indent(out) << " return new " << tstruct->get_name() << "(this);" << endl; + indent(out) << "}" << endl << endl; + + // generate "constructors" for each field + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* type = (*m_iter)->get_type(); + indent(out) << "public static " << type_name(tstruct) << " " << (*m_iter)->get_name() << "(" + << type_name(type) << " value) {" << endl; + indent(out) << " " << type_name(tstruct) << " x = new " << type_name(tstruct) << "();" << endl; + indent(out) << " x.set" << get_cap_name((*m_iter)->get_name()) << "(value);" << endl; + indent(out) << " return x;" << endl; + indent(out) << "}" << endl << endl; + + if (type->is_binary()) { + indent(out) << "public static " << type_name(tstruct) << " " << (*m_iter)->get_name() + << "(byte[] value) {" << endl; + indent(out) << " " << type_name(tstruct) << " x = new " << type_name(tstruct) << "();" + << endl; + indent(out) << " x.set" << get_cap_name((*m_iter)->get_name()); + if(unsafe_binaries_) { + indent(out) << "(java.nio.ByteBuffer.wrap(value));" << endl; + }else{ + indent(out) << "(java.nio.ByteBuffer.wrap(value.clone()));" << endl; + } + indent(out) << " return x;" << endl; + indent(out) << "}" << endl << endl; + } + } +} + +void t_java_generator::generate_union_getters_and_setters(ostream& out, t_struct* tstruct) { + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + bool first = true; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if (first) { + first = false; + } else { + out << endl; + } + + t_field* field = (*m_iter); + t_type* type = field->get_type(); + std::string cap_name = get_cap_name(field->get_name()); + bool is_deprecated = this->is_deprecated(field->annotations_); + + generate_java_doc(out, field); + if (type->is_binary()) { + if (is_deprecated) { + indent(out) << "@Deprecated" << endl; + } + indent(out) << "public byte[] get" << cap_name << "() {" << endl; + indent(out) << " set" << cap_name << "(org.apache.thrift.TBaseHelper.rightSize(buffer" + << get_cap_name("for") << cap_name << "()));" << endl; + indent(out) << " java.nio.ByteBuffer b = buffer" << get_cap_name("for") << cap_name << "();" << endl; + indent(out) << " return b == null ? null : b.array();" << endl; + indent(out) << "}" << endl; + + out << endl; + + indent(out) << "public java.nio.ByteBuffer buffer" << get_cap_name("for") + << get_cap_name(field->get_name()) << "() {" << endl; + indent(out) << " if (getSetField() == _Fields." << constant_name(field->get_name()) << ") {" + << endl; + + if(unsafe_binaries_){ + indent(out) + << " return (java.nio.ByteBuffer)getFieldValue();" + << endl; + }else{ + indent(out) + << " return org.apache.thrift.TBaseHelper.copyBinary((java.nio.ByteBuffer)getFieldValue());" + << endl; + } + + indent(out) << " } else {" << endl; + indent(out) << " throw new java.lang.RuntimeException(\"Cannot get field '" << field->get_name() + << "' because union is currently set to \" + getFieldDesc(getSetField()).name);" + << endl; + indent(out) << " }" << endl; + indent(out) << "}" << endl; + } else { + if (is_deprecated) { + indent(out) << "@Deprecated" << endl; + } + indent(out) << "public " << type_name(field->get_type()) << " get" + << get_cap_name(field->get_name()) << "() {" << endl; + indent(out) << " if (getSetField() == _Fields." << constant_name(field->get_name()) << ") {" + << endl; + indent(out) << " return (" << type_name(field->get_type(), true) << ")getFieldValue();" + << endl; + indent(out) << " } else {" << endl; + indent(out) << " throw new java.lang.RuntimeException(\"Cannot get field '" << field->get_name() + << "' because union is currently set to \" + getFieldDesc(getSetField()).name);" + << endl; + indent(out) << " }" << endl; + indent(out) << "}" << endl; + } + + out << endl; + + generate_java_doc(out, field); + if (type->is_binary()) { + if (is_deprecated) { + indent(out) << "@Deprecated" << endl; + } + indent(out) << "public void set" << get_cap_name(field->get_name()) << "(byte[] value) {" + << endl; + indent(out) << " set" << get_cap_name(field->get_name()); + + if(unsafe_binaries_){ + indent(out) << "(java.nio.ByteBuffer.wrap(value));" << endl; + }else{ + indent(out) << "(java.nio.ByteBuffer.wrap(value.clone()));" << endl; + } + + indent(out) << "}" << endl; + + out << endl; + } + if (is_deprecated) { + indent(out) << "@Deprecated" << endl; + } + indent(out) << "public void set" << get_cap_name(field->get_name()) << "(" + << type_name(field->get_type()) << " value) {" << endl; + if (type_can_be_null(field->get_type())) { + indent(out) << " if (value == null) throw new java.lang.NullPointerException();" << endl; + } + indent(out) << " setField_ = _Fields." << constant_name(field->get_name()) << ";" << endl; + indent(out) << " value_ = value;" << endl; + indent(out) << "}" << endl; + } +} + +void t_java_generator::generate_union_is_set_methods(ostream& out, t_struct* tstruct) { + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + bool first = true; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if (first) { + first = false; + } else { + out << endl; + } + + std::string field_name = (*m_iter)->get_name(); + + indent(out) << "public boolean is" << get_cap_name("set") << get_cap_name(field_name) << "() {" + << endl; + indent_up(); + indent(out) << "return setField_ == _Fields." << constant_name(field_name) << ";" << endl; + indent_down(); + indent(out) << "}" << endl << endl; + } +} + +void t_java_generator::generate_union_abstract_methods(ostream& out, t_struct* tstruct) { + generate_check_type(out, tstruct); + out << endl; + generate_standard_scheme_read_value(out, tstruct); + out << endl; + generate_standard_scheme_write_value(out, tstruct); + out << endl; + generate_tuple_scheme_read_value(out, tstruct); + out << endl; + generate_tuple_scheme_write_value(out, tstruct); + out << endl; + generate_get_field_desc(out, tstruct); + out << endl; + generate_get_struct_desc(out, tstruct); + out << endl; + indent(out) << "@Override" << endl; + indent(out) << "protected _Fields enumForId(short id) {" << endl; + indent(out) << " return _Fields.findByThriftIdOrThrow(id);" << endl; + indent(out) << "}" << endl; +} + +void t_java_generator::generate_check_type(ostream& out, t_struct* tstruct) { + indent(out) << "@Override" << endl; + indent(out) + << "protected void checkType(_Fields setField, java.lang.Object value) throws java.lang.ClassCastException {" + << endl; + indent_up(); + + indent(out) << "switch (setField) {" << endl; + indent_up(); + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_field* field = (*m_iter); + + indent(out) << "case " << constant_name(field->get_name()) << ":" << endl; + indent(out) << " if (value instanceof " << type_name(field->get_type(), true, false, true) + << ") {" << endl; + indent(out) << " break;" << endl; + indent(out) << " }" << endl; + indent(out) << " throw new java.lang.ClassCastException(\"Was expecting value of type " + << type_name(field->get_type(), true, false) << " for field '" << field->get_name() + << "', but got \" + value.getClass().getSimpleName());" << endl; + // do the real check here + } + + indent(out) << "default:" << endl; + indent(out) << " throw new java.lang.IllegalArgumentException(\"Unknown field id \" + setField);" << endl; + + indent_down(); + indent(out) << "}" << endl; + + indent_down(); + indent(out) << "}" << endl; +} + +void t_java_generator::generate_standard_scheme_read_value(ostream& out, t_struct* tstruct) { + indent(out) << "@Override" << endl; + indent(out) << "protected java.lang.Object standardSchemeReadValue(org.apache.thrift.protocol.TProtocol " + "iprot, org.apache.thrift.protocol.TField field) throws " + "org.apache.thrift.TException {" << endl; + + indent_up(); + + indent(out) << "_Fields setField = _Fields.findByThriftId(field.id);" << endl; + indent(out) << "if (setField != null) {" << endl; + indent_up(); + indent(out) << "switch (setField) {" << endl; + indent_up(); + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_field* field = (*m_iter); + + indent(out) << "case " << constant_name(field->get_name()) << ":" << endl; + indent_up(); + indent(out) << "if (field.type == " << constant_name(field->get_name()) << "_FIELD_DESC.type) {" + << endl; + indent_up(); + indent(out) << type_name(field->get_type(), true, false) << " " << field->get_name() << ";" + << endl; + generate_deserialize_field(out, field, ""); + indent(out) << "return " << field->get_name() << ";" << endl; + indent_down(); + indent(out) << "} else {" << endl; + indent(out) << " org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);" << endl; + indent(out) << " return null;" << endl; + indent(out) << "}" << endl; + indent_down(); + } + + indent(out) << "default:" << endl; + indent(out) << " throw new java.lang.IllegalStateException(\"setField wasn't null, but didn't match any " + "of the case statements!\");" << endl; + + indent_down(); + indent(out) << "}" << endl; + + indent_down(); + indent(out) << "} else {" << endl; + indent_up(); + indent(out) << "org.apache.thrift.protocol.TProtocolUtil.skip(iprot, field.type);" << endl; + indent(out) << "return null;" << endl; + indent_down(); + indent(out) << "}" << endl; + + indent_down(); + indent(out) << "}" << endl; +} + +void t_java_generator::generate_standard_scheme_write_value(ostream& out, t_struct* tstruct) { + indent(out) << "@Override" << endl; + indent(out) << "protected void standardSchemeWriteValue(org.apache.thrift.protocol.TProtocol " + "oprot) throws org.apache.thrift.TException {" << endl; + + indent_up(); + + indent(out) << "switch (setField_) {" << endl; + indent_up(); + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_field* field = (*m_iter); + + indent(out) << "case " << constant_name(field->get_name()) << ":" << endl; + indent_up(); + indent(out) << type_name(field->get_type(), true, false) << " " << field->get_name() << " = (" + << type_name(field->get_type(), true, false) << ")value_;" << endl; + generate_serialize_field(out, field, ""); + indent(out) << "return;" << endl; + indent_down(); + } + + indent(out) << "default:" << endl; + indent(out) << " throw new java.lang.IllegalStateException(\"Cannot write union with unknown field \" + " + "setField_);" << endl; + + indent_down(); + indent(out) << "}" << endl; + + indent_down(); + + indent(out) << "}" << endl; +} + +void t_java_generator::generate_tuple_scheme_read_value(ostream& out, t_struct* tstruct) { + indent(out) << "@Override" << endl; + indent(out) << "protected java.lang.Object tupleSchemeReadValue(org.apache.thrift.protocol.TProtocol " + "iprot, short fieldID) throws org.apache.thrift.TException {" << endl; + + indent_up(); + + indent(out) << "_Fields setField = _Fields.findByThriftId(fieldID);" << endl; + indent(out) << "if (setField != null) {" << endl; + indent_up(); + indent(out) << "switch (setField) {" << endl; + indent_up(); + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_field* field = (*m_iter); + + indent(out) << "case " << constant_name(field->get_name()) << ":" << endl; + indent_up(); + indent(out) << type_name(field->get_type(), true, false) << " " << field->get_name() << ";" + << endl; + generate_deserialize_field(out, field, ""); + indent(out) << "return " << field->get_name() << ";" << endl; + indent_down(); + } + + indent(out) << "default:" << endl; + indent(out) << " throw new java.lang.IllegalStateException(\"setField wasn't null, but didn't match any " + "of the case statements!\");" << endl; + + indent_down(); + indent(out) << "}" << endl; + + indent_down(); + indent(out) << "} else {" << endl; + indent_up(); + indent(out) << "throw new org.apache.thrift.protocol.TProtocolException(\"Couldn't find a field with field id \" + fieldID);" + << endl; + indent_down(); + indent(out) << "}" << endl; + indent_down(); + indent(out) << "}" << endl; +} + +void t_java_generator::generate_tuple_scheme_write_value(ostream& out, t_struct* tstruct) { + indent(out) << "@Override" << endl; + indent(out) << "protected void tupleSchemeWriteValue(org.apache.thrift.protocol.TProtocol oprot) " + "throws org.apache.thrift.TException {" << endl; + + indent_up(); + + indent(out) << "switch (setField_) {" << endl; + indent_up(); + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_field* field = (*m_iter); + + indent(out) << "case " << constant_name(field->get_name()) << ":" << endl; + indent_up(); + indent(out) << type_name(field->get_type(), true, false) << " " << field->get_name() << " = (" + << type_name(field->get_type(), true, false) << ")value_;" << endl; + generate_serialize_field(out, field, ""); + indent(out) << "return;" << endl; + indent_down(); + } + + indent(out) << "default:" << endl; + indent(out) << " throw new java.lang.IllegalStateException(\"Cannot write union with unknown field \" + " + "setField_);" << endl; + + indent_down(); + indent(out) << "}" << endl; + + indent_down(); + + indent(out) << "}" << endl; +} + +void t_java_generator::generate_get_field_desc(ostream& out, t_struct* tstruct) { + indent(out) << "@Override" << endl; + indent(out) << "protected org.apache.thrift.protocol.TField getFieldDesc(_Fields setField) {" + << endl; + indent_up(); + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + indent(out) << "switch (setField) {" << endl; + indent_up(); + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_field* field = (*m_iter); + indent(out) << "case " << constant_name(field->get_name()) << ":" << endl; + indent(out) << " return " << constant_name(field->get_name()) << "_FIELD_DESC;" << endl; + } + + indent(out) << "default:" << endl; + indent(out) << " throw new java.lang.IllegalArgumentException(\"Unknown field id \" + setField);" << endl; + + indent_down(); + indent(out) << "}" << endl; + + indent_down(); + indent(out) << "}" << endl; +} + +void t_java_generator::generate_get_struct_desc(ostream& out, t_struct* tstruct) { + (void)tstruct; + indent(out) << "@Override" << endl; + indent(out) << "protected org.apache.thrift.protocol.TStruct getStructDesc() {" << endl; + indent(out) << " return STRUCT_DESC;" << endl; + indent(out) << "}" << endl; +} + +void t_java_generator::generate_union_comparisons(ostream& out, t_struct* tstruct) { + // equality + indent(out) << "public boolean equals(java.lang.Object other) {" << endl; + indent(out) << " if (other instanceof " << tstruct->get_name() << ") {" << endl; + indent(out) << " return equals((" << tstruct->get_name() << ")other);" << endl; + indent(out) << " } else {" << endl; + indent(out) << " return false;" << endl; + indent(out) << " }" << endl; + indent(out) << "}" << endl; + + out << endl; + + indent(out) << "public boolean equals(" << tstruct->get_name() << " other) {" << endl; + indent(out) << " return other != null && getSetField() == other.getSetField() && " + "getFieldValue().equals(other.getFieldValue());" << endl; + indent(out) << "}" << endl; + out << endl; + + indent(out) << "@Override" << endl; + indent(out) << "public int compareTo(" << type_name(tstruct) << " other) {" << endl; + indent(out) << " int lastComparison = org.apache.thrift.TBaseHelper.compareTo(getSetField(), " + "other.getSetField());" << endl; + indent(out) << " if (lastComparison == 0) {" << endl; + indent(out) << " return org.apache.thrift.TBaseHelper.compareTo(getFieldValue(), " + "other.getFieldValue());" << endl; + indent(out) << " }" << endl; + indent(out) << " return lastComparison;" << endl; + indent(out) << "}" << endl; + out << endl; +} + +void t_java_generator::generate_union_hashcode(ostream& out, t_struct* tstruct) { + (void)tstruct; + indent(out) << "@Override" << endl; + indent(out) << "public int hashCode() {" << endl; + indent(out) << " java.util.List<java.lang.Object> list = new java.util.ArrayList<java.lang.Object>();" << endl; + indent(out) << " list.add(this.getClass().getName());" << endl; + indent(out) << " org.apache.thrift.TFieldIdEnum setField = getSetField();" << endl; + indent(out) << " if (setField != null) {" << endl; + indent(out) << " list.add(setField.getThriftFieldId());" << endl; + indent(out) << " java.lang.Object value = getFieldValue();" << endl; + indent(out) << " if (value instanceof org.apache.thrift.TEnum) {" << endl; + indent(out) << " list.add(((org.apache.thrift.TEnum)getFieldValue()).getValue());" << endl; + indent(out) << " } else {" << endl; + indent(out) << " list.add(value);" << endl; + indent(out) << " }" << endl; + indent(out) << " }" << endl; + indent(out) << " return list.hashCode();" << endl; + indent(out) << "}"; +} + +/** + * Java struct definition. This has various parameters, as it could be + * generated standalone or inside another class as a helper. If it + * is a helper than it is a static class. + * + * @param tstruct The struct definition + * @param is_exception Is this an exception? + * @param in_class If inside a class, needs to be static class + * @param is_result If this is a result it needs a different writer + */ +void t_java_generator::generate_java_struct_definition(ostream& out, + t_struct* tstruct, + bool is_exception, + bool in_class, + bool is_result) { + generate_java_doc(out, tstruct); + + bool is_final = (tstruct->annotations_.find("final") != tstruct->annotations_.end()); + bool is_deprecated = this->is_deprecated(tstruct->annotations_); + + if (!in_class && !suppress_generated_annotations_) { + generate_javax_generated_annotation(out); + } + + if (is_deprecated) { + indent(out) << "@Deprecated" << endl; + } + indent(out) << "public " << (is_final ? "final " : "") << (in_class ? "static " : "") << "class " + << tstruct->get_name() << " "; + + if (is_exception) { + out << "extends org.apache.thrift.TException "; + } + out << "implements org.apache.thrift.TBase<" << tstruct->get_name() << ", " << tstruct->get_name() + << "._Fields>, java.io.Serializable, Cloneable, Comparable<" << tstruct->get_name() << ">"; + + if (android_style_) { + out << ", android.os.Parcelable"; + } + + out << " "; + + scope_up(out); + + generate_struct_desc(out, tstruct); + + // Members are public for -java, private for -javabean + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + out << endl; + + generate_field_descs(out, tstruct); + + out << endl; + + generate_scheme_map(out, tstruct); + + out << endl; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if (bean_style_ || private_members_) { + indent(out) << "private "; + } else { + generate_java_doc(out, *m_iter); + indent(out) << "public "; + } + out << declare_field(*m_iter, false, true) << endl; + } + + out << endl; + + if (android_style_) { + generate_java_struct_parcelable(out, tstruct); + } + + generate_field_name_constants(out, tstruct); + + // isset data + if (members.size() > 0) { + out << endl; + + indent(out) << "// isset id assignments" << endl; + + int i = 0; + int optionals = 0; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if ((*m_iter)->get_req() == t_field::T_OPTIONAL) { + optionals++; + } + if (!type_can_be_null((*m_iter)->get_type())) { + indent(out) << "private static final int " << isset_field_id(*m_iter) << " = " << i << ";" + << endl; + i++; + } + } + + std::string primitiveType; + switch (needs_isset(tstruct, &primitiveType)) { + case ISSET_NONE: + break; + case ISSET_PRIMITIVE: + indent(out) << "private " << primitiveType << " __isset_bitfield = 0;" << endl; + break; + case ISSET_BITSET: + indent(out) << "private java.util.BitSet __isset_bit_vector = new java.util.BitSet(" << i << ");" << endl; + break; + } + + if (optionals > 0) { + std::string output_string = "private static final _Fields optionals[] = {"; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if ((*m_iter)->get_req() == t_field::T_OPTIONAL) { + output_string = output_string + "_Fields." + constant_name((*m_iter)->get_name()) + ","; + } + } + indent(out) << output_string.substr(0, output_string.length() - 1) << "};" << endl; + } + } + + generate_java_meta_data_map(out, tstruct); + + bool all_optional_members = true; + + // Default constructor + indent(out) << "public " << tstruct->get_name() << "() {" << endl; + indent_up(); + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* t = get_true_type((*m_iter)->get_type()); + if ((*m_iter)->get_value() != NULL) { + print_const_value(out, + "this." + (*m_iter)->get_name(), + t, + (*m_iter)->get_value(), + true, + true); + } + if ((*m_iter)->get_req() != t_field::T_OPTIONAL) { + all_optional_members = false; + } + } + indent_down(); + indent(out) << "}" << endl << endl; + + if (!members.empty() && !all_optional_members) { + // Full constructor for all fields + indent(out) << "public " << tstruct->get_name() << "(" << endl; + indent_up(); + bool first = true; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if ((*m_iter)->get_req() != t_field::T_OPTIONAL) { + if (!first) { + out << "," << endl; + } + first = false; + indent(out) << type_name((*m_iter)->get_type()) << " " << (*m_iter)->get_name(); + } + } + out << ")" << endl; + indent_down(); + indent(out) << "{" << endl; + indent_up(); + indent(out) << "this();" << endl; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if ((*m_iter)->get_req() != t_field::T_OPTIONAL) { + t_type* type = get_true_type((*m_iter)->get_type()); + if (type->is_binary()) { + if(unsafe_binaries_){ + indent(out) << "this." << (*m_iter)->get_name() + << " = " << (*m_iter)->get_name() + << ";" << endl; + }else{ + indent(out) << "this." << (*m_iter)->get_name() + << " = org.apache.thrift.TBaseHelper.copyBinary(" << (*m_iter)->get_name() + << ");" << endl; + } + } else { + indent(out) << "this." << (*m_iter)->get_name() << " = " << (*m_iter)->get_name() << ";" + << endl; + } + generate_isset_set(out, (*m_iter), ""); + } + } + + indent_down(); + indent(out) << "}" << endl << endl; + } + + // copy constructor + indent(out) << "/**" << endl; + indent(out) << " * Performs a deep copy on <i>other</i>." << endl; + indent(out) << " */" << endl; + indent(out) << "public " << tstruct->get_name() << "(" << tstruct->get_name() << " other) {" + << endl; + indent_up(); + + switch (needs_isset(tstruct)) { + case ISSET_NONE: + break; + case ISSET_PRIMITIVE: + indent(out) << "__isset_bitfield = other.__isset_bitfield;" << endl; + break; + case ISSET_BITSET: + indent(out) << "__isset_bit_vector.clear();" << endl; + indent(out) << "__isset_bit_vector.or(other.__isset_bit_vector);" << endl; + break; + } + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_field* field = (*m_iter); + std::string field_name = field->get_name(); + t_type* type = field->get_type()->get_true_type(); + bool can_be_null = type_can_be_null(type); + + if (can_be_null) { + indent(out) << "if (other." << generate_isset_check(field) << ") {" << endl; + indent_up(); + } + + if (type->is_container()) { + generate_deep_copy_container(out, "other", field_name, "__this__" + field_name, type); + indent(out) << "this." << field_name << " = __this__" << field_name << ";" << endl; + } else { + indent(out) << "this." << field_name << " = "; + generate_deep_copy_non_container(out, "other." + field_name, field_name, type); + out << ";" << endl; + } + + if (can_be_null) { + indent_down(); + indent(out) << "}" << endl; + } + } + + indent_down(); + indent(out) << "}" << endl << endl; + + // clone method, so that you can deep copy an object when you don't know its class. + indent(out) << "public " << tstruct->get_name() << " deepCopy() {" << endl; + indent(out) << " return new " << tstruct->get_name() << "(this);" << endl; + indent(out) << "}" << endl << endl; + + generate_java_struct_clear(out, tstruct); + + generate_java_bean_boilerplate(out, tstruct); + generate_generic_field_getters_setters(out, tstruct); + generate_generic_isset_method(out, tstruct); + + generate_java_struct_equality(out, tstruct); + generate_java_struct_compare_to(out, tstruct); + generate_java_struct_field_by_id(out, tstruct); + + generate_java_struct_reader(out, tstruct); + if (is_result) { + generate_java_struct_result_writer(out, tstruct); + } else { + generate_java_struct_writer(out, tstruct); + } + generate_java_struct_tostring(out, tstruct); + generate_java_validator(out, tstruct); + + generate_java_struct_write_object(out, tstruct); + generate_java_struct_read_object(out, tstruct); + + generate_java_struct_standard_scheme(out, tstruct, is_result); + generate_java_struct_tuple_scheme(out, tstruct); + generate_java_scheme_lookup(out); + + scope_down(out); + out << endl; +} + +/** + * generates parcelable interface implementation + */ +void t_java_generator::generate_java_struct_parcelable(ostream& out, t_struct* tstruct) { + string tname = tstruct->get_name(); + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + out << indent() << "@Override" << endl << indent() + << "public void writeToParcel(android.os.Parcel out, int flags) {" << endl; + indent_up(); + string bitsetPrimitiveType = ""; + switch (needs_isset(tstruct, &bitsetPrimitiveType)) { + case ISSET_NONE: + break; + case ISSET_PRIMITIVE: + indent(out) << "//primitive bitfield of type: " << bitsetPrimitiveType << endl; + if (bitsetPrimitiveType == "byte") { + indent(out) << "out.writeByte(__isset_bitfield);" << endl; + } else if (bitsetPrimitiveType == "short") { + indent(out) << "out.writeInt(new Short(__isset_bitfield).intValue());" << endl; + } else if (bitsetPrimitiveType == "int") { + indent(out) << "out.writeInt(__isset_bitfield);" << endl; + } else if (bitsetPrimitiveType == "long") { + indent(out) << "out.writeLong(__isset_bitfield);" << endl; + } + out << endl; + break; + case ISSET_BITSET: + indent(out) << "//BitSet" << endl; + indent(out) << "out.writeSerializable(__isset_bit_vector);" << endl; + out << endl; + break; + } + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* t = get_true_type((*m_iter)->get_type()); + string name = (*m_iter)->get_name(); + + if (t->is_struct()) { + indent(out) << "out.writeParcelable(" << name << ", flags);" << endl; + } else if (type_name(t) == "float") { + indent(out) << "out.writeFloat(" << name << ");" << endl; + } else if (t->is_enum()) { + indent(out) << "out.writeInt(" << name << " != null ? " << name << ".getValue() : -1);" << endl; + } else if (t->is_list()) { + if (((t_list*)t)->get_elem_type()->get_true_type()->is_struct()) { + indent(out) << "out.writeTypedList(" << name << ");" << endl; + } else { + indent(out) << "out.writeList(" << name << ");" << endl; + } + } else if (t->is_map()) { + indent(out) << "out.writeMap(" << name << ");" << endl; + } else if (t->is_base_type()) { + if (t->is_binary()) { + indent(out) << "out.writeInt(" << name << "!=null ? 1 : 0);" << endl; + indent(out) << "if(" << name << " != null) { " << endl; + indent_up(); + indent(out) << "out.writeByteArray(" << name << ".array(), " << name << ".position() + " + << name << ".arrayOffset(), " << name << ".limit() - " << name + << ".position() );" << endl; + scope_down(out); + } else { + switch (((t_base_type*)t)->get_base()) { + case t_base_type::TYPE_I16: + indent(out) << "out.writeInt(new Short(" << name << ").intValue());" << endl; + break; + case t_base_type::TYPE_I32: + indent(out) << "out.writeInt(" << name << ");" << endl; + break; + case t_base_type::TYPE_I64: + indent(out) << "out.writeLong(" << name << ");" << endl; + break; + case t_base_type::TYPE_BOOL: + indent(out) << "out.writeInt(" << name << " ? 1 : 0);" << endl; + break; + case t_base_type::TYPE_I8: + indent(out) << "out.writeByte(" << name << ");" << endl; + break; + case t_base_type::TYPE_DOUBLE: + indent(out) << "out.writeDouble(" << name << ");" << endl; + break; + case t_base_type::TYPE_STRING: + indent(out) << "out.writeString(" << name << ");" << endl; + break; + case t_base_type::TYPE_VOID: + break; + } + } + } + } + scope_down(out); + out << endl; + + out << indent() << "@Override" << endl << indent() << "public int describeContents() {" << endl; + indent_up(); + out << indent() << "return 0;" << endl; + scope_down(out); + out << endl; + + indent(out) << "public " << tname << "(android.os.Parcel in) {" << endl; + indent_up(); + // read in the required bitfield + switch (needs_isset(tstruct, &bitsetPrimitiveType)) { + case ISSET_NONE: + break; + case ISSET_PRIMITIVE: + indent(out) << "//primitive bitfield of type: " << bitsetPrimitiveType << endl; + if (bitsetPrimitiveType == "byte") { + indent(out) << "__isset_bitfield = in.readByte();" << endl; + } else if (bitsetPrimitiveType == "short") { + indent(out) << "__isset_bitfield = (short) in.readInt();" << endl; + } else if (bitsetPrimitiveType == "int") { + indent(out) << "__isset_bitfield = in.readInt();" << endl; + } else if (bitsetPrimitiveType == "long") { + indent(out) << "__isset_bitfield = in.readLong();" << endl; + } + out << endl; + break; + case ISSET_BITSET: + indent(out) << "//BitSet" << endl; + indent(out) << "__isset_bit_vector = (java.util.BitSet) in.readSerializable();" << endl; + out << endl; + break; + } + // read all the fields + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* t = get_true_type((*m_iter)->get_type()); + string name = (*m_iter)->get_name(); + string prefix = "this." + name; + + if (t->is_struct()) { + indent(out) << prefix << "= in.readParcelable(" << tname << ".class.getClassLoader());" + << endl; + } else if (t->is_enum()) { + indent(out) << prefix << " = " << type_name(t) << ".findByValue(in.readInt());" << endl; + } else if (t->is_list()) { + t_list* list = (t_list*)t; + indent(out) << prefix << " = new " << type_name(t, false, true) << "();" << endl; + if (list->get_elem_type()->get_true_type()->is_struct()) { + indent(out) << "in.readTypedList(" << prefix << ", " << type_name(list->get_elem_type()) + << ".CREATOR);" << endl; + } else { + indent(out) << "in.readList(" << prefix << ", " << tname << ".class.getClassLoader());" + << endl; + } + } else if (t->is_map()) { + indent(out) << prefix << " = new " << type_name(t, false, true) << "();" << endl; + indent(out) << " in.readMap(" << prefix << ", " << tname << ".class.getClassLoader());" + << endl; + } else if (type_name(t) == "float") { + indent(out) << prefix << " = in.readFloat();" << endl; + } else if (t->is_base_type()) { + t_base_type* bt = (t_base_type*)t; + if (bt->is_binary()) { + indent(out) << "if(in.readInt()==1) {" << endl; + indent_up(); + indent(out) << prefix << " = java.nio.ByteBuffer.wrap(in.createByteArray());" << endl; + scope_down(out); + } else { + switch (bt->get_base()) { + case t_base_type::TYPE_I16: + indent(out) << prefix << " = (short) in.readInt();" << endl; + break; + case t_base_type::TYPE_I32: + indent(out) << prefix << " = in.readInt();" << endl; + break; + case t_base_type::TYPE_I64: + indent(out) << prefix << " = in.readLong();" << endl; + break; + case t_base_type::TYPE_BOOL: + indent(out) << prefix << " = (in.readInt()==1);" << endl; + break; + case t_base_type::TYPE_I8: + indent(out) << prefix << " = in.readByte();" << endl; + break; + case t_base_type::TYPE_DOUBLE: + indent(out) << prefix << " = in.readDouble();" << endl; + break; + case t_base_type::TYPE_STRING: + indent(out) << prefix << "= in.readString();" << endl; + break; + case t_base_type::TYPE_VOID: + break; + } + } + } + } + + scope_down(out); + out << endl; + + indent(out) << "public static final android.os.Parcelable.Creator<" << tname + << "> CREATOR = new android.os.Parcelable.Creator<" << tname << ">() {" << endl; + indent_up(); + + indent(out) << "@Override" << endl << indent() << "public " << tname << "[] newArray(int size) {" + << endl; + indent_up(); + indent(out) << "return new " << tname << "[size];" << endl; + scope_down(out); + out << endl; + + indent(out) << "@Override" << endl << indent() << "public " << tname + << " createFromParcel(android.os.Parcel in) {" << endl; + indent_up(); + indent(out) << "return new " << tname << "(in);" << endl; + scope_down(out); + + indent_down(); + indent(out) << "};" << endl; + out << endl; +} + +/** + * Generates equals methods and a hashCode method for a structure. + * + * @param tstruct The struct definition + */ +void t_java_generator::generate_java_struct_equality(ostream& out, t_struct* tstruct) { + out << indent() << "@Override" << endl << indent() << "public boolean equals(java.lang.Object that) {" + << endl; + indent_up(); + out << indent() << "if (that == null)" << endl << indent() << " return false;" << endl + << indent() << "if (that instanceof " << tstruct->get_name() << ")" << endl << indent() + << " return this.equals((" << tstruct->get_name() << ")that);" << endl << indent() + << "return false;" << endl; + scope_down(out); + out << endl; + + out << indent() << "public boolean equals(" << tstruct->get_name() << " that) {" << endl; + indent_up(); + out << indent() << "if (that == null)" << endl << indent() << " return false;" << endl + << indent() << "if (this == that)" << endl << indent() << " return true;" << endl; + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + out << endl; + + t_type* t = get_true_type((*m_iter)->get_type()); + // Most existing Thrift code does not use isset or optional/required, + // so we treat "default" fields as required. + bool is_optional = (*m_iter)->get_req() == t_field::T_OPTIONAL; + bool can_be_null = type_can_be_null(t); + string name = (*m_iter)->get_name(); + + string this_present = "true"; + string that_present = "true"; + string unequal; + + if (is_optional || can_be_null) { + this_present += " && this." + generate_isset_check(*m_iter); + that_present += " && that." + generate_isset_check(*m_iter); + } + + out << indent() << "boolean this_present_" << name << " = " << this_present << ";" << endl + << indent() << "boolean that_present_" << name << " = " << that_present << ";" << endl + << indent() << "if (" + << "this_present_" << name << " || that_present_" << name << ") {" << endl; + indent_up(); + out << indent() << "if (!(" + << "this_present_" << name << " && that_present_" << name << "))" << endl << indent() + << " return false;" << endl; + + if (t->is_binary()) { + unequal = "!this." + name + ".equals(that." + name + ")"; + } else if (can_be_null) { + unequal = "!this." + name + ".equals(that." + name + ")"; + } else { + unequal = "this." + name + " != that." + name; + } + + out << indent() << "if (" << unequal << ")" << endl << indent() << " return false;" << endl; + + scope_down(out); + } + out << endl; + indent(out) << "return true;" << endl; + scope_down(out); + out << endl; + + const int MUL = 8191; // HashCode multiplier + const int B_YES = 131071; + const int B_NO = 524287; + out << indent() << "@Override" << endl << indent() << "public int hashCode() {" << endl; + indent_up(); + indent(out) << "int hashCode = 1;" << endl; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + out << endl; + + t_type* t = get_true_type((*m_iter)->get_type()); + bool is_optional = (*m_iter)->get_req() == t_field::T_OPTIONAL; + bool can_be_null = type_can_be_null(t); + string name = (*m_iter)->get_name(); + + if (is_optional || can_be_null) { + indent(out) << "hashCode = hashCode * " << MUL << " + ((" << generate_isset_check(*m_iter) + << ") ? " << B_YES << " : " << B_NO << ");" << endl; + } + + if (is_optional || can_be_null) { + indent(out) << "if (" + generate_isset_check(*m_iter) + ")" << endl; + indent_up(); + } + + if (t->is_enum()) { + indent(out) << "hashCode = hashCode * " << MUL << " + " << name << ".getValue();" << endl; + } else if (t->is_base_type()) { + switch(((t_base_type*)t)->get_base()) { + case t_base_type::TYPE_STRING: + indent(out) << "hashCode = hashCode * " << MUL << " + " << name << ".hashCode();" << endl; + break; + case t_base_type::TYPE_BOOL: + indent(out) << "hashCode = hashCode * " << MUL << " + ((" << name << ") ? " + << B_YES << " : " << B_NO << ");" << endl; + break; + case t_base_type::TYPE_I8: + indent(out) << "hashCode = hashCode * " << MUL << " + (int) (" << name << ");" << endl; + break; + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + indent(out) << "hashCode = hashCode * " << MUL << " + " << name << ";" << endl; + break; + case t_base_type::TYPE_I64: + case t_base_type::TYPE_DOUBLE: + indent(out) << "hashCode = hashCode * " << MUL << " + org.apache.thrift.TBaseHelper.hashCode(" << name << ");" << endl; + break; + case t_base_type::TYPE_VOID: + throw std::logic_error("compiler error: a struct field cannot be void"); + default: + throw std::logic_error("compiler error: the following base type has no hashcode generator: " + + t_base_type::t_base_name(((t_base_type*)t)->get_base())); + } + } else { + indent(out) << "hashCode = hashCode * " << MUL << " + " << name << ".hashCode();" << endl; + } + + if (is_optional || can_be_null) { + indent_down(); + } + } + + out << endl; + indent(out) << "return hashCode;" << endl; + indent_down(); + indent(out) << "}" << endl << endl; +} + +void t_java_generator::generate_java_struct_compare_to(ostream& out, t_struct* tstruct) { + indent(out) << "@Override" << endl; + indent(out) << "public int compareTo(" << type_name(tstruct) << " other) {" << endl; + indent_up(); + + indent(out) << "if (!getClass().equals(other.getClass())) {" << endl; + indent(out) << " return getClass().getName().compareTo(other.getClass().getName());" << endl; + indent(out) << "}" << endl; + out << endl; + + indent(out) << "int lastComparison = 0;" << endl; + out << endl; + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_field* field = *m_iter; + indent(out) << "lastComparison = java.lang.Boolean.valueOf(" << generate_isset_check(field) + << ").compareTo(other." << generate_isset_check(field) << ");" << endl; + indent(out) << "if (lastComparison != 0) {" << endl; + indent(out) << " return lastComparison;" << endl; + indent(out) << "}" << endl; + + indent(out) << "if (" << generate_isset_check(field) << ") {" << endl; + indent(out) << " lastComparison = org.apache.thrift.TBaseHelper.compareTo(this." + << field->get_name() << ", other." << field->get_name() << ");" << endl; + indent(out) << " if (lastComparison != 0) {" << endl; + indent(out) << " return lastComparison;" << endl; + indent(out) << " }" << endl; + indent(out) << "}" << endl; + } + + indent(out) << "return 0;" << endl; + + indent_down(); + indent(out) << "}" << endl << endl; +} + +/** + * Generates a function to read all the fields of the struct. + * + * @param tstruct The struct definition + */ +void t_java_generator::generate_java_struct_reader(ostream& out, t_struct* tstruct) { + (void)tstruct; + indent(out) << "public void read(org.apache.thrift.protocol.TProtocol iprot) throws " + "org.apache.thrift.TException {" << endl; + indent_up(); + indent(out) << "scheme(iprot).read(iprot, this);" << endl; + indent_down(); + indent(out) << "}" << endl << endl; +} + +// generates java method to perform various checks +// (e.g. check that all required fields are set) +void t_java_generator::generate_java_validator(ostream& out, t_struct* tstruct) { + indent(out) << "public void validate() throws org.apache.thrift.TException {" << endl; + indent_up(); + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + out << indent() << "// check for required fields" << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_REQUIRED) { + if (bean_style_) { + out << indent() << "if (!" << generate_isset_check(*f_iter) << ") {" << endl << indent() + << " throw new org.apache.thrift.protocol.TProtocolException(\"Required field '" + << (*f_iter)->get_name() << "' is unset! Struct:\" + toString());" << endl << indent() + << "}" << endl << endl; + } else { + if (type_can_be_null((*f_iter)->get_type())) { + indent(out) << "if (" << (*f_iter)->get_name() << " == null) {" << endl; + indent(out) + << " throw new org.apache.thrift.protocol.TProtocolException(\"Required field '" + << (*f_iter)->get_name() << "' was not present! Struct: \" + toString());" << endl; + indent(out) << "}" << endl; + } else { + indent(out) << "// alas, we cannot check '" << (*f_iter)->get_name() + << "' because it's a primitive and you chose the non-beans generator." + << endl; + } + } + } + } + + out << indent() << "// check for sub-struct validity" << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_type* type = (*f_iter)->get_type(); + if (type->is_struct() && !((t_struct*)type)->is_union()) { + out << indent() << "if (" << (*f_iter)->get_name() << " != null) {" << endl; + out << indent() << " " << (*f_iter)->get_name() << ".validate();" << endl; + out << indent() << "}" << endl; + } + } + + indent_down(); + indent(out) << "}" << endl << endl; +} + +/** + * Generates a function to write all the fields of the struct + * + * @param tstruct The struct definition + */ +void t_java_generator::generate_java_struct_writer(ostream& out, t_struct* tstruct) { + (void)tstruct; + indent(out) << "public void write(org.apache.thrift.protocol.TProtocol oprot) throws " + "org.apache.thrift.TException {" << endl; + indent_up(); + indent(out) << "scheme(oprot).write(oprot, this);" << endl; + + indent_down(); + indent(out) << "}" << endl << endl; +} + +/** + * Generates a function to write all the fields of the struct, + * which is a function result. These fields are only written + * if they are set in the Isset array, and only one of them + * can be set at a time. + * + * @param tstruct The struct definition + */ +void t_java_generator::generate_java_struct_result_writer(ostream& out, t_struct* tstruct) { + (void)tstruct; + indent(out) << "public void write(org.apache.thrift.protocol.TProtocol oprot) throws " + "org.apache.thrift.TException {" << endl; + indent_up(); + indent(out) << "scheme(oprot).write(oprot, this);" << endl; + + indent_down(); + indent(out) << " }" << endl << endl; +} + +void t_java_generator::generate_java_struct_field_by_id(ostream& out, t_struct* tstruct) { + (void)tstruct; + indent(out) << java_nullable_annotation() << endl; + indent(out) << "public _Fields fieldForId(int fieldId) {" << endl; + indent(out) << " return _Fields.findByThriftId(fieldId);" << endl; + indent(out) << "}" << endl << endl; +} + +void t_java_generator::generate_reflection_getters(ostringstream& out, + t_type* type, + string field_name, + string cap_name) { + indent(out) << "case " << constant_name(field_name) << ":" << endl; + indent_up(); + indent(out) << "return " << (type->is_bool() ? "is" : "get") << cap_name << "();" << endl << endl; + indent_down(); +} + +void t_java_generator::generate_reflection_setters(ostringstream& out, + t_type* type, + string field_name, + string cap_name) { + const bool is_binary = type->is_binary(); + indent(out) << "case " << constant_name(field_name) << ":" << endl; + indent_up(); + indent(out) << "if (value == null) {" << endl; + indent(out) << " unset" << get_cap_name(field_name) << "();" << endl; + indent(out) << "} else {" << endl; + if (is_binary) { + indent_up(); + indent(out) << "if (value instanceof byte[]) {" << endl; + indent(out) << " set" << cap_name << "((byte[])value);" << endl; + indent(out) << "} else {" << endl; + } + indent(out) << " set" << cap_name << "((" << type_name(type, true, false) << ")value);" << endl; + if (is_binary) { + indent(out) << "}" << endl; + indent_down(); + } + indent(out) << "}" << endl; + indent(out) << "break;" << endl << endl; + + indent_down(); +} + +void t_java_generator::generate_generic_field_getters_setters(std::ostream& out, + t_struct* tstruct) { + std::ostringstream getter_stream; + std::ostringstream setter_stream; + + // build up the bodies of both the getter and setter at once + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = *f_iter; + t_type* type = get_true_type(field->get_type()); + std::string field_name = field->get_name(); + std::string cap_name = get_cap_name(field_name); + + indent_up(); + generate_reflection_setters(setter_stream, type, field_name, cap_name); + generate_reflection_getters(getter_stream, type, field_name, cap_name); + indent_down(); + } + + // create the setter + + indent(out) << "public void setFieldValue(_Fields field, " << java_nullable_annotation() + << " java.lang.Object value) {" << endl; + indent(out) << " switch (field) {" << endl; + out << setter_stream.str(); + indent(out) << " }" << endl; + indent(out) << "}" << endl << endl; + + // create the getter + indent(out) << java_nullable_annotation() << endl; + indent(out) << "public java.lang.Object getFieldValue(_Fields field) {" << endl; + indent_up(); + indent(out) << "switch (field) {" << endl; + out << getter_stream.str(); + indent(out) << "}" << endl; + indent(out) << "throw new java.lang.IllegalStateException();" << endl; + indent_down(); + indent(out) << "}" << endl << endl; +} + +// Creates a generic isSet method that takes the field number as argument +void t_java_generator::generate_generic_isset_method(std::ostream& out, t_struct* tstruct) { + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + // create the isSet method + indent(out) << "/** Returns true if field corresponding to fieldID is set (has been assigned a " + "value) and false otherwise */" << endl; + indent(out) << "public boolean isSet(_Fields field) {" << endl; + indent_up(); + indent(out) << "if (field == null) {" << endl; + indent(out) << " throw new java.lang.IllegalArgumentException();" << endl; + indent(out) << "}" << endl << endl; + + indent(out) << "switch (field) {" << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = *f_iter; + indent(out) << "case " << constant_name(field->get_name()) << ":" << endl; + indent_up(); + indent(out) << "return " << generate_isset_check(field) << ";" << endl; + indent_down(); + } + + indent(out) << "}" << endl; + indent(out) << "throw new java.lang.IllegalStateException();" << endl; + indent_down(); + indent(out) << "}" << endl << endl; +} + +/** + * Generates a set of Java Bean boilerplate functions (setters, getters, etc.) + * for the given struct. + * + * @param tstruct The struct definition + */ +void t_java_generator::generate_java_bean_boilerplate(ostream& out, t_struct* tstruct) { + isset_type issetType = needs_isset(tstruct); + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = *f_iter; + t_type* type = get_true_type(field->get_type()); + std::string field_name = field->get_name(); + std::string cap_name = get_cap_name(field_name); + bool optional = use_option_type_ && field->get_req() == t_field::T_OPTIONAL; + bool is_deprecated = this->is_deprecated(field->annotations_); + + if (type->is_container()) { + // Method to return the size of the collection + if (optional) { + if (is_deprecated) { + indent(out) << "@Deprecated" << endl; + } + indent(out) << "public org.apache.thrift.Option<Integer> get" << cap_name; + out << get_cap_name("size() {") << endl; + + indent_up(); + indent(out) << "if (this." << field_name << " == null) {" << endl; + indent_up(); + indent(out) << "return org.apache.thrift.Option.none();" << endl; + indent_down(); + indent(out) << "} else {" << endl; + indent_up(); + indent(out) << "return org.apache.thrift.Option.some(this." << field_name << ".size());" << endl; + indent_down(); + indent(out) << "}" << endl; + indent_down(); + indent(out) << "}" << endl << endl; + } else { + if (is_deprecated) { + indent(out) << "@Deprecated" << endl; + } + indent(out) << "public int get" << cap_name; + out << get_cap_name("size() {") << endl; + + indent_up(); + indent(out) << "return (this." << field_name << " == null) ? 0 : " + << "this." << field_name << ".size();" << endl; + indent_down(); + indent(out) << "}" << endl << endl; + } + } + + if (type->is_set() || type->is_list()) { + t_type* element_type; + if (type->is_set()) { + element_type = ((t_set*)type)->get_elem_type(); + } else { + element_type = ((t_list*)type)->get_elem_type(); + } + + // Iterator getter for sets and lists + if (optional) { + if (is_deprecated) { + indent(out) << "@Deprecated" << endl; + } + indent(out) << "public org.apache.thrift.Option<java.util.Iterator<" << type_name(element_type, true, false) + << ">> get" << cap_name; + out << get_cap_name("iterator() {") << endl; + + indent_up(); + indent(out) << "if (this." << field_name << " == null) {" << endl; + indent_up(); + indent(out) << "return org.apache.thrift.Option.none();" << endl; + indent_down(); + indent(out) << "} else {" << endl; + indent_up(); + indent(out) << "return org.apache.thrift.Option.some(this." << field_name << ".iterator());" << endl; + indent_down(); + indent(out) << "}" << endl; + indent_down(); + indent(out) << "}" << endl << endl; + } else { + if (is_deprecated) { + indent(out) << "@Deprecated" << endl; + } + indent(out) << java_nullable_annotation() << endl; + indent(out) << "public java.util.Iterator<" << type_name(element_type, true, false) + << "> get" << cap_name; + out << get_cap_name("iterator() {") << endl; + + indent_up(); + indent(out) << "return (this." << field_name << " == null) ? null : " + << "this." << field_name << ".iterator();" << endl; + indent_down(); + indent(out) << "}" << endl << endl; + } + + // Add to set or list, create if the set/list is null + if (is_deprecated) { + indent(out) << "@Deprecated" << endl; + } + indent(out) << "public void add" << get_cap_name("to"); + out << cap_name << "(" << type_name(element_type) << " elem) {" << endl; + + indent_up(); + indent(out) << "if (this." << field_name << " == null) {" << endl; + indent_up(); + indent(out) << "this." << field_name; + if (is_enum_set(type)) { + out << " = " << type_name(type, false, true, true) << ".noneOf(" << inner_enum_type_name(type) << ");" << endl; + } else { + out << " = new " << type_name(type, false, true) << "();" << endl; + } + indent_down(); + indent(out) << "}" << endl; + indent(out) << "this." << field_name << ".add(elem);" << endl; + indent_down(); + indent(out) << "}" << endl << endl; + } else if (type->is_map()) { + // Put to map + t_type* key_type = ((t_map*)type)->get_key_type(); + t_type* val_type = ((t_map*)type)->get_val_type(); + + if (is_deprecated) { + indent(out) << "@Deprecated" << endl; + } + indent(out) << "public void put" << get_cap_name("to"); + out << cap_name << "(" << type_name(key_type) << " key, " << type_name(val_type) << " val) {" + << endl; + + indent_up(); + indent(out) << "if (this." << field_name << " == null) {" << endl; + indent_up(); + std::string constructor_args; + if (is_enum_map(type)) { + constructor_args = inner_enum_type_name(type); + } + indent(out) << "this." << field_name << " = new " << type_name(type, false, true) << "(" << constructor_args << ");" + << endl; + indent_down(); + indent(out) << "}" << endl; + indent(out) << "this." << field_name << ".put(key, val);" << endl; + indent_down(); + indent(out) << "}" << endl << endl; + } + + // Simple getter + generate_java_doc(out, field); + if (type->is_binary()) { + if (is_deprecated) { + indent(out) << "@Deprecated" << endl; + } + indent(out) << "public byte[] get" << cap_name << "() {" << endl; + indent(out) << " set" << cap_name << "(org.apache.thrift.TBaseHelper.rightSize(" + << field_name << "));" << endl; + indent(out) << " return " << field_name << " == null ? null : " << field_name << ".array();" + << endl; + indent(out) << "}" << endl << endl; + + indent(out) << "public java.nio.ByteBuffer buffer" << get_cap_name("for") << cap_name << "() {" + << endl; + if(unsafe_binaries_){ + indent(out) << " return " << field_name << ";" + << endl; + }else { + indent(out) << " return org.apache.thrift.TBaseHelper.copyBinary(" << field_name << ");" + << endl; + } + indent(out) << "}" << endl << endl; + } else { + if (optional) { + if (is_deprecated) { + indent(out) << "@Deprecated" << endl; + } + indent(out) << "public org.apache.thrift.Option<" << type_name(type, true) << ">"; + if (type->is_base_type() && ((t_base_type*)type)->get_base() == t_base_type::TYPE_BOOL) { + out << " is"; + } else { + out << " get"; + } + out << cap_name << "() {" << endl; + indent_up(); + + indent(out) << "if (this.isSet" << cap_name << "()) {" << endl; + indent_up(); + indent(out) << "return org.apache.thrift.Option.some(this." << field_name << ");" << endl; + indent_down(); + indent(out) << "} else {" << endl; + indent_up(); + indent(out) << "return org.apache.thrift.Option.none();" << endl; + indent_down(); + indent(out) << "}" << endl; + indent_down(); + indent(out) << "}" << endl << endl; + } else { + if (is_deprecated) { + indent(out) << "@Deprecated" << endl; + } + if (type_can_be_null(type)) { + indent(out) << java_nullable_annotation() << endl; + } + indent(out) << "public " << type_name(type); + if (type->is_base_type() && ((t_base_type*)type)->get_base() == t_base_type::TYPE_BOOL) { + out << " is"; + } else { + out << " get"; + } + out << cap_name << "() {" << endl; + indent_up(); + indent(out) << "return this." << field_name << ";" << endl; + indent_down(); + indent(out) << "}" << endl << endl; + } + } + + // Simple setter + generate_java_doc(out, field); + if (type->is_binary()) { + if (is_deprecated) { + indent(out) << "@Deprecated" << endl; + } + indent(out) << "public "; + if (bean_style_) { + out << "void"; + } else { + out << type_name(tstruct); + } + out << " set" << cap_name << "(byte[] " << field_name << ") {" << endl; + indent(out) << " this." << field_name << " = " << field_name << " == null ? (java.nio.ByteBuffer)null"; + + if(unsafe_binaries_){ + indent(out) << " : java.nio.ByteBuffer.wrap(" << field_name << ");" << endl; + }else{ + indent(out) << " : java.nio.ByteBuffer.wrap(" << field_name << ".clone());" << endl; + } + + if (!bean_style_) { + indent(out) << " return this;" << endl; + } + indent(out) << "}" << endl << endl; + } + if (is_deprecated) { + indent(out) << "@Deprecated" << endl; + } + indent(out) << "public "; + if (bean_style_) { + out << "void"; + } else { + out << type_name(tstruct); + } + out << " set" << cap_name << "(" << (type_can_be_null(type) ? (java_nullable_annotation() + " ") : "") + << type_name(type) << " " << field_name << ") {" << endl; + indent_up(); + indent(out) << "this." << field_name << " = "; + if (type->is_binary() && !unsafe_binaries_) { + out << "org.apache.thrift.TBaseHelper.copyBinary(" << field_name << ")"; + } else { + out << field_name; + } + out << ";" << endl; + generate_isset_set(out, field, ""); + if (!bean_style_) { + indent(out) << "return this;" << endl; + } + + indent_down(); + indent(out) << "}" << endl << endl; + + // Unsetter + if (is_deprecated) { + indent(out) << "@Deprecated" << endl; + } + indent(out) << "public void unset" << cap_name << "() {" << endl; + indent_up(); + if (type_can_be_null(type)) { + indent(out) << "this." << field_name << " = null;" << endl; + } else if (issetType == ISSET_PRIMITIVE) { + indent(out) << "__isset_bitfield = org.apache.thrift.EncodingUtils.clearBit(__isset_bitfield, " + << isset_field_id(field) << ");" << endl; + } else { + indent(out) << "__isset_bit_vector.clear(" << isset_field_id(field) << ");" << endl; + } + indent_down(); + indent(out) << "}" << endl << endl; + + // isSet method + indent(out) << "/** Returns true if field " << field_name + << " is set (has been assigned a value) and false otherwise */" << endl; + if (is_deprecated) { + indent(out) << "@Deprecated" << endl; + } + indent(out) << "public boolean is" << get_cap_name("set") << cap_name << "() {" << endl; + indent_up(); + if (type_can_be_null(type)) { + indent(out) << "return this." << field_name << " != null;" << endl; + } else if (issetType == ISSET_PRIMITIVE) { + indent(out) << "return org.apache.thrift.EncodingUtils.testBit(__isset_bitfield, " << isset_field_id(field) + << ");" << endl; + } else { + indent(out) << "return __isset_bit_vector.get(" << isset_field_id(field) << ");" << endl; + } + indent_down(); + indent(out) << "}" << endl << endl; + + if (is_deprecated) { + indent(out) << "@Deprecated" << endl; + } + indent(out) << "public void set" << cap_name << get_cap_name("isSet") << "(boolean value) {" + << endl; + indent_up(); + if (type_can_be_null(type)) { + indent(out) << "if (!value) {" << endl; + indent(out) << " this." << field_name << " = null;" << endl; + indent(out) << "}" << endl; + } else if (issetType == ISSET_PRIMITIVE) { + indent(out) << "__isset_bitfield = org.apache.thrift.EncodingUtils.setBit(__isset_bitfield, " + << isset_field_id(field) << ", value);" << endl; + } else { + indent(out) << "__isset_bit_vector.set(" << isset_field_id(field) << ", value);" << endl; + } + indent_down(); + indent(out) << "}" << endl << endl; + } +} + +/** + * Generates a toString() method for the given struct + * + * @param tstruct The struct definition + */ +void t_java_generator::generate_java_struct_tostring(ostream& out, t_struct* tstruct) { + out << indent() << "@Override" << endl << indent() << "public java.lang.String toString() {" << endl; + indent_up(); + + out << indent() << "java.lang.StringBuilder sb = new java.lang.StringBuilder(\"" << tstruct->get_name() << "(\");" + << endl; + out << indent() << "boolean first = true;" << endl << endl; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + bool could_be_unset = (*f_iter)->get_req() == t_field::T_OPTIONAL; + if (could_be_unset) { + indent(out) << "if (" << generate_isset_check(*f_iter) << ") {" << endl; + indent_up(); + } + + t_field* field = (*f_iter); + + if (!first) { + indent(out) << "if (!first) sb.append(\", \");" << endl; + } + indent(out) << "sb.append(\"" << (*f_iter)->get_name() << ":\");" << endl; + bool can_be_null = type_can_be_null(field->get_type()); + if (can_be_null) { + indent(out) << "if (this." << (*f_iter)->get_name() << " == null) {" << endl; + indent(out) << " sb.append(\"null\");" << endl; + indent(out) << "} else {" << endl; + indent_up(); + } + + if (get_true_type(field->get_type())->is_binary()) { + indent(out) << "org.apache.thrift.TBaseHelper.toString(this." << field->get_name() << ", sb);" + << endl; + } else if ((field->get_type()->is_set()) + && (get_true_type(((t_set*)field->get_type())->get_elem_type())->is_binary())) { + indent(out) << "org.apache.thrift.TBaseHelper.toString(this." << field->get_name() << ", sb);" + << endl; + } else if ((field->get_type()->is_list()) + && (get_true_type(((t_list*)field->get_type())->get_elem_type())->is_binary())) { + indent(out) << "org.apache.thrift.TBaseHelper.toString(this." << field->get_name() << ", sb);" + << endl; + } else { + indent(out) << "sb.append(this." << (*f_iter)->get_name() << ");" << endl; + } + + if (can_be_null) { + indent_down(); + indent(out) << "}" << endl; + } + indent(out) << "first = false;" << endl; + + if (could_be_unset) { + indent_down(); + indent(out) << "}" << endl; + } + first = false; + } + out << indent() << "sb.append(\")\");" << endl << indent() << "return sb.toString();" << endl; + + indent_down(); + indent(out) << "}" << endl << endl; +} + +/** + * Generates a static map with meta data to store information such as fieldID to + * fieldName mapping + * + * @param tstruct The struct definition + */ +void t_java_generator::generate_java_meta_data_map(ostream& out, t_struct* tstruct) { + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + // Static Map with fieldID -> org.apache.thrift.meta_data.FieldMetaData mappings + indent(out) + << "public static final java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;" + << endl; + indent(out) << "static {" << endl; + indent_up(); + + indent(out) << "java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new " + "java.util.EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);" + << endl; + + // Populate map + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = *f_iter; + std::string field_name = field->get_name(); + indent(out) << "tmpMap.put(_Fields." << constant_name(field_name) + << ", new org.apache.thrift.meta_data.FieldMetaData(\"" << field_name << "\", "; + + // Set field requirement type (required, optional, etc.) + if (field->get_req() == t_field::T_REQUIRED) { + out << "org.apache.thrift.TFieldRequirementType.REQUIRED, "; + } else if (field->get_req() == t_field::T_OPTIONAL) { + out << "org.apache.thrift.TFieldRequirementType.OPTIONAL, "; + } else { + out << "org.apache.thrift.TFieldRequirementType.DEFAULT, "; + } + + // Create value meta data + generate_field_value_meta_data(out, field->get_type()); + out << "));" << endl; + } + + indent(out) << "metaDataMap = java.util.Collections.unmodifiableMap(tmpMap);" << endl; + + indent(out) << "org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(" + << type_name(tstruct) << ".class, metaDataMap);" << endl; + indent_down(); + indent(out) << "}" << endl << endl; +} + +/** + * Returns a string with the java representation of the given thrift type + * (e.g. for the type struct it returns "org.apache.thrift.protocol.TType.STRUCT") + */ +std::string t_java_generator::get_java_type_string(t_type* type) { + if (type->is_list()) { + return "org.apache.thrift.protocol.TType.LIST"; + } else if (type->is_map()) { + return "org.apache.thrift.protocol.TType.MAP"; + } else if (type->is_set()) { + return "org.apache.thrift.protocol.TType.SET"; + } else if (type->is_struct() || type->is_xception()) { + return "org.apache.thrift.protocol.TType.STRUCT"; + } else if (type->is_enum()) { + return "org.apache.thrift.protocol.TType.ENUM"; + } else if (type->is_typedef()) { + return get_java_type_string(((t_typedef*)type)->get_type()); + } else if (type->is_base_type()) { + switch (((t_base_type*)type)->get_base()) { + case t_base_type::TYPE_VOID: + return "org.apache.thrift.protocol.TType.VOID"; + break; + case t_base_type::TYPE_STRING: + return "org.apache.thrift.protocol.TType.STRING"; + break; + case t_base_type::TYPE_BOOL: + return "org.apache.thrift.protocol.TType.BOOL"; + break; + case t_base_type::TYPE_I8: + return "org.apache.thrift.protocol.TType.BYTE"; + break; + case t_base_type::TYPE_I16: + return "org.apache.thrift.protocol.TType.I16"; + break; + case t_base_type::TYPE_I32: + return "org.apache.thrift.protocol.TType.I32"; + break; + case t_base_type::TYPE_I64: + return "org.apache.thrift.protocol.TType.I64"; + break; + case t_base_type::TYPE_DOUBLE: + return "org.apache.thrift.protocol.TType.DOUBLE"; + break; + default: + throw std::runtime_error("Unknown thrift type \"" + type->get_name() + + "\" passed to t_java_generator::get_java_type_string!"); + return "Unknown thrift type \"" + type->get_name() + + "\" passed to t_java_generator::get_java_type_string!"; + break; // This should never happen! + } + } else { + throw std::runtime_error("Unknown thrift type \"" + type->get_name() + + "\" passed to t_java_generator::get_java_type_string!"); + // This should never happen! + } +} + +void t_java_generator::generate_field_value_meta_data(std::ostream& out, t_type* type) { + out << endl; + indent_up(); + indent_up(); + if (type->is_struct() || type->is_xception()) { + indent(out) << "new " + "org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType." + "STRUCT, " << type_name(type) << ".class"; + } else if (type->is_container()) { + if (type->is_list()) { + indent(out) + << "new org.apache.thrift.meta_data.ListMetaData(org.apache.thrift.protocol.TType.LIST, "; + t_type* elem_type = ((t_list*)type)->get_elem_type(); + generate_field_value_meta_data(out, elem_type); + } else if (type->is_set()) { + indent(out) + << "new org.apache.thrift.meta_data.SetMetaData(org.apache.thrift.protocol.TType.SET, "; + t_type* elem_type = ((t_set*)type)->get_elem_type(); + generate_field_value_meta_data(out, elem_type); + } else { // map + indent(out) + << "new org.apache.thrift.meta_data.MapMetaData(org.apache.thrift.protocol.TType.MAP, "; + t_type* key_type = ((t_map*)type)->get_key_type(); + t_type* val_type = ((t_map*)type)->get_val_type(); + generate_field_value_meta_data(out, key_type); + out << ", "; + generate_field_value_meta_data(out, val_type); + } + } else if (type->is_enum()) { + indent(out) + << "new org.apache.thrift.meta_data.EnumMetaData(org.apache.thrift.protocol.TType.ENUM, " + << type_name(type) << ".class"; + } else { + indent(out) << "new org.apache.thrift.meta_data.FieldValueMetaData(" + << get_java_type_string(type); + if (type->is_typedef()) { + indent(out) << ", \"" << ((t_typedef*)type)->get_symbolic() << "\""; + } else if (type->is_binary()) { + indent(out) << ", true"; + } + } + out << ")"; + indent_down(); + indent_down(); +} + +/** + * Generates a thrift service. In C++, this comprises an entirely separate + * header and source file. The header file defines the methods and includes + * the data types defined in the main header file, and the implementation + * file contains implementations of the basic printer and default interfaces. + * + * @param tservice The service definition + */ +void t_java_generator::generate_service(t_service* tservice) { + // Make output file + string f_service_name = package_dir_ + "/" + make_valid_java_filename(service_name_) + ".java"; + f_service_.open(f_service_name.c_str()); + + f_service_ << autogen_comment() << java_package() << java_suppressions(); + + if (!suppress_generated_annotations_) { + generate_javax_generated_annotation(f_service_); + } + f_service_ << "public class " << service_name_ << " {" << endl << endl; + indent_up(); + + // Generate the three main parts of the service + generate_service_interface(tservice); + generate_service_async_interface(tservice); + generate_service_client(tservice); + generate_service_async_client(tservice); + generate_service_server(tservice); + generate_service_async_server(tservice); + generate_service_helpers(tservice); + + indent_down(); + f_service_ << "}" << endl; + f_service_.close(); +} + +/** + * Generates a service interface definition. + * + * @param tservice The service to generate a header definition for + */ +void t_java_generator::generate_service_interface(t_service* tservice) { + string extends = ""; + string extends_iface = ""; + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + extends_iface = " extends " + extends + ".Iface"; + } + + generate_java_doc(f_service_, tservice); + f_service_ << indent() << "public interface Iface" << extends_iface << " {" << endl << endl; + indent_up(); + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_java_doc(f_service_, *f_iter); + indent(f_service_) << "public " << function_signature(*f_iter) << ";" << endl << endl; + } + indent_down(); + f_service_ << indent() << "}" << endl << endl; +} + +void t_java_generator::generate_service_async_interface(t_service* tservice) { + string extends = ""; + string extends_iface = ""; + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + extends_iface = " extends " + extends + " .AsyncIface"; + } + + f_service_ << indent() << "public interface AsyncIface" << extends_iface << " {" << endl << endl; + indent_up(); + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + indent(f_service_) << "public " << function_signature_async(*f_iter, true) + << " throws org.apache.thrift.TException;" << endl << endl; + } + indent_down(); + f_service_ << indent() << "}" << endl << endl; +} + +/** + * Generates structs for all the service args and return types + * + * @param tservice The service + */ +void t_java_generator::generate_service_helpers(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* ts = (*f_iter)->get_arglist(); + generate_java_struct_definition(f_service_, ts, false, true); + generate_function_helpers(*f_iter); + } +} + +/** + * Generates a service client definition. + * + * @param tservice The service to generate a server for. + */ +void t_java_generator::generate_service_client(t_service* tservice) { + string extends = ""; + string extends_client = ""; + if (tservice->get_extends() == NULL) { + extends_client = "org.apache.thrift.TServiceClient"; + } else { + extends = type_name(tservice->get_extends()); + extends_client = extends + ".Client"; + } + + indent(f_service_) << "public static class Client extends " << extends_client + << " implements Iface {" << endl; + indent_up(); + + indent(f_service_) + << "public static class Factory implements org.apache.thrift.TServiceClientFactory<Client> {" + << endl; + indent_up(); + indent(f_service_) << "public Factory() {}" << endl; + indent(f_service_) << "public Client getClient(org.apache.thrift.protocol.TProtocol prot) {" + << endl; + indent_up(); + indent(f_service_) << "return new Client(prot);" << endl; + indent_down(); + indent(f_service_) << "}" << endl; + indent(f_service_) << "public Client getClient(org.apache.thrift.protocol.TProtocol iprot, " + "org.apache.thrift.protocol.TProtocol oprot) {" << endl; + indent_up(); + indent(f_service_) << "return new Client(iprot, oprot);" << endl; + indent_down(); + indent(f_service_) << "}" << endl; + indent_down(); + indent(f_service_) << "}" << endl << endl; + + indent(f_service_) << "public Client(org.apache.thrift.protocol.TProtocol prot)" << endl; + scope_up(f_service_); + indent(f_service_) << "super(prot, prot);" << endl; + scope_down(f_service_); + f_service_ << endl; + + indent(f_service_) << "public Client(org.apache.thrift.protocol.TProtocol iprot, " + "org.apache.thrift.protocol.TProtocol oprot) {" << endl; + indent(f_service_) << " super(iprot, oprot);" << endl; + indent(f_service_) << "}" << endl << endl; + + // Generate client method implementations + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + string funname = (*f_iter)->get_name(); + string sep = "_"; + string javaname = funname; + if (fullcamel_style_) { + sep = ""; + javaname = as_camel_case(funname); + } + + // Open function + indent(f_service_) << "public " << function_signature(*f_iter) << endl; + scope_up(f_service_); + indent(f_service_) << "send" << sep << javaname << "("; + + // Get the struct of function call params + t_struct* arg_struct = (*f_iter)->get_arglist(); + + // Declare the function arguments + const vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator fld_iter; + bool first = true; + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << (*fld_iter)->get_name(); + } + f_service_ << ");" << endl; + + if (!(*f_iter)->is_oneway()) { + f_service_ << indent(); + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_ << "return "; + } + f_service_ << "recv" << sep << javaname << "();" << endl; + } + scope_down(f_service_); + f_service_ << endl; + + t_function send_function(g_type_void, + string("send") + sep + javaname, + (*f_iter)->get_arglist()); + + string argsname = (*f_iter)->get_name() + "_args"; + + // Open function + indent(f_service_) << "public " << function_signature(&send_function) << endl; + scope_up(f_service_); + + // Serialize the request + indent(f_service_) << argsname << " args = new " << argsname << "();" << endl; + + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + indent(f_service_) << "args.set" << get_cap_name((*fld_iter)->get_name()) << "(" + << (*fld_iter)->get_name() << ");" << endl; + } + + const string sendBaseName = (*f_iter)->is_oneway() ? "sendBaseOneway" : "sendBase"; + indent(f_service_) << sendBaseName << "(\"" << funname << "\", args);" << endl; + + scope_down(f_service_); + f_service_ << endl; + + if (!(*f_iter)->is_oneway()) { + string resultname = (*f_iter)->get_name() + "_result"; + + t_struct noargs(program_); + t_function recv_function((*f_iter)->get_returntype(), + string("recv") + sep + javaname, + &noargs, + (*f_iter)->get_xceptions()); + // Open function + indent(f_service_) << "public " << function_signature(&recv_function) << endl; + scope_up(f_service_); + + f_service_ << indent() << resultname << " result = new " << resultname << "();" << endl + << indent() << "receiveBase(result, \"" << funname << "\");" << endl; + + // Careful, only return _result if not a void function + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_ << indent() << "if (result." << generate_isset_check("success") << ") {" << endl + << indent() << " return result.success;" << endl << indent() << "}" << endl; + } + + t_struct* xs = (*f_iter)->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + f_service_ << indent() << "if (result." << (*x_iter)->get_name() << " != null) {" << endl + << indent() << " throw result." << (*x_iter)->get_name() << ";" << endl + << indent() << "}" << endl; + } + + // If you get here it's an exception, unless a void function + if ((*f_iter)->get_returntype()->is_void()) { + indent(f_service_) << "return;" << endl; + } else { + f_service_ << indent() << "throw new " + "org.apache.thrift.TApplicationException(org.apache.thrift." + "TApplicationException.MISSING_RESULT, \"" + << (*f_iter)->get_name() << " failed: unknown result\");" << endl; + } + + // Close function + scope_down(f_service_); + f_service_ << endl; + } + } + + indent_down(); + indent(f_service_) << "}" << endl; +} + +void t_java_generator::generate_service_async_client(t_service* tservice) { + string extends = "org.apache.thrift.async.TAsyncClient"; + string extends_client = ""; + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()) + ".AsyncClient"; + } + + indent(f_service_) << "public static class AsyncClient extends " << extends + << " implements AsyncIface {" << endl; + indent_up(); + + // Factory method + indent(f_service_) << "public static class Factory implements " + "org.apache.thrift.async.TAsyncClientFactory<AsyncClient> {" << endl; + indent(f_service_) << " private org.apache.thrift.async.TAsyncClientManager clientManager;" + << endl; + indent(f_service_) << " private org.apache.thrift.protocol.TProtocolFactory protocolFactory;" + << endl; + indent(f_service_) << " public Factory(org.apache.thrift.async.TAsyncClientManager " + "clientManager, org.apache.thrift.protocol.TProtocolFactory " + "protocolFactory) {" << endl; + indent(f_service_) << " this.clientManager = clientManager;" << endl; + indent(f_service_) << " this.protocolFactory = protocolFactory;" << endl; + indent(f_service_) << " }" << endl; + indent(f_service_) << " public AsyncClient " + "getAsyncClient(org.apache.thrift.transport.TNonblockingTransport " + "transport) {" << endl; + indent(f_service_) << " return new AsyncClient(protocolFactory, clientManager, transport);" + << endl; + indent(f_service_) << " }" << endl; + indent(f_service_) << "}" << endl << endl; + + indent(f_service_) << "public AsyncClient(org.apache.thrift.protocol.TProtocolFactory " + "protocolFactory, org.apache.thrift.async.TAsyncClientManager " + "clientManager, org.apache.thrift.transport.TNonblockingTransport " + "transport) {" << endl; + indent(f_service_) << " super(protocolFactory, clientManager, transport);" << endl; + indent(f_service_) << "}" << endl << endl; + + // Generate client method implementations + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + string funname = (*f_iter)->get_name(); + string sep = "_"; + string javaname = funname; + if (fullcamel_style_) { + sep = ""; + javaname = as_camel_case(javaname); + } + t_type* ret_type = (*f_iter)->get_returntype(); + t_struct* arg_struct = (*f_iter)->get_arglist(); + string funclassname = funname + "_call"; + const vector<t_field*>& fields = arg_struct->get_members(); + const std::vector<t_field*>& xceptions = (*f_iter)->get_xceptions()->get_members(); + vector<t_field*>::const_iterator fld_iter; + string args_name = (*f_iter)->get_name() + "_args"; + string result_name = (*f_iter)->get_name() + "_result"; + + // Main method body + indent(f_service_) << "public " << function_signature_async(*f_iter, false) + << " throws org.apache.thrift.TException {" << endl; + indent(f_service_) << " checkReady();" << endl; + indent(f_service_) << " " << funclassname << " method_call = new " + funclassname + "(" + << async_argument_list(*f_iter, arg_struct, ret_type) + << ", this, ___protocolFactory, ___transport);" << endl; + indent(f_service_) << " this.___currentMethod = method_call;" << endl; + indent(f_service_) << " ___manager.call(method_call);" << endl; + indent(f_service_) << "}" << endl; + + f_service_ << endl; + + // TAsyncMethod object for this function call + indent(f_service_) << "public static class " + funclassname + + " extends org.apache.thrift.async.TAsyncMethodCall<" + + type_name((*f_iter)->get_returntype(), true) + "> {" << endl; + indent_up(); + + // Member variables + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + indent(f_service_) << "private " + type_name((*fld_iter)->get_type()) + " " + + (*fld_iter)->get_name() + ";" << endl; + } + + // NOTE since we use a new Client instance to deserialize, let's keep seqid to 0 for now + // indent(f_service_) << "private int seqid;" << endl << endl; + + // Constructor + indent(f_service_) << "public " + funclassname + "(" + + async_argument_list(*f_iter, arg_struct, ret_type, true) + << ", org.apache.thrift.async.TAsyncClient client, " + "org.apache.thrift.protocol.TProtocolFactory protocolFactory, " + "org.apache.thrift.transport.TNonblockingTransport transport) throws " + "org.apache.thrift.TException {" << endl; + indent(f_service_) << " super(client, protocolFactory, transport, resultHandler, " + << ((*f_iter)->is_oneway() ? "true" : "false") << ");" << endl; + + // Assign member variables + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + indent(f_service_) << " this." + (*fld_iter)->get_name() + " = " + (*fld_iter)->get_name() + + ";" << endl; + } + + indent(f_service_) << "}" << endl << endl; + + indent(f_service_) << "public void write_args(org.apache.thrift.protocol.TProtocol prot) " + "throws org.apache.thrift.TException {" << endl; + indent_up(); + + // Serialize request + // NOTE we are leaving seqid as 0, for now (see above) + f_service_ << indent() << "prot.writeMessageBegin(new org.apache.thrift.protocol.TMessage(\"" + << funname << "\", org.apache.thrift.protocol." + << ((*f_iter)->is_oneway() ? "TMessageType.ONEWAY" : "TMessageType.CALL") << ", 0));" + << endl << indent() << args_name << " args = new " << args_name << "();" << endl; + + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + f_service_ << indent() << "args.set" << get_cap_name((*fld_iter)->get_name()) << "(" + << (*fld_iter)->get_name() << ");" << endl; + } + + f_service_ << indent() << "args.write(prot);" << endl << indent() << "prot.writeMessageEnd();" + << endl; + + indent_down(); + indent(f_service_) << "}" << endl << endl; + + // Return method + indent(f_service_) << "public " + type_name(ret_type, true) + " getResult() throws "; + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + f_service_ << type_name((*x_iter)->get_type(), false, false) + ", "; + } + f_service_ << "org.apache.thrift.TException {" << endl; + + indent_up(); + f_service_ + << indent() + << "if (getState() != org.apache.thrift.async.TAsyncMethodCall.State.RESPONSE_READ) {" + << endl << indent() << " throw new java.lang.IllegalStateException(\"Method call not finished!\");" + << endl << indent() << "}" << endl << indent() + << "org.apache.thrift.transport.TMemoryInputTransport memoryTransport = new " + "org.apache.thrift.transport.TMemoryInputTransport(getFrameBuffer().array());" << endl + << indent() << "org.apache.thrift.protocol.TProtocol prot = " + "client.getProtocolFactory().getProtocol(memoryTransport);" << endl; + indent(f_service_); + if (ret_type->is_void()) { // NB: Includes oneways which always return void. + f_service_ << "return null;" << endl; + } else { + f_service_ << "return (new Client(prot)).recv" + sep + javaname + "();" << endl; + } + + // Close function + indent_down(); + indent(f_service_) << "}" << endl; + + // Close class + indent_down(); + indent(f_service_) << "}" << endl << endl; + } + + // Close AsyncClient + scope_down(f_service_); + f_service_ << endl; +} + +/** + * Generates a service server definition. + * + * @param tservice The service to generate a server for. + */ +void t_java_generator::generate_service_server(t_service* tservice) { + // Generate the dispatch methods + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + // Extends stuff + string extends = ""; + string extends_processor = ""; + if (tservice->get_extends() == NULL) { + extends_processor = "org.apache.thrift.TBaseProcessor<I>"; + } else { + extends = type_name(tservice->get_extends()); + extends_processor = extends + ".Processor<I>"; + } + + // Generate the header portion + indent(f_service_) << "public static class Processor<I extends Iface> extends " + << extends_processor << " implements org.apache.thrift.TProcessor {" << endl; + indent_up(); + + indent(f_service_) + << "private static final org.slf4j.Logger _LOGGER = org.slf4j.LoggerFactory.getLogger(Processor.class.getName());" + << endl; + + indent(f_service_) << "public Processor(I iface) {" << endl; + indent(f_service_) << " super(iface, getProcessMap(new java.util.HashMap<java.lang.String, " + "org.apache.thrift.ProcessFunction<I, ? extends " + "org.apache.thrift.TBase>>()));" << endl; + indent(f_service_) << "}" << endl << endl; + + indent(f_service_) << "protected Processor(I iface, java.util.Map<java.lang.String, " + "org.apache.thrift.ProcessFunction<I, ? extends org.apache.thrift.TBase>> " + "processMap) {" << endl; + indent(f_service_) << " super(iface, getProcessMap(processMap));" << endl; + indent(f_service_) << "}" << endl << endl; + + indent(f_service_) << "private static <I extends Iface> java.util.Map<java.lang.String, " + "org.apache.thrift.ProcessFunction<I, ? extends org.apache.thrift.TBase>> " + "getProcessMap(java.util.Map<java.lang.String, org.apache.thrift.ProcessFunction<I, ? extends " + " org.apache.thrift.TBase>> processMap) {" << endl; + indent_up(); + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + indent(f_service_) << "processMap.put(\"" << (*f_iter)->get_name() << "\", new " + << (*f_iter)->get_name() << "());" << endl; + } + indent(f_service_) << "return processMap;" << endl; + indent_down(); + indent(f_service_) << "}" << endl << endl; + + // Generate the process subfunctions + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_process_function(tservice, *f_iter); + } + + indent_down(); + indent(f_service_) << "}" << endl << endl; +} + +/** + * Generates a service server definition. + * + * @param tservice The service to generate a server for. + */ +void t_java_generator::generate_service_async_server(t_service* tservice) { + // Generate the dispatch methods + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + // Extends stuff + string extends = ""; + string extends_processor = ""; + if (tservice->get_extends() == NULL) { + extends_processor = "org.apache.thrift.TBaseAsyncProcessor<I>"; + } else { + extends = type_name(tservice->get_extends()); + extends_processor = extends + ".AsyncProcessor<I>"; + } + + // Generate the header portion + indent(f_service_) << "public static class AsyncProcessor<I extends AsyncIface> extends " + << extends_processor << " {" << endl; + indent_up(); + + indent(f_service_) << "private static final org.slf4j.Logger _LOGGER = " + "org.slf4j.LoggerFactory.getLogger(AsyncProcessor.class.getName());" << endl; + + indent(f_service_) << "public AsyncProcessor(I iface) {" << endl; + indent(f_service_) << " super(iface, getProcessMap(new java.util.HashMap<java.lang.String, " + "org.apache.thrift.AsyncProcessFunction<I, ? extends " + "org.apache.thrift.TBase, ?>>()));" << endl; + indent(f_service_) << "}" << endl << endl; + + indent(f_service_) << "protected AsyncProcessor(I iface, java.util.Map<java.lang.String, " + "org.apache.thrift.AsyncProcessFunction<I, ? extends " + "org.apache.thrift.TBase, ?>> processMap) {" << endl; + indent(f_service_) << " super(iface, getProcessMap(processMap));" << endl; + indent(f_service_) << "}" << endl << endl; + + indent(f_service_) << "private static <I extends AsyncIface> java.util.Map<java.lang.String, " + "org.apache.thrift.AsyncProcessFunction<I, ? extends " + "org.apache.thrift.TBase,?>> getProcessMap(java.util.Map<java.lang.String, " + "org.apache.thrift.AsyncProcessFunction<I, ? extends " + "org.apache.thrift.TBase, ?>> processMap) {" << endl; + indent_up(); + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + indent(f_service_) << "processMap.put(\"" << (*f_iter)->get_name() << "\", new " + << (*f_iter)->get_name() << "());" << endl; + } + indent(f_service_) << "return processMap;" << endl; + indent_down(); + indent(f_service_) << "}" << endl << endl; + + // Generate the process subfunctions + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_process_async_function(tservice, *f_iter); + } + + indent_down(); + indent(f_service_) << "}" << endl << endl; +} + +/** + * Generates a struct and helpers for a function. + * + * @param tfunction The function + */ +void t_java_generator::generate_function_helpers(t_function* tfunction) { + if (tfunction->is_oneway()) { + return; + } + + t_struct result(program_, tfunction->get_name() + "_result"); + t_field success(tfunction->get_returntype(), "success", 0); + if (!tfunction->get_returntype()->is_void()) { + result.append(&success); + } + + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& fields = xs->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + result.append(*f_iter); + } + + generate_java_struct_definition(f_service_, &result, false, true, true); +} + +/** + * Generates a process function definition. + * + * @param tfunction The function to write a dispatcher for + */ +void t_java_generator::generate_process_async_function(t_service* tservice, t_function* tfunction) { + string argsname = tfunction->get_name() + "_args"; + + string resultname = tfunction->get_name() + "_result"; + if (tfunction->is_oneway()) { + resultname = "org.apache.thrift.TBase"; + } + + string resulttype = type_name(tfunction->get_returntype(), true); + + (void)tservice; + // Open class + indent(f_service_) << "public static class " << tfunction->get_name() + << "<I extends AsyncIface> extends org.apache.thrift.AsyncProcessFunction<I, " + << argsname << ", " << resulttype << "> {" << endl; + indent_up(); + + indent(f_service_) << "public " << tfunction->get_name() << "() {" << endl; + indent(f_service_) << " super(\"" << tfunction->get_name() << "\");" << endl; + indent(f_service_) << "}" << endl << endl; + + indent(f_service_) << "public " << argsname << " getEmptyArgsInstance() {" << endl; + indent(f_service_) << " return new " << argsname << "();" << endl; + indent(f_service_) << "}" << endl << endl; + + indent(f_service_) << "public org.apache.thrift.async.AsyncMethodCallback<" << resulttype + << "> getResultHandler(final org.apache.thrift.server.AbstractNonblockingServer.AsyncFrameBuffer fb, final int seqid) {" << endl; + indent_up(); + indent(f_service_) << "final org.apache.thrift.AsyncProcessFunction fcall = this;" << endl; + indent(f_service_) << "return new org.apache.thrift.async.AsyncMethodCallback<" << resulttype + << ">() { " << endl; + indent_up(); + indent(f_service_) << "public void onComplete(" << resulttype << " o) {" << endl; + + indent_up(); + if (!tfunction->is_oneway()) { + indent(f_service_) << resultname << " result = new " << resultname << "();" << endl; + + if (!tfunction->get_returntype()->is_void()) { + indent(f_service_) << "result.success = o;" << endl; + // Set isset on success field + if (!type_can_be_null(tfunction->get_returntype())) { + indent(f_service_) << "result.set" << get_cap_name("success") << get_cap_name("isSet") + << "(true);" << endl; + } + } + + indent(f_service_) << "try {" << endl; + indent(f_service_) + << " fcall.sendResponse(fb, result, org.apache.thrift.protocol.TMessageType.REPLY,seqid);" + << endl; + indent(f_service_) << "} catch (org.apache.thrift.transport.TTransportException e) {" << endl; + indent_up(); + f_service_ << indent() + << "_LOGGER.error(\"TTransportException writing to internal frame buffer\", e);" + << endl + << indent() << "fb.close();" << endl; + indent_down(); + indent(f_service_) << "} catch (java.lang.Exception e) {" << endl; + indent_up(); + f_service_ << indent() << "_LOGGER.error(\"Exception writing to internal frame buffer\", e);" + << endl + << indent() << "onError(e);" << endl; + indent_down(); + indent(f_service_) << "}" << endl; + } + indent_down(); + indent(f_service_) << "}" << endl; + + indent(f_service_) << "public void onError(java.lang.Exception e) {" << endl; + indent_up(); + + if (tfunction->is_oneway()) { + indent(f_service_) << "if (e instanceof org.apache.thrift.transport.TTransportException) {" + << endl; + indent_up(); + + f_service_ << indent() << "_LOGGER.error(\"TTransportException inside handler\", e);" << endl + << indent() << "fb.close();" << endl; + + indent_down(); + indent(f_service_) << "} else {" << endl; + indent_up(); + + f_service_ << indent() << "_LOGGER.error(\"Exception inside oneway handler\", e);" << endl; + + indent_down(); + indent(f_service_) << "}" << endl; + } else { + indent(f_service_) << "byte msgType = org.apache.thrift.protocol.TMessageType.REPLY;" << endl; + indent(f_service_) << "org.apache.thrift.TSerializable msg;" << endl; + indent(f_service_) << resultname << " result = new " << resultname << "();" << endl; + + t_struct* xs = tfunction->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + + vector<t_field*>::const_iterator x_iter; + if (xceptions.size() > 0) { + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + if (x_iter == xceptions.begin()) + f_service_ << indent(); + string type = type_name((*x_iter)->get_type(), false, false); + string name = (*x_iter)->get_name(); + f_service_ << "if (e instanceof " << type << ") {" << endl; + indent_up(); + f_service_ << indent() << "result." << name << " = (" << type << ") e;" << endl + << indent() << "result.set" << get_cap_name(name) << get_cap_name("isSet") + << "(true);" << endl + << indent() << "msg = result;" << endl; + indent_down(); + indent(f_service_) << "} else "; + } + } else { + indent(f_service_); + } + f_service_ << "if (e instanceof org.apache.thrift.transport.TTransportException) {" << endl; + indent_up(); + f_service_ << indent() << "_LOGGER.error(\"TTransportException inside handler\", e);" << endl + << indent() << "fb.close();" << endl + << indent() << "return;" << endl; + indent_down(); + indent(f_service_) << "} else if (e instanceof org.apache.thrift.TApplicationException) {" + << endl; + indent_up(); + f_service_ << indent() << "_LOGGER.error(\"TApplicationException inside handler\", e);" << endl + << indent() << "msgType = org.apache.thrift.protocol.TMessageType.EXCEPTION;" << endl + << indent() << "msg = (org.apache.thrift.TApplicationException)e;" << endl; + indent_down(); + indent(f_service_) << "} else {" << endl; + indent_up(); + f_service_ << indent() << "_LOGGER.error(\"Exception inside handler\", e);" << endl + << indent() << "msgType = org.apache.thrift.protocol.TMessageType.EXCEPTION;" << endl + << indent() << "msg = new " + "org.apache.thrift.TApplicationException(org.apache.thrift." + "TApplicationException.INTERNAL_ERROR, e.getMessage());" + << endl; + indent_down(); + f_service_ << indent() << "}" << endl + << indent() << "try {" << endl + << indent() << " fcall.sendResponse(fb,msg,msgType,seqid);" << endl + << indent() << "} catch (java.lang.Exception ex) {" << endl + << indent() << " _LOGGER.error(\"Exception writing to internal frame buffer\", ex);" + << endl + << indent() << " fb.close();" << endl + << indent() << "}" << endl; + } + indent_down(); + indent(f_service_) << "}" << endl; + indent_down(); + indent(f_service_) << "};" << endl; + indent_down(); + indent(f_service_) << "}" << endl << endl; + + indent(f_service_) << "protected boolean isOneway() {" << endl; + indent(f_service_) << " return " << ((tfunction->is_oneway()) ? "true" : "false") << ";" << endl; + indent(f_service_) << "}" << endl << endl; + + indent(f_service_) << "public void start(I iface, " << argsname + << " args, org.apache.thrift.async.AsyncMethodCallback<" << resulttype + << "> resultHandler) throws org.apache.thrift.TException {" << endl; + indent_up(); + + // Generate the function call + t_struct* arg_struct = tfunction->get_arglist(); + const std::vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + f_service_ << indent(); + + f_service_ << "iface." << get_rpc_method_name(tfunction->get_name()) << "("; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << "args." << (*f_iter)->get_name(); + } + if (!first) + f_service_ << ","; + f_service_ << "resultHandler"; + f_service_ << ");" << endl; + + indent_down(); + indent(f_service_) << "}"; + + // Close function + f_service_ << endl; + + // Close class + indent_down(); + f_service_ << indent() << "}" << endl << endl; +} + +/** + * Generates a process function definition. + * + * @param tfunction The function to write a dispatcher for + */ +void t_java_generator::generate_process_function(t_service* tservice, t_function* tfunction) { + string argsname = tfunction->get_name() + "_args"; + string resultname = tfunction->get_name() + "_result"; + if (tfunction->is_oneway()) { + resultname = "org.apache.thrift.TBase"; + } + + (void)tservice; + // Open class + indent(f_service_) << "public static class " << tfunction->get_name() + << "<I extends Iface> extends org.apache.thrift.ProcessFunction<I, " + << argsname << "> {" << endl; + indent_up(); + + indent(f_service_) << "public " << tfunction->get_name() << "() {" << endl; + indent(f_service_) << " super(\"" << tfunction->get_name() << "\");" << endl; + indent(f_service_) << "}" << endl << endl; + + indent(f_service_) << "public " << argsname << " getEmptyArgsInstance() {" << endl; + indent(f_service_) << " return new " << argsname << "();" << endl; + indent(f_service_) << "}" << endl << endl; + + indent(f_service_) << "protected boolean isOneway() {" << endl; + indent(f_service_) << " return " << ((tfunction->is_oneway()) ? "true" : "false") << ";" << endl; + indent(f_service_) << "}" << endl << endl; + + indent(f_service_) << "@Override" << endl; + indent(f_service_) << "protected boolean rethrowUnhandledExceptions() {" << endl; + indent(f_service_) << " return " << ((rethrow_unhandled_exceptions_) ? "true" : "false") << ";" << endl; + indent(f_service_) << "}" << endl << endl; + + indent(f_service_) << "public " << resultname << " getResult(I iface, " << argsname + << " args) throws org.apache.thrift.TException {" << endl; + indent_up(); + if (!tfunction->is_oneway()) { + indent(f_service_) << resultname << " result = new " << resultname << "();" << endl; + } + + t_struct* xs = tfunction->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + + // Try block for a function with exceptions + if (xceptions.size() > 0) { + f_service_ << indent() << "try {" << endl; + indent_up(); + } + + // Generate the function call + t_struct* arg_struct = tfunction->get_arglist(); + const std::vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + f_service_ << indent(); + + if (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void()) { + f_service_ << "result.success = "; + } + f_service_ << "iface." << get_rpc_method_name(tfunction->get_name()) << "("; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << "args." << (*f_iter)->get_name(); + } + f_service_ << ");" << endl; + + // Set isset on success field + if (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void() + && !type_can_be_null(tfunction->get_returntype())) { + indent(f_service_) << "result.set" << get_cap_name("success") << get_cap_name("isSet") + << "(true);" << endl; + } + + if (!tfunction->is_oneway() && xceptions.size() > 0) { + indent_down(); + f_service_ << indent() << "}"; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + f_service_ << " catch (" << type_name((*x_iter)->get_type(), false, false) << " " + << (*x_iter)->get_name() << ") {" << endl; + if (!tfunction->is_oneway()) { + indent_up(); + f_service_ << indent() << "result." << (*x_iter)->get_name() << " = " + << (*x_iter)->get_name() << ";" << endl; + indent_down(); + f_service_ << indent() << "}"; + } else { + f_service_ << "}"; + } + } + f_service_ << endl; + } + + if (tfunction->is_oneway()) { + indent(f_service_) << "return null;" << endl; + } else { + indent(f_service_) << "return result;" << endl; + } + indent_down(); + indent(f_service_) << "}"; + + // Close function + f_service_ << endl; + + // Close class + indent_down(); + f_service_ << indent() << "}" << endl << endl; +} + +/** + * Deserializes a field of any type. + * + * @param tfield The field + * @param prefix The variable name or container for this field + */ +void t_java_generator::generate_deserialize_field(ostream& out, + t_field* tfield, + string prefix, + bool has_metadata) { + t_type* type = get_true_type(tfield->get_type()); + + if (type->is_void()) { + throw "CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name(); + } + + string name = prefix + tfield->get_name(); + + if (type->is_struct() || type->is_xception()) { + generate_deserialize_struct(out, (t_struct*)type, name); + } else if (type->is_container()) { + generate_deserialize_container(out, type, name, has_metadata); + } else if (type->is_base_type()) { + indent(out) << name << " = iprot."; + + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + out << "readBinary();"; + } else { + out << "readString();"; + } + break; + case t_base_type::TYPE_BOOL: + out << "readBool();"; + break; + case t_base_type::TYPE_I8: + out << "readByte();"; + break; + case t_base_type::TYPE_I16: + out << "readI16();"; + break; + case t_base_type::TYPE_I32: + out << "readI32();"; + break; + case t_base_type::TYPE_I64: + out << "readI64();"; + break; + case t_base_type::TYPE_DOUBLE: + out << "readDouble();"; + break; + default: + throw "compiler error: no Java name for base type " + t_base_type::t_base_name(tbase); + } + out << endl; + } else if (type->is_enum()) { + indent(out) << name << " = " + << type_name(tfield->get_type(), true, false, false, true) + + ".findByValue(iprot.readI32());" << endl; + } else { + printf("DO NOT KNOW HOW TO DESERIALIZE FIELD '%s' TYPE '%s'\n", + tfield->get_name().c_str(), + type_name(type).c_str()); + } +} + +/** + * Generates an unserializer for a struct, invokes read() + */ +void t_java_generator::generate_deserialize_struct(ostream& out, + t_struct* tstruct, + string prefix) { + + if (reuse_objects_) { + indent(out) << "if (" << prefix << " == null) {" << endl; + indent_up(); + } + indent(out) << prefix << " = new " << type_name(tstruct) << "();" << endl; + if (reuse_objects_) { + indent_down(); + indent(out) << "}" << endl; + } + indent(out) << prefix << ".read(iprot);" << endl; +} + +/** + * Deserializes a container by reading its size and then iterating + */ +void t_java_generator::generate_deserialize_container(ostream& out, + t_type* ttype, + string prefix, + bool has_metadata) { + + scope_up(out); + + string obj; + + if (ttype->is_map()) { + obj = tmp("_map"); + } else if (ttype->is_set()) { + obj = tmp("_set"); + } else if (ttype->is_list()) { + obj = tmp("_list"); + } + + if (has_metadata) { + // Declare variables, read header + if (ttype->is_map()) { + indent(out) << "org.apache.thrift.protocol.TMap " << obj << " = iprot.readMapBegin();" + << endl; + } else if (ttype->is_set()) { + indent(out) << "org.apache.thrift.protocol.TSet " << obj << " = iprot.readSetBegin();" + << endl; + } else if (ttype->is_list()) { + indent(out) << "org.apache.thrift.protocol.TList " << obj << " = iprot.readListBegin();" + << endl; + } + } else { + // Declare variables, read header + if (ttype->is_map()) { + indent(out) << "org.apache.thrift.protocol.TMap " << obj + << " = new org.apache.thrift.protocol.TMap(" + << type_to_enum(((t_map*)ttype)->get_key_type()) << ", " + << type_to_enum(((t_map*)ttype)->get_val_type()) << ", " + << "iprot.readI32());" << endl; + } else if (ttype->is_set()) { + indent(out) << "org.apache.thrift.protocol.TSet " << obj + << " = new org.apache.thrift.protocol.TSet(" + << type_to_enum(((t_set*)ttype)->get_elem_type()) << ", iprot.readI32());" + << endl; + } else if (ttype->is_list()) { + indent(out) << "org.apache.thrift.protocol.TList " << obj + << " = new org.apache.thrift.protocol.TList(" + << type_to_enum(((t_list*)ttype)->get_elem_type()) << ", iprot.readI32());" + << endl; + } + } + + if (reuse_objects_) { + indent(out) << "if (" << prefix << " == null) {" << endl; + indent_up(); + } + + if (is_enum_set(ttype)) { + out << indent() << prefix << " = " << type_name(ttype, false, true, true) << ".noneOf"; + } else { + out << indent() << prefix << " = new " << type_name(ttype, false, true); + } + + // construct the collection correctly i.e. with appropriate size/type + if (is_enum_set(ttype) || is_enum_map(ttype)) { + out << "(" << inner_enum_type_name(ttype) << ");" << endl; + } else if (sorted_containers_ && (ttype->is_map() || ttype->is_set())) { + // TreeSet and TreeMap don't have any constructor which takes a capacity as an argument + out << "();" << endl; + } else { + out << "(" << (ttype->is_list() ? "" : "2*") << obj << ".size" + << ");" << endl; + } + + if (reuse_objects_) { + indent_down(); + indent(out) << "}" << endl; + } + + if (ttype->is_map()) { + generate_deserialize_map_element(out, (t_map*)ttype, prefix, obj, has_metadata); + } else if (ttype->is_set()) { + generate_deserialize_set_element(out, (t_set*)ttype, prefix, obj, has_metadata); + } else if (ttype->is_list()) { + generate_deserialize_list_element(out, (t_list*)ttype, prefix, obj, has_metadata); + } + + scope_down(out); + + if (has_metadata) { + // Read container end + if (ttype->is_map()) { + indent(out) << "iprot.readMapEnd();" << endl; + } else if (ttype->is_set()) { + indent(out) << "iprot.readSetEnd();" << endl; + } else if (ttype->is_list()) { + indent(out) << "iprot.readListEnd();" << endl; + } + } + scope_down(out); +} + +/** + * Generates code to deserialize a map + */ +void t_java_generator::generate_deserialize_map_element(ostream& out, + t_map* tmap, + string prefix, + string obj, + bool has_metadata) { + string key = tmp("_key"); + string val = tmp("_val"); + t_field fkey(tmap->get_key_type(), key); + t_field fval(tmap->get_val_type(), val); + + indent(out) << declare_field(&fkey, reuse_objects_, false) << endl; + indent(out) << declare_field(&fval, reuse_objects_, false) << endl; + + // For loop iterates over elements + string i = tmp("_i"); + indent(out) << "for (int " << i << " = 0; " << i << " < " << obj << ".size" + << "; " + << "++" << i << ")" << endl; + + scope_up(out); + + generate_deserialize_field(out, &fkey, "", has_metadata); + generate_deserialize_field(out, &fval, "", has_metadata); + + if (get_true_type(fkey.get_type())->is_enum()) { + indent(out) << "if (" << key << " != null)" << endl; + scope_up(out); + } + + indent(out) << prefix << ".put(" << key << ", " << val << ");" << endl; + + if (get_true_type(fkey.get_type())->is_enum()) { + scope_down(out); + } + + if (reuse_objects_ && !get_true_type(fkey.get_type())->is_base_type()) { + indent(out) << key << " = null;" << endl; + } + + if (reuse_objects_ && !get_true_type(fval.get_type())->is_base_type()) { + indent(out) << val << " = null;" << endl; + } +} + +/** + * Deserializes a set element + */ +void t_java_generator::generate_deserialize_set_element(ostream& out, + t_set* tset, + string prefix, + string obj, + bool has_metadata) { + string elem = tmp("_elem"); + t_field felem(tset->get_elem_type(), elem); + + indent(out) << declare_field(&felem, reuse_objects_, false) << endl; + + // For loop iterates over elements + string i = tmp("_i"); + indent(out) << "for (int " << i << " = 0; " << i << " < " << obj << ".size" + << "; " + << "++" << i << ")" << endl; + scope_up(out); + + generate_deserialize_field(out, &felem, "", has_metadata); + + if (get_true_type(felem.get_type())->is_enum()) { + indent(out) << "if (" << elem << " != null)" << endl; + scope_up(out); + } + + indent(out) << prefix << ".add(" << elem << ");" << endl; + + if (get_true_type(felem.get_type())->is_enum()) { + scope_down(out); + } + + if (reuse_objects_ && !get_true_type(felem.get_type())->is_base_type()) { + indent(out) << elem << " = null;" << endl; + } +} + +/** + * Deserializes a list element + */ +void t_java_generator::generate_deserialize_list_element(ostream& out, + t_list* tlist, + string prefix, + string obj, + bool has_metadata) { + string elem = tmp("_elem"); + t_field felem(tlist->get_elem_type(), elem); + + indent(out) << declare_field(&felem, reuse_objects_, false) << endl; + + // For loop iterates over elements + string i = tmp("_i"); + indent(out) << "for (int " << i << " = 0; " << i << " < " << obj << ".size" + << "; " + << "++" << i << ")" << endl; + scope_up(out); + + generate_deserialize_field(out, &felem, "", has_metadata); + + if (get_true_type(felem.get_type())->is_enum()) { + indent(out) << "if (" << elem << " != null)" << endl; + scope_up(out); + } + + indent(out) << prefix << ".add(" << elem << ");" << endl; + + if (get_true_type(felem.get_type())->is_enum()) { + scope_down(out); + } + + if (reuse_objects_ && !get_true_type(felem.get_type())->is_base_type()) { + indent(out) << elem << " = null;" << endl; + } +} + +/** + * Serializes a field of any type. + * + * @param tfield The field to serialize + * @param prefix Name to prepend to field name + */ +void t_java_generator::generate_serialize_field(ostream& out, + t_field* tfield, + string prefix, + bool has_metadata) { + t_type* type = get_true_type(tfield->get_type()); + + // Do nothing for void types + if (type->is_void()) { + throw "CANNOT GENERATE SERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name(); + } + + if (type->is_struct() || type->is_xception()) { + generate_serialize_struct(out, (t_struct*)type, prefix + tfield->get_name()); + } else if (type->is_container()) { + generate_serialize_container(out, type, prefix + tfield->get_name(), has_metadata); + } else if (type->is_enum()) { + indent(out) << "oprot.writeI32(" << prefix + tfield->get_name() << ".getValue());" << endl; + } else if (type->is_base_type()) { + string name = prefix + tfield->get_name(); + indent(out) << "oprot."; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + out << "writeBinary(" << name << ");"; + } else { + out << "writeString(" << name << ");"; + } + break; + case t_base_type::TYPE_BOOL: + out << "writeBool(" << name << ");"; + break; + case t_base_type::TYPE_I8: + out << "writeByte(" << name << ");"; + break; + case t_base_type::TYPE_I16: + out << "writeI16(" << name << ");"; + break; + case t_base_type::TYPE_I32: + out << "writeI32(" << name << ");"; + break; + case t_base_type::TYPE_I64: + out << "writeI64(" << name << ");"; + break; + case t_base_type::TYPE_DOUBLE: + out << "writeDouble(" << name << ");"; + break; + default: + throw "compiler error: no Java name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "writeI32(struct." << name << ");"; + } + out << endl; + } else { + printf("DO NOT KNOW HOW TO SERIALIZE FIELD '%s%s' TYPE '%s'\n", + prefix.c_str(), + tfield->get_name().c_str(), + type_name(type).c_str()); + } +} + +/** + * Serializes all the members of a struct. + * + * @param tstruct The struct to serialize + * @param prefix String prefix to attach to all fields + */ +void t_java_generator::generate_serialize_struct(ostream& out, t_struct* tstruct, string prefix) { + (void)tstruct; + out << indent() << prefix << ".write(oprot);" << endl; +} + +/** + * Serializes a container by writing its size then the elements. + * + * @param ttype The type of container + * @param prefix String prefix for fields + */ +void t_java_generator::generate_serialize_container(ostream& out, + t_type* ttype, + string prefix, + bool has_metadata) { + scope_up(out); + + if (has_metadata) { + if (ttype->is_map()) { + indent(out) << "oprot.writeMapBegin(new org.apache.thrift.protocol.TMap(" + << type_to_enum(((t_map*)ttype)->get_key_type()) << ", " + << type_to_enum(((t_map*)ttype)->get_val_type()) << ", " << prefix << ".size()));" + << endl; + } else if (ttype->is_set()) { + indent(out) << "oprot.writeSetBegin(new org.apache.thrift.protocol.TSet(" + << type_to_enum(((t_set*)ttype)->get_elem_type()) << ", " << prefix + << ".size()));" << endl; + } else if (ttype->is_list()) { + indent(out) << "oprot.writeListBegin(new org.apache.thrift.protocol.TList(" + << type_to_enum(((t_list*)ttype)->get_elem_type()) << ", " << prefix + << ".size()));" << endl; + } + } else { + indent(out) << "oprot.writeI32(" << prefix << ".size());" << endl; + } + + string iter = tmp("_iter"); + if (ttype->is_map()) { + indent(out) << "for (java.util.Map.Entry<" << type_name(((t_map*)ttype)->get_key_type(), true, false) + << ", " << type_name(((t_map*)ttype)->get_val_type(), true, false) << "> " << iter + << " : " << prefix << ".entrySet())"; + } else if (ttype->is_set()) { + indent(out) << "for (" << type_name(((t_set*)ttype)->get_elem_type()) << " " << iter << " : " + << prefix << ")"; + } else if (ttype->is_list()) { + indent(out) << "for (" << type_name(((t_list*)ttype)->get_elem_type()) << " " << iter << " : " + << prefix << ")"; + } + + out << endl; + scope_up(out); + if (ttype->is_map()) { + generate_serialize_map_element(out, (t_map*)ttype, iter, prefix, has_metadata); + } else if (ttype->is_set()) { + generate_serialize_set_element(out, (t_set*)ttype, iter, has_metadata); + } else if (ttype->is_list()) { + generate_serialize_list_element(out, (t_list*)ttype, iter, has_metadata); + } + scope_down(out); + + if (has_metadata) { + if (ttype->is_map()) { + indent(out) << "oprot.writeMapEnd();" << endl; + } else if (ttype->is_set()) { + indent(out) << "oprot.writeSetEnd();" << endl; + } else if (ttype->is_list()) { + indent(out) << "oprot.writeListEnd();" << endl; + } + } + + scope_down(out); +} + +/** + * Serializes the members of a map. + */ +void t_java_generator::generate_serialize_map_element(ostream& out, + t_map* tmap, + string iter, + string map, + bool has_metadata) { + (void)map; + t_field kfield(tmap->get_key_type(), iter + ".getKey()"); + generate_serialize_field(out, &kfield, "", has_metadata); + t_field vfield(tmap->get_val_type(), iter + ".getValue()"); + generate_serialize_field(out, &vfield, "", has_metadata); +} + +/** + * Serializes the members of a set. + */ +void t_java_generator::generate_serialize_set_element(ostream& out, + t_set* tset, + string iter, + bool has_metadata) { + t_field efield(tset->get_elem_type(), iter); + generate_serialize_field(out, &efield, "", has_metadata); +} + +/** + * Serializes the members of a list. + */ +void t_java_generator::generate_serialize_list_element(ostream& out, + t_list* tlist, + string iter, + bool has_metadata) { + t_field efield(tlist->get_elem_type(), iter); + generate_serialize_field(out, &efield, "", has_metadata); +} + +/** + * Returns a Java type name + * + * @param ttype The type + * @param container Is the type going inside a container? + * @return Java type name, i.e. java.util.HashMap<Key,Value> + */ +string t_java_generator::type_name(t_type* ttype, + bool in_container, + bool in_init, + bool skip_generic, + bool force_namespace) { + // In Java typedefs are just resolved to their real type + ttype = get_true_type(ttype); + string prefix; + + if (ttype->is_base_type()) { + return base_type_name((t_base_type*)ttype, in_container); + } else if (ttype->is_map()) { + t_map* tmap = (t_map*)ttype; + if (in_init) { + if (is_enum_map(tmap)) { + prefix = "java.util.EnumMap"; + } else if (sorted_containers_) { + prefix = "java.util.TreeMap"; + } else { + prefix = "java.util.HashMap"; + } + } else { + prefix = "java.util.Map"; + } + return prefix + (skip_generic ? "" : "<" + type_name(tmap->get_key_type(), true) + "," + + type_name(tmap->get_val_type(), true) + ">"); + } else if (ttype->is_set()) { + t_set* tset = (t_set*)ttype; + if (in_init) { + if (is_enum_set(tset)) { + prefix = "java.util.EnumSet"; + } else if (sorted_containers_) { + prefix = "java.util.TreeSet"; + } else { + prefix = "java.util.HashSet"; + } + } else { + prefix = "java.util.Set"; + } + return prefix + (skip_generic ? "" : "<" + type_name(tset->get_elem_type(), true) + ">"); + } else if (ttype->is_list()) { + t_list* tlist = (t_list*)ttype; + if (in_init) { + prefix = "java.util.ArrayList"; + } else { + prefix = "java.util.List"; + } + return prefix + (skip_generic ? "" : "<" + type_name(tlist->get_elem_type(), true) + ">"); + } + + // Check for namespacing + t_program* program = ttype->get_program(); + if ((program != NULL) && ((program != program_) || force_namespace)) { + string package = program->get_namespace("java"); + if (!package.empty()) { + return package + "." + ttype->get_name(); + } + } + + return ttype->get_name(); +} + +/** + * Returns the Java type that corresponds to the thrift type. + * + * @param tbase The base type + * @param container Is it going in a Java container? + */ +string t_java_generator::base_type_name(t_base_type* type, bool in_container) { + t_base_type::t_base tbase = type->get_base(); + + switch (tbase) { + case t_base_type::TYPE_VOID: + return (in_container ? "Void" : "void"); + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + return "java.nio.ByteBuffer"; + } else { + return "java.lang.String"; + } + case t_base_type::TYPE_BOOL: + return (in_container ? "java.lang.Boolean" : "boolean"); + case t_base_type::TYPE_I8: + return (in_container ? "java.lang.Byte" : "byte"); + case t_base_type::TYPE_I16: + return (in_container ? "java.lang.Short" : "short"); + case t_base_type::TYPE_I32: + return (in_container ? "java.lang.Integer" : "int"); + case t_base_type::TYPE_I64: + return (in_container ? "java.lang.Long" : "long"); + case t_base_type::TYPE_DOUBLE: + return (in_container ? "java.lang.Double" : "double"); + default: + throw "compiler error: no Java name for base type " + t_base_type::t_base_name(tbase); + } +} + +/** + * Declares a field, which may include initialization as necessary. + * + * @param tfield The field + * @param init Whether to initialize the field + */ +string t_java_generator::declare_field(t_field* tfield, bool init, bool comment) { + // TODO(mcslee): do we ever need to initialize the field? + string result = ""; + t_type* ttype = get_true_type(tfield->get_type()); + if (type_can_be_null(ttype)) { + result += java_nullable_annotation() + " "; + } + result += type_name(tfield->get_type()) + " " + tfield->get_name(); + if (init) { + if (ttype->is_base_type() && tfield->get_value() != NULL) { + std::ofstream dummy; + result += " = " + render_const_value(dummy, ttype, tfield->get_value()); + } else if (ttype->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)ttype)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + result += " = null"; + break; + case t_base_type::TYPE_BOOL: + result += " = false"; + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + result += " = 0"; + break; + case t_base_type::TYPE_DOUBLE: + result += " = (double)0"; + break; + } + } else if (ttype->is_enum()) { + result += " = null"; + } else if (ttype->is_container()) { + result += " = new " + type_name(ttype, false, true) + "()"; + } else { + result += " = new " + type_name(ttype, false, true) + "()"; + ; + } + } + result += ";"; + if (comment) { + result += " // "; + if (tfield->get_req() == t_field::T_OPTIONAL) { + result += "optional"; + } else { + result += "required"; + } + } + return result; +} + +/** + * Renders a function signature of the form 'type name(args)' + * + * @param tfunction Function definition + * @return String of rendered function definition + */ +string t_java_generator::function_signature(t_function* tfunction, string prefix) { + t_type* ttype = tfunction->get_returntype(); + std::string fn_name = get_rpc_method_name(tfunction->get_name()); + std::string result = type_name(ttype) + " " + prefix + fn_name + "(" + + argument_list(tfunction->get_arglist()) + ") throws "; + t_struct* xs = tfunction->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + result += type_name((*x_iter)->get_type(), false, false) + ", "; + } + result += "org.apache.thrift.TException"; + return result; +} + +/** + * Renders a function signature of the form 'void name(args, resultHandler)' + * + * @params tfunction Function definition + * @return String of rendered function definition + */ +string t_java_generator::function_signature_async(t_function* tfunction, + bool use_base_method, + string prefix) { + std::string arglist = async_function_call_arglist(tfunction, use_base_method, true); + + std::string ret_type = ""; + if (use_base_method) { + ret_type += "AsyncClient."; + } + ret_type += tfunction->get_name() + "_call"; + + std::string fn_name = get_rpc_method_name(tfunction->get_name()); + + std::string result = prefix + "void " + fn_name + "(" + arglist + ")"; + return result; +} + +string t_java_generator::async_function_call_arglist(t_function* tfunc, + bool use_base_method, + bool include_types) { + (void)use_base_method; + std::string arglist = ""; + if (tfunc->get_arglist()->get_members().size() > 0) { + arglist = argument_list(tfunc->get_arglist(), include_types) + ", "; + } + + if (include_types) { + arglist += "org.apache.thrift.async.AsyncMethodCallback<"; + arglist += type_name(tfunc->get_returntype(), true) + "> "; + } + arglist += "resultHandler"; + + return arglist; +} + +/** + * Renders a comma separated field list, with type names + */ +string t_java_generator::argument_list(t_struct* tstruct, bool include_types) { + string result = ""; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + result += ", "; + } + if (include_types) { + result += type_name((*f_iter)->get_type()) + " "; + } + result += (*f_iter)->get_name(); + } + return result; +} + +string t_java_generator::async_argument_list(t_function* tfunct, + t_struct* tstruct, + t_type* ttype, + bool include_types) { + (void)tfunct; + (void)ttype; + string result = ""; + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + result += ", "; + } + if (include_types) { + result += type_name((*f_iter)->get_type()) + " "; + } + result += (*f_iter)->get_name(); + } + if (!first) { + result += ", "; + } + if (include_types) { + result += "org.apache.thrift.async.AsyncMethodCallback<"; + result += type_name(tfunct->get_returntype(), true) + "> "; + } + result += "resultHandler"; + return result; +} + +/** + * Converts the parse type to a Java enum string for the given type. + */ +string t_java_generator::type_to_enum(t_type* type) { + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + return "org.apache.thrift.protocol.TType.STRING"; + case t_base_type::TYPE_BOOL: + return "org.apache.thrift.protocol.TType.BOOL"; + case t_base_type::TYPE_I8: + return "org.apache.thrift.protocol.TType.BYTE"; + case t_base_type::TYPE_I16: + return "org.apache.thrift.protocol.TType.I16"; + case t_base_type::TYPE_I32: + return "org.apache.thrift.protocol.TType.I32"; + case t_base_type::TYPE_I64: + return "org.apache.thrift.protocol.TType.I64"; + case t_base_type::TYPE_DOUBLE: + return "org.apache.thrift.protocol.TType.DOUBLE"; + } + } else if (type->is_enum()) { + return "org.apache.thrift.protocol.TType.I32"; + } else if (type->is_struct() || type->is_xception()) { + return "org.apache.thrift.protocol.TType.STRUCT"; + } else if (type->is_map()) { + return "org.apache.thrift.protocol.TType.MAP"; + } else if (type->is_set()) { + return "org.apache.thrift.protocol.TType.SET"; + } else if (type->is_list()) { + return "org.apache.thrift.protocol.TType.LIST"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +/** + * Takes a name and produes a valid Java source file name from it + * + * @param fromName The name which shall become a valid Java source file name + * @return The produced identifier + */ +std::string t_java_generator::make_valid_java_filename(std::string const& fromName) { + // if any further rules apply to source file names in Java, modify as necessary + return make_valid_java_identifier(fromName); +} + +/** + * Takes a name and produes a valid Java identifier from it + * + * @param fromName The name which shall become a valid Java identifier + * @return The produced identifier + */ +std::string t_java_generator::make_valid_java_identifier(std::string const& fromName) { + std::string str = fromName; + if (str.empty()) { + return str; + } + + // tests rely on this + assert(('A' < 'Z') && ('a' < 'z') && ('0' < '9')); + + // if the first letter is a number, we add an additional underscore in front of it + char c = str.at(0); + if (('0' <= c) && (c <= '9')) { + str = "_" + str; + } + + // following chars: letter, number or underscore + for (size_t i = 0; i < str.size(); ++i) { + c = str.at(i); + if ((('A' > c) || (c > 'Z')) && (('a' > c) || (c > 'z')) && (('0' > c) || (c > '9')) + && ('_' != c)) { + str.replace(i, 1, "_"); + } + } + + return str; +} + +std::string t_java_generator::as_camel_case(std::string name, bool ucfirst) { + std::string new_name; + size_t i = 0; + for (i = 0; i < name.size(); i++) { + if (name[i] != '_') + break; + } + if (ucfirst) { + new_name += toupper(name[i++]); + } else { + new_name += tolower(name[i++]); + } + for (; i < name.size(); i++) { + if (name[i] == '_') { + if (i < name.size() - 1) { + i++; + new_name += toupper(name[i]); + } + } else { + new_name += name[i]; + } + } + return new_name; +} + +std::string t_java_generator::get_rpc_method_name(std::string name) { + if (fullcamel_style_) { + return as_camel_case(name, false); + } else { + return name; + } +} + +/** + * Applies the correct style to a string based on the value of nocamel_style_ + * and/or fullcamel_style_ + */ +std::string t_java_generator::get_cap_name(std::string name) { + if (nocamel_style_) { + return "_" + name; + } else if (fullcamel_style_) { + return as_camel_case(name); + } else { + name[0] = toupper(name[0]); + return name; + } +} + +string t_java_generator::constant_name(string name) { + string constant_name; + + bool is_first = true; + bool was_previous_char_upper = false; + for (char character : name) { + bool is_upper = isupper(character); + + if (is_upper && !is_first && !was_previous_char_upper) { + constant_name += '_'; + } + constant_name += toupper(character); + + is_first = false; + was_previous_char_upper = is_upper; + } + + return constant_name; +} + +void t_java_generator::generate_deep_copy_container(ostream& out, + std::string source_name_p1, + std::string source_name_p2, + std::string result_name, + t_type* type) { + + t_container* container = (t_container*)type; + std::string source_name; + if (source_name_p2 == "") + source_name = source_name_p1; + else + source_name = source_name_p1 + "." + source_name_p2; + + bool copy_construct_container; + if (container->is_map()) { + t_map* tmap = (t_map*)container; + copy_construct_container = tmap->get_key_type()->is_base_type() + && tmap->get_val_type()->is_base_type(); + } else { + t_type* elem_type = container->is_list() ? ((t_list*)container)->get_elem_type() + : ((t_set*)container)->get_elem_type(); + copy_construct_container = elem_type->is_base_type(); + } + + if (copy_construct_container) { + // deep copy of base types can be done much more efficiently than iterating over all the + // elements manually + indent(out) << type_name(type, true, false) << " " << result_name << " = new " + << type_name(container, false, true) << "(" << source_name << ");" << endl; + return; + } + + std::string constructor_args; + if (is_enum_set(container) || is_enum_map(container)) { + constructor_args = inner_enum_type_name(container); + } else if (!(sorted_containers_ && (container->is_map() || container->is_set()))) { + // unsorted containers accept a capacity value + constructor_args = source_name + ".size()"; + } + + if (is_enum_set(container)) { + indent(out) << type_name(type, true, false) << " " << result_name << " = " + << type_name(container, false, true, true) << ".noneOf(" << constructor_args << ");" << endl; + } else { + indent(out) << type_name(type, true, false) << " " << result_name << " = new " + << type_name(container, false, true) << "(" << constructor_args << ");" << endl; + } + + std::string iterator_element_name = source_name_p1 + "_element"; + std::string result_element_name = result_name + "_copy"; + + if (container->is_map()) { + t_type* key_type = ((t_map*)container)->get_key_type(); + t_type* val_type = ((t_map*)container)->get_val_type(); + + indent(out) << "for (java.util.Map.Entry<" << type_name(key_type, true, false) << ", " + << type_name(val_type, true, false) << "> " << iterator_element_name << " : " + << source_name << ".entrySet()) {" << endl; + indent_up(); + + out << endl; + + indent(out) << type_name(key_type, true, false) << " " << iterator_element_name + << "_key = " << iterator_element_name << ".getKey();" << endl; + indent(out) << type_name(val_type, true, false) << " " << iterator_element_name + << "_value = " << iterator_element_name << ".getValue();" << endl; + + out << endl; + + if (key_type->is_container()) { + generate_deep_copy_container(out, + iterator_element_name + "_key", + "", + result_element_name + "_key", + key_type); + } else { + indent(out) << type_name(key_type, true, false) << " " << result_element_name << "_key = "; + generate_deep_copy_non_container(out, + iterator_element_name + "_key", + result_element_name + "_key", + key_type); + out << ";" << endl; + } + + out << endl; + + if (val_type->is_container()) { + generate_deep_copy_container(out, + iterator_element_name + "_value", + "", + result_element_name + "_value", + val_type); + } else { + indent(out) << type_name(val_type, true, false) << " " << result_element_name << "_value = "; + generate_deep_copy_non_container(out, + iterator_element_name + "_value", + result_element_name + "_value", + val_type); + out << ";" << endl; + } + + out << endl; + + indent(out) << result_name << ".put(" << result_element_name << "_key, " << result_element_name + << "_value);" << endl; + + indent_down(); + indent(out) << "}" << endl; + + } else { + t_type* elem_type; + + if (container->is_set()) { + elem_type = ((t_set*)container)->get_elem_type(); + } else { + elem_type = ((t_list*)container)->get_elem_type(); + } + + indent(out) << "for (" << type_name(elem_type, true, false) << " " << iterator_element_name + << " : " << source_name << ") {" << endl; + + indent_up(); + + if (elem_type->is_container()) { + // recursive deep copy + generate_deep_copy_container(out, iterator_element_name, "", result_element_name, elem_type); + indent(out) << result_name << ".add(" << result_element_name << ");" << endl; + } else { + // iterative copy + if (elem_type->is_binary()) { + indent(out) << "java.nio.ByteBuffer temp_binary_element = "; + generate_deep_copy_non_container(out, + iterator_element_name, + "temp_binary_element", + elem_type); + out << ";" << endl; + indent(out) << result_name << ".add(temp_binary_element);" << endl; + } else { + indent(out) << result_name << ".add("; + generate_deep_copy_non_container(out, iterator_element_name, result_name, elem_type); + out << ");" << endl; + } + } + + indent_down(); + + indent(out) << "}" << endl; + } +} + +void t_java_generator::generate_deep_copy_non_container(ostream& out, + std::string source_name, + std::string dest_name, + t_type* type) { + (void)dest_name; + type = get_true_type(type); + if (type->is_base_type() || type->is_enum() || type->is_typedef()) { + if (type->is_binary()) { + out << "org.apache.thrift.TBaseHelper.copyBinary(" << source_name << ")"; + } else { + // everything else can be copied directly + out << source_name; + } + } else { + out << "new " << type_name(type, true, true) << "(" << source_name << ")"; + } +} + +std::string t_java_generator::generate_isset_check(t_field* field) { + return generate_isset_check(field->get_name()); +} + +std::string t_java_generator::isset_field_id(t_field* field) { + return "__" + upcase_string(field->get_name() + "_isset_id"); +} + +std::string t_java_generator::generate_isset_check(std::string field_name) { + return "is" + get_cap_name("set") + get_cap_name(field_name) + "()"; +} + +void t_java_generator::generate_isset_set(ostream& out, t_field* field, string prefix) { + if (!type_can_be_null(field->get_type())) { + indent(out) << prefix << "set" << get_cap_name(field->get_name()) << get_cap_name("isSet") + << "(true);" << endl; + } +} + +void t_java_generator::generate_struct_desc(ostream& out, t_struct* tstruct) { + indent(out) << "private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new " + "org.apache.thrift.protocol.TStruct(\"" << tstruct->get_name() << "\");" << endl; +} + +void t_java_generator::generate_field_descs(ostream& out, t_struct* tstruct) { + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + indent(out) << "private static final org.apache.thrift.protocol.TField " + << constant_name((*m_iter)->get_name()) + << "_FIELD_DESC = new org.apache.thrift.protocol.TField(\"" << (*m_iter)->get_name() + << "\", " << type_to_enum((*m_iter)->get_type()) << ", " + << "(short)" << (*m_iter)->get_key() << ");" << endl; + } +} + +void t_java_generator::generate_scheme_map(ostream& out, t_struct* tstruct) { + indent(out) << "private static final org.apache.thrift.scheme.SchemeFactory STANDARD_SCHEME_FACTORY = new " + << tstruct->get_name() << "StandardSchemeFactory();" << endl; + indent(out) << "private static final org.apache.thrift.scheme.SchemeFactory TUPLE_SCHEME_FACTORY = new " + << tstruct->get_name() << "TupleSchemeFactory();" << endl; +} + +void t_java_generator::generate_field_name_constants(ostream& out, t_struct* tstruct) { + indent(out) << "/** The set of fields this struct contains, along with convenience methods for " + "finding and manipulating them. */" << endl; + indent(out) << "public enum _Fields implements org.apache.thrift.TFieldIdEnum {" << endl; + + indent_up(); + bool first = true; + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if (!first) { + out << "," << endl; + } + first = false; + generate_java_doc(out, *m_iter); + indent(out) << constant_name((*m_iter)->get_name()) << "((short)" << (*m_iter)->get_key() + << ", \"" << (*m_iter)->get_name() << "\")"; + } + + out << ";" << endl << endl; + + indent(out) + << "private static final java.util.Map<java.lang.String, _Fields> byName = new java.util.HashMap<java.lang.String, _Fields>();" + << endl; + out << endl; + + indent(out) << "static {" << endl; + indent(out) << " for (_Fields field : java.util.EnumSet.allOf(_Fields.class)) {" << endl; + indent(out) << " byName.put(field.getFieldName(), field);" << endl; + indent(out) << " }" << endl; + indent(out) << "}" << endl << endl; + + indent(out) << "/**" << endl; + indent(out) << " * Find the _Fields constant that matches fieldId, or null if its not found." + << endl; + indent(out) << " */" << endl; + indent(out) << java_nullable_annotation() << endl; + indent(out) << "public static _Fields findByThriftId(int fieldId) {" << endl; + indent_up(); + indent(out) << "switch(fieldId) {" << endl; + indent_up(); + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + indent(out) << "case " << (*m_iter)->get_key() << ": // " + << constant_name((*m_iter)->get_name()) << endl; + indent(out) << " return " << constant_name((*m_iter)->get_name()) << ";" << endl; + } + + indent(out) << "default:" << endl; + indent(out) << " return null;" << endl; + + indent_down(); + indent(out) << "}" << endl; + + indent_down(); + indent(out) << "}" << endl << endl; + + indent(out) << "/**" << endl; + indent(out) << " * Find the _Fields constant that matches fieldId, throwing an exception" << endl; + indent(out) << " * if it is not found." << endl; + indent(out) << " */" << endl; + indent(out) << "public static _Fields findByThriftIdOrThrow(int fieldId) {" << endl; + indent(out) << " _Fields fields = findByThriftId(fieldId);" << endl; + indent(out) << " if (fields == null) throw new java.lang.IllegalArgumentException(\"Field \" + fieldId + " + "\" doesn't exist!\");" << endl; + indent(out) << " return fields;" << endl; + indent(out) << "}" << endl << endl; + + indent(out) << "/**" << endl; + indent(out) << " * Find the _Fields constant that matches name, or null if its not found." + << endl; + indent(out) << " */" << endl; + indent(out) << java_nullable_annotation() << endl; + indent(out) << "public static _Fields findByName(java.lang.String name) {" << endl; + indent(out) << " return byName.get(name);" << endl; + indent(out) << "}" << endl << endl; + + indent(out) << "private final short _thriftId;" << endl; + indent(out) << "private final java.lang.String _fieldName;" << endl << endl; + + indent(out) << "_Fields(short thriftId, java.lang.String fieldName) {" << endl; + indent(out) << " _thriftId = thriftId;" << endl; + indent(out) << " _fieldName = fieldName;" << endl; + indent(out) << "}" << endl << endl; + + indent(out) << "public short getThriftFieldId() {" << endl; + indent(out) << " return _thriftId;" << endl; + indent(out) << "}" << endl << endl; + + indent(out) << "public java.lang.String getFieldName() {" << endl; + indent(out) << " return _fieldName;" << endl; + indent(out) << "}" << endl; + + indent_down(); + + indent(out) << "}" << endl; +} + +t_java_generator::isset_type t_java_generator::needs_isset(t_struct* tstruct, + std::string* outPrimitiveType) { + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + int count = 0; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if (!type_can_be_null(get_true_type((*m_iter)->get_type()))) { + count++; + } + } + if (count == 0) { + return ISSET_NONE; + } else if (count <= 64) { + if (outPrimitiveType != NULL) { + if (count <= 8) + *outPrimitiveType = "byte"; + else if (count <= 16) + *outPrimitiveType = "short"; + else if (count <= 32) + *outPrimitiveType = "int"; + else if (count <= 64) + *outPrimitiveType = "long"; + } + return ISSET_PRIMITIVE; + } else { + return ISSET_BITSET; + } +} + +void t_java_generator::generate_java_struct_clear(std::ostream& out, t_struct* tstruct) { + if (!java5_) { + indent(out) << "@Override" << endl; + } + indent(out) << "public void clear() {" << endl; + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + indent_up(); + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_field* field = *m_iter; + t_type* t = get_true_type(field->get_type()); + + if (field->get_value() != NULL) { + print_const_value(out, "this." + field->get_name(), t, field->get_value(), true, true); + continue; + } + + if (type_can_be_null(t)) { + + if (reuse_objects_ && (t->is_container() || t->is_struct())) { + indent(out) << "if (this." << field->get_name() << " != null) {" << endl; + indent_up(); + indent(out) << "this." << field->get_name() << ".clear();" << endl; + indent_down(); + indent(out) << "}" << endl; + + } else { + + indent(out) << "this." << field->get_name() << " = null;" << endl; + } + continue; + } + + // must be a base type + // means it also needs to be explicitly unset + indent(out) << "set" << get_cap_name(field->get_name()) << get_cap_name("isSet") << "(false);" + << endl; + t_base_type* base_type = (t_base_type*)t; + + switch (base_type->get_base()) { + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + indent(out) << "this." << field->get_name() << " = 0;" << endl; + break; + case t_base_type::TYPE_DOUBLE: + indent(out) << "this." << field->get_name() << " = 0.0;" << endl; + break; + case t_base_type::TYPE_BOOL: + indent(out) << "this." << field->get_name() << " = false;" << endl; + break; + default: + throw "unsupported type: " + base_type->get_name() + " for field " + field->get_name(); + } + } + indent_down(); + + indent(out) << "}" << endl << endl; +} + +// generates java method to serialize (in the Java sense) the object +void t_java_generator::generate_java_struct_write_object(ostream& out, t_struct* tstruct) { + (void)tstruct; + indent(out) + << "private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {" + << endl; + indent(out) << " try {" << endl; + indent(out) << " write(new org.apache.thrift.protocol.TCompactProtocol(new " + "org.apache.thrift.transport.TIOStreamTransport(out)));" << endl; + indent(out) << " } catch (org.apache.thrift.TException te) {" << endl; + indent(out) << " throw new java.io.IOException(te" << (android_legacy_ ? ".getMessage()" : "") + << ");" << endl; + indent(out) << " }" << endl; + indent(out) << "}" << endl << endl; +} + +// generates java method to serialize (in the Java sense) the object +void t_java_generator::generate_java_struct_read_object(ostream& out, t_struct* tstruct) { + indent(out) << "private void readObject(java.io.ObjectInputStream in) throws " + "java.io.IOException, java.lang.ClassNotFoundException {" << endl; + indent(out) << " try {" << endl; + if (!tstruct->is_union()) { + switch (needs_isset(tstruct)) { + case ISSET_NONE: + break; + case ISSET_PRIMITIVE: + indent(out) << " // it doesn't seem like you should have to do this, but java " + "serialization is wacky, and doesn't call the default constructor." << endl; + indent(out) << " __isset_bitfield = 0;" << endl; + break; + case ISSET_BITSET: + indent(out) << " // it doesn't seem like you should have to do this, but java " + "serialization is wacky, and doesn't call the default constructor." << endl; + indent(out) << " __isset_bit_vector = new java.util.BitSet(1);" << endl; + break; + } + } + indent(out) << " read(new org.apache.thrift.protocol.TCompactProtocol(new " + "org.apache.thrift.transport.TIOStreamTransport(in)));" << endl; + indent(out) << " } catch (org.apache.thrift.TException te) {" << endl; + indent(out) << " throw new java.io.IOException(te" << (android_legacy_ ? ".getMessage()" : "") + << ");" << endl; + indent(out) << " }" << endl; + indent(out) << "}" << endl << endl; +} + +void t_java_generator::generate_standard_reader(ostream& out, t_struct* tstruct) { + out << indent() << "public void read(org.apache.thrift.protocol.TProtocol iprot, " + << tstruct->get_name() << " struct) throws org.apache.thrift.TException {" << endl; + indent_up(); + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + // Declare stack tmp variables and read struct header + out << indent() << "org.apache.thrift.protocol.TField schemeField;" << endl << indent() + << "iprot.readStructBegin();" << endl; + + // Loop over reading in fields + indent(out) << "while (true)" << endl; + scope_up(out); + + // Read beginning field marker + indent(out) << "schemeField = iprot.readFieldBegin();" << endl; + + // Check for field STOP marker and break + indent(out) << "if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { " << endl; + indent_up(); + indent(out) << "break;" << endl; + indent_down(); + indent(out) << "}" << endl; + + // Switch statement on the field we are reading + indent(out) << "switch (schemeField.id) {" << endl; + + indent_up(); + + // Generate deserialization code for known cases + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + indent(out) << "case " << (*f_iter)->get_key() << ": // " + << constant_name((*f_iter)->get_name()) << endl; + indent_up(); + indent(out) << "if (schemeField.type == " << type_to_enum((*f_iter)->get_type()) << ") {" + << endl; + indent_up(); + + generate_deserialize_field(out, *f_iter, "struct.", true); + indent(out) << "struct." + << "set" << get_cap_name((*f_iter)->get_name()) << get_cap_name("isSet") + << "(true);" << endl; + indent_down(); + out << indent() << "} else { " << endl << indent() + << " org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);" << endl + << indent() << "}" << endl << indent() << "break;" << endl; + indent_down(); + } + + indent(out) << "default:" << endl; + indent(out) << " org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);" + << endl; + + indent_down(); + indent(out) << "}" << endl; + + // Read field end marker + indent(out) << "iprot.readFieldEnd();" << endl; + + indent_down(); + indent(out) << "}" << endl; + + out << indent() << "iprot.readStructEnd();" << endl; + + // in non-beans style, check for required fields of primitive type + // (which can be checked here but not in the general validate method) + if (!bean_style_) { + out << endl << indent() << "// check for required fields of primitive type, which can't be " + "checked in the validate method" << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_REQUIRED && !type_can_be_null((*f_iter)->get_type())) { + out << indent() << "if (!struct." << generate_isset_check(*f_iter) << ") {" << endl + << indent() + << " throw new org.apache.thrift.protocol.TProtocolException(\"Required field '" + << (*f_iter)->get_name() + << "' was not found in serialized data! Struct: \" + toString());" << endl << indent() + << "}" << endl; + } + } + } + + // performs various checks (e.g. check that all required fields are set) + indent(out) << "struct.validate();" << endl; + + indent_down(); + out << indent() << "}" << endl; +} + +void t_java_generator::generate_standard_writer(ostream& out, t_struct* tstruct, bool is_result) { + indent_up(); + out << indent() << "public void write(org.apache.thrift.protocol.TProtocol oprot, " + << tstruct->get_name() << " struct) throws org.apache.thrift.TException {" << endl; + indent_up(); + const vector<t_field*>& fields = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator f_iter; + + // performs various checks (e.g. check that all required fields are set) + indent(out) << "struct.validate();" << endl << endl; + + indent(out) << "oprot.writeStructBegin(STRUCT_DESC);" << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + bool null_allowed = type_can_be_null((*f_iter)->get_type()); + if (null_allowed) { + out << indent() << "if (struct." << (*f_iter)->get_name() << " != null) {" << endl; + indent_up(); + } + bool optional = ((*f_iter)->get_req() == t_field::T_OPTIONAL) || (is_result && !null_allowed); + if (optional) { + indent(out) << "if (" + << "struct." << generate_isset_check((*f_iter)) << ") {" << endl; + indent_up(); + } + + indent(out) << "oprot.writeFieldBegin(" << constant_name((*f_iter)->get_name()) + << "_FIELD_DESC);" << endl; + + // Write field contents + generate_serialize_field(out, *f_iter, "struct.", true); + + // Write field closer + indent(out) << "oprot.writeFieldEnd();" << endl; + + if (optional) { + indent_down(); + indent(out) << "}" << endl; + } + if (null_allowed) { + indent_down(); + indent(out) << "}" << endl; + } + } + // Write the struct map + out << indent() << "oprot.writeFieldStop();" << endl << indent() << "oprot.writeStructEnd();" + << endl; + + indent_down(); + out << indent() << "}" << endl << endl; + indent_down(); +} + +void t_java_generator::generate_java_struct_standard_scheme(ostream& out, + t_struct* tstruct, + bool is_result) { + indent(out) << "private static class " << tstruct->get_name() + << "StandardSchemeFactory implements org.apache.thrift.scheme.SchemeFactory {" << endl; + indent_up(); + indent(out) << "public " << tstruct->get_name() << "StandardScheme getScheme() {" << endl; + indent_up(); + indent(out) << "return new " << tstruct->get_name() << "StandardScheme();" << endl; + indent_down(); + indent(out) << "}" << endl; + indent_down(); + indent(out) << "}" << endl << endl; + + out << indent() << "private static class " << tstruct->get_name() + << "StandardScheme extends org.apache.thrift.scheme.StandardScheme<" << tstruct->get_name() << "> {" << endl << endl; + indent_up(); + generate_standard_reader(out, tstruct); + indent_down(); + out << endl; + generate_standard_writer(out, tstruct, is_result); + + out << indent() << "}" << endl << endl; +} + +void t_java_generator::generate_java_struct_tuple_reader(ostream& out, t_struct* tstruct) { + indent(out) << "@Override" << endl; + indent(out) << "public void read(org.apache.thrift.protocol.TProtocol prot, " + << tstruct->get_name() << " struct) throws org.apache.thrift.TException {" << endl; + indent_up(); + indent(out) << "org.apache.thrift.protocol.TTupleProtocol iprot = (org.apache.thrift.protocol.TTupleProtocol) prot;" << endl; + int optional_count = 0; + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_OPTIONAL + || (*f_iter)->get_req() == t_field::T_OPT_IN_REQ_OUT) { + optional_count++; + } + if ((*f_iter)->get_req() == t_field::T_REQUIRED) { + generate_deserialize_field(out, (*f_iter), "struct.", false); + indent(out) << "struct.set" << get_cap_name((*f_iter)->get_name()) << get_cap_name("isSet") + << "(true);" << endl; + } + } + if (optional_count > 0) { + indent(out) << "java.util.BitSet incoming = iprot.readBitSet(" << optional_count << ");" << endl; + int i = 0; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_OPTIONAL + || (*f_iter)->get_req() == t_field::T_OPT_IN_REQ_OUT) { + indent(out) << "if (incoming.get(" << i << ")) {" << endl; + indent_up(); + generate_deserialize_field(out, (*f_iter), "struct.", false); + indent(out) << "struct.set" << get_cap_name((*f_iter)->get_name()) << get_cap_name("isSet") + << "(true);" << endl; + indent_down(); + indent(out) << "}" << endl; + i++; + } + } + } + indent_down(); + indent(out) << "}" << endl; +} + +void t_java_generator::generate_java_struct_tuple_writer(ostream& out, t_struct* tstruct) { + indent(out) << "@Override" << endl; + indent(out) << "public void write(org.apache.thrift.protocol.TProtocol prot, " + << tstruct->get_name() << " struct) throws org.apache.thrift.TException {" << endl; + indent_up(); + indent(out) << "org.apache.thrift.protocol.TTupleProtocol oprot = (org.apache.thrift.protocol.TTupleProtocol) prot;" << endl; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool has_optional = false; + int optional_count = 0; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_OPTIONAL + || (*f_iter)->get_req() == t_field::T_OPT_IN_REQ_OUT) { + optional_count++; + has_optional = true; + } + if ((*f_iter)->get_req() == t_field::T_REQUIRED) { + generate_serialize_field(out, (*f_iter), "struct.", false); + } + } + if (has_optional) { + indent(out) << "java.util.BitSet optionals = new java.util.BitSet();" << endl; + int i = 0; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_OPTIONAL + || (*f_iter)->get_req() == t_field::T_OPT_IN_REQ_OUT) { + indent(out) << "if (struct." << generate_isset_check((*f_iter)) << ") {" << endl; + indent_up(); + indent(out) << "optionals.set(" << i << ");" << endl; + indent_down(); + indent(out) << "}" << endl; + i++; + } + } + + indent(out) << "oprot.writeBitSet(optionals, " << optional_count << ");" << endl; + int j = 0; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_OPTIONAL + || (*f_iter)->get_req() == t_field::T_OPT_IN_REQ_OUT) { + indent(out) << "if (struct." << generate_isset_check(*f_iter) << ") {" << endl; + indent_up(); + generate_serialize_field(out, (*f_iter), "struct.", false); + indent_down(); + indent(out) << "}" << endl; + j++; + } + } + } + indent_down(); + indent(out) << "}" << endl; +} + +void t_java_generator::generate_java_struct_tuple_scheme(ostream& out, t_struct* tstruct) { + indent(out) << "private static class " << tstruct->get_name() + << "TupleSchemeFactory implements org.apache.thrift.scheme.SchemeFactory {" << endl; + indent_up(); + indent(out) << "public " << tstruct->get_name() << "TupleScheme getScheme() {" << endl; + indent_up(); + indent(out) << "return new " << tstruct->get_name() << "TupleScheme();" << endl; + indent_down(); + indent(out) << "}" << endl; + indent_down(); + indent(out) << "}" << endl << endl; + out << indent() << "private static class " << tstruct->get_name() + << "TupleScheme extends org.apache.thrift.scheme.TupleScheme<" << tstruct->get_name() << "> {" << endl << endl; + indent_up(); + generate_java_struct_tuple_writer(out, tstruct); + out << endl; + generate_java_struct_tuple_reader(out, tstruct); + indent_down(); + out << indent() << "}" << endl << endl; +} + +void t_java_generator::generate_java_scheme_lookup(ostream& out) { + indent(out) << "private static <S extends org.apache.thrift.scheme.IScheme> S scheme(" + << "org.apache.thrift.protocol.TProtocol proto) {" << endl; + indent_up(); + indent(out) << "return (org.apache.thrift.scheme.StandardScheme.class.equals(proto.getScheme()) " + << "? STANDARD_SCHEME_FACTORY " + << ": TUPLE_SCHEME_FACTORY" + << ").getScheme();" << endl; + indent_down(); + indent(out) << "}" << endl; +} + +void t_java_generator::generate_javax_generated_annotation(ostream& out) { + time_t seconds = time(NULL); + struct tm* now = localtime(&seconds); + indent(out) << "@javax.annotation.Generated(value = \"" << autogen_summary() << "\""; + if (undated_generated_annotations_) { + out << ")" << endl; + } else { + indent(out) << ", date = \"" << (now->tm_year + 1900) << "-" << setfill('0') << setw(2) + << (now->tm_mon + 1) << "-" << setfill('0') << setw(2) << now->tm_mday + << "\")" << endl; + } +} + +THRIFT_REGISTER_GENERATOR( + java, + "Java", + " beans: Members will be private, and setter methods will return void.\n" + " private-members: Members will be private, but setter methods will return 'this' like " + "usual.\n" + " nocamel: Do not use CamelCase field accessors with beans.\n" + " fullcamel: Convert underscored_accessor_or_service_names to camelCase.\n" + " android: Generated structures are Parcelable.\n" + " android_legacy: Do not use java.io.IOException(throwable) (available for Android 2.3 and " + "above).\n" + " option_type: Wrap optional fields in an Option type.\n" + " rethrow_unhandled_exceptions:\n" + " Enable rethrow of unhandled exceptions and let them propagate futher." + " (Default behavior is to catch and log it.)\n" + " java5: Generate Java 1.5 compliant code (includes android_legacy flag).\n" + " reuse-objects: Data objects will not be allocated, but existing instances will be used " + "(read and write).\n" + " sorted_containers:\n" + " Use TreeSet/TreeMap instead of HashSet/HashMap as a implementation of " + "set/map.\n" + " generated_annotations=[undated|suppress]:\n" + " undated: suppress the date at @Generated annotations\n" + " suppress: suppress @Generated annotations entirely\n" + " unsafe_binaries: Do not copy ByteBuffers in constructors, getters, and setters.\n") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_javame_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_javame_generator.cc new file mode 100644 index 000000000..e1694abdf --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_javame_generator.cc @@ -0,0 +1,3294 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <sstream> +#include <string> +#include <fstream> +#include <iostream> +#include <vector> +#include <cctype> + +#include <sys/stat.h> +#include <stdexcept> + +#include "thrift/platform.h" +#include "thrift/generate/t_oop_generator.h" + +using std::map; +using std::ostream; +using std::ostringstream; +using std::string; +using std::stringstream; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes + +/** + * Java code generator. + * + */ +class t_javame_generator : public t_oop_generator { +public: + t_javame_generator(t_program* program, + const std::map<std::string, std::string>& parsed_options, + const std::string& option_string) + : t_oop_generator(program) { + (void)parsed_options; + (void)option_string; + std::map<std::string, std::string>::const_iterator iter; + + /* no options yet */ + for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + throw "unknown option javame:" + iter->first; + } + + out_dir_base_ = "gen-javame"; + } + + /** + * Init and close methods + */ + + void init_generator() override; + void close_generator() override; + + void generate_consts(std::vector<t_const*> consts) override; + + /** + * Program-level generation functions + */ + + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_struct(t_struct* tstruct) override; + void generate_union(t_struct* tunion); + void generate_xception(t_struct* txception) override; + void generate_service(t_service* tservice) override; + + void print_const_value(std::ostream& out, + std::string name, + t_type* type, + t_const_value* value, + bool in_static, + bool defval = false); + std::string render_const_value(std::ostream& out, + std::string name, + t_type* type, + t_const_value* value); + + /** + * Service-level generation functions + */ + + void generate_java_struct(t_struct* tstruct, bool is_exception); + + void generate_java_struct_definition(std::ostream& out, + t_struct* tstruct, + bool is_xception = false, + bool in_class = false, + bool is_result = false); + void generate_java_struct_equality(std::ostream& out, t_struct* tstruct); + void generate_java_struct_compare_to(std::ostream& out, t_struct* tstruct); + void generate_java_struct_reader(std::ostream& out, t_struct* tstruct); + void generate_java_validator(std::ostream& out, t_struct* tstruct); + void generate_java_struct_result_writer(std::ostream& out, t_struct* tstruct); + void generate_java_struct_writer(std::ostream& out, t_struct* tstruct); + void generate_java_struct_tostring(std::ostream& out, t_struct* tstruct); + void generate_java_struct_clear(std::ostream& out, t_struct* tstruct); + void generate_field_value_meta_data(std::ostream& out, t_type* type); + std::string get_java_type_string(t_type* type); + void generate_reflection_setters(std::ostringstream& out, + t_type* type, + std::string field_name, + std::string cap_name); + void generate_reflection_getters(std::ostringstream& out, + t_type* type, + std::string field_name, + std::string cap_name); + void generate_generic_field_getters_setters(std::ostream& out, t_struct* tstruct); + void generate_java_bean_boilerplate(std::ostream& out, t_struct* tstruct); + + void generate_function_helpers(t_function* tfunction); + std::string get_cap_name(std::string name); + std::string generate_isset_check(t_field* field); + std::string generate_isset_check(std::string field); + void generate_isset_set(ostream& out, t_field* field); + std::string isset_field_id(t_field* field); + + void generate_primitive_service_interface(t_service* tservice); + void generate_service_interface(t_service* tservice); + void generate_service_helpers(t_service* tservice); + void generate_service_client(t_service* tservice); + void generate_service_server(t_service* tservice); + void generate_process_function(t_service* tservice, t_function* tfunction); + + void generate_java_union(t_struct* tstruct); + void generate_union_constructor(ostream& out, t_struct* tstruct); + void generate_union_getters_and_setters(ostream& out, t_struct* tstruct); + void generate_union_abstract_methods(ostream& out, t_struct* tstruct); + void generate_check_type(ostream& out, t_struct* tstruct); + void generate_read_value(ostream& out, t_struct* tstruct); + void generate_write_value(ostream& out, t_struct* tstruct); + void generate_get_field_desc(ostream& out, t_struct* tstruct); + void generate_get_struct_desc(ostream& out, t_struct* tstruct); + void generate_get_field_name(ostream& out, t_struct* tstruct); + + void generate_union_comparisons(ostream& out, t_struct* tstruct); + void generate_union_hashcode(ostream& out, t_struct* tstruct); + + /** + * Serialization constructs + */ + + void generate_deserialize_field(std::ostream& out, t_field* tfield, std::string prefix = ""); + + void generate_deserialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); + + void generate_deserialize_container(std::ostream& out, t_type* ttype, std::string prefix = ""); + + void generate_deserialize_set_element(std::ostream& out, t_set* tset, std::string prefix = ""); + + void generate_deserialize_map_element(std::ostream& out, t_map* tmap, std::string prefix = ""); + + void generate_deserialize_list_element(std::ostream& out, + t_list* tlist, + std::string prefix = ""); + + void generate_serialize_field(std::ostream& out, t_field* tfield, std::string prefix = ""); + + void generate_serialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); + + void generate_serialize_container(std::ostream& out, t_type* ttype, std::string prefix = ""); + + void generate_serialize_map_element(std::ostream& out, + t_map* tmap, + std::string iter, + std::string map); + + void generate_serialize_set_element(std::ostream& out, t_set* tmap, std::string iter); + + void generate_serialize_list_element(std::ostream& out, t_list* tlist, std::string iter); + + void generate_java_doc(std::ostream& out, t_field* field) override; + + void generate_java_doc(std::ostream& out, t_doc* tdoc) override; + + void generate_java_doc(std::ostream& out, t_function* tdoc) override; + + void generate_java_docstring_comment(std::ostream& out, string contents) override; + + void generate_deep_copy_container(std::ostream& out, + std::string source_name_p1, + std::string source_name_p2, + std::string result_name, + t_type* type); + void generate_deep_copy_non_container(std::ostream& out, + std::string source_name, + std::string dest_name, + t_type* type); + + bool has_bit_vector(t_struct* tstruct); + + /** + * Helper rendering functions + */ + + std::string java_package(); + std::string java_type_imports(); + std::string java_thrift_imports(); + std::string type_name(t_type* ttype, + bool in_container = false, + bool in_init = false, + bool skip_generic = false); + std::string base_type_name(t_base_type* tbase, bool in_container = false); + std::string declare_field(t_field* tfield, bool init = false); + std::string function_signature(t_function* tfunction, std::string prefix = ""); + std::string argument_list(t_struct* tstruct, bool include_types = true); + std::string type_to_enum(t_type* ttype); + std::string get_enum_class_name(t_type* type) override; + void generate_struct_desc(ostream& out, t_struct* tstruct); + void generate_field_descs(ostream& out, t_struct* tstruct); + std::string box_type(t_type* type, string value); + + bool type_can_be_null(t_type* ttype) { + ttype = get_true_type(ttype); + + return ttype->is_container() || ttype->is_struct() || ttype->is_xception() || ttype->is_string() + || ttype->is_enum(); + } + + std::string constant_name(std::string name); + +private: + /** + * File streams + */ + + std::string package_name_; + ofstream_with_content_based_conditional_update f_service_; + std::string package_dir_; +}; + +/** + * Prepares for file generation by opening up the necessary file output + * streams. + * + * @param tprogram The program to generate + */ +void t_javame_generator::init_generator() { + // Make output directory + MKDIR(get_out_dir().c_str()); + package_name_ = program_->get_namespace("java"); + + string dir = package_name_; + string subdir = get_out_dir(); + string::size_type loc; + while ((loc = dir.find(".")) != string::npos) { + subdir = subdir + "/" + dir.substr(0, loc); + MKDIR(subdir.c_str()); + dir = dir.substr(loc + 1); + } + if (dir.size() > 0) { + subdir = subdir + "/" + dir; + MKDIR(subdir.c_str()); + } + + package_dir_ = subdir; +} + +/** + * Packages the generated file + * + * @return String of the package, i.e. "package org.apache.thriftdemo;" + */ +string t_javame_generator::java_package() { + if (!package_name_.empty()) { + return string("package ") + package_name_ + ";\n\n"; + } + return ""; +} + +/** + * Prints standard java imports + * + * @return List of imports for Java types that are used in here + */ +string t_javame_generator::java_type_imports() { + return string() + "import java.util.Hashtable;\n" + "import java.util.Vector;\n" + + "import java.util.Enumeration;\n\n"; +} + +/** + * Prints standard java imports + * + * @return List of imports necessary for thrift + */ +string t_javame_generator::java_thrift_imports() { + return string() + "import org.apache.thrift.*;\n" + "import org.apache.thrift.meta_data.*;\n" + + "import org.apache.thrift.transport.*;\n" + "import org.apache.thrift.protocol.*;\n\n"; +} + +/** + * Nothing in Java + */ +void t_javame_generator::close_generator() { +} + +/** + * Generates a typedef. This is not done in Java, since it does + * not support arbitrary name replacements, and it'd be a wacky waste + * of overhead to make wrapper classes. + * + * @param ttypedef The type definition + */ +void t_javame_generator::generate_typedef(t_typedef* ttypedef) { + (void)ttypedef; +} + +/** + * Enums are a class with a set of static constants. + * + * @param tenum The enumeration + */ +void t_javame_generator::generate_enum(t_enum* tenum) { + // Make output file + string f_enum_name = package_dir_ + "/" + (tenum->get_name()) + ".java"; + ofstream_with_content_based_conditional_update f_enum; + f_enum.open(f_enum_name.c_str()); + + // Comment and package it + f_enum << autogen_comment() << java_package(); + + generate_java_doc(f_enum, tenum); + indent(f_enum) << "public class " << tenum->get_name() << " implements org.apache.thrift.TEnum "; + scope_up(f_enum); + f_enum << endl; + + vector<t_enum_value*> constants = tenum->get_constants(); + vector<t_enum_value*>::iterator c_iter; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + int value = (*c_iter)->get_value(); + generate_java_doc(f_enum, *c_iter); + indent(f_enum) << "public static final " << tenum->get_name() << " " << (*c_iter)->get_name() + << " = new " << tenum->get_name() << "(" << value << ");" << endl; + } + f_enum << endl; + + // Field for thriftCode + indent(f_enum) << "private final int value;" << endl << endl; + + indent(f_enum) << "private " << tenum->get_name() << "(int value) {" << endl; + indent(f_enum) << " this.value = value;" << endl; + indent(f_enum) << "}" << endl << endl; + + indent(f_enum) << "/**" << endl; + indent(f_enum) << " * Get the integer value of this enum value, as defined in the Thrift IDL." + << endl; + indent(f_enum) << " */" << endl; + indent(f_enum) << "public int getValue() {" << endl; + indent(f_enum) << " return value;" << endl; + indent(f_enum) << "}" << endl << endl; + + indent(f_enum) << "/**" << endl; + indent(f_enum) << " * Find a the enum type by its integer value, as defined in the Thrift IDL." + << endl; + indent(f_enum) << " * @return null if the value is not found." << endl; + indent(f_enum) << " */" << endl; + indent(f_enum) << "public static " + tenum->get_name() + " findByValue(int value) { " << endl; + + indent_up(); + + indent(f_enum) << "switch (value) {" << endl; + indent_up(); + + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + int value = (*c_iter)->get_value(); + indent(f_enum) << "case " << value << ":" << endl; + indent(f_enum) << " return " << (*c_iter)->get_name() << ";" << endl; + } + + indent(f_enum) << "default:" << endl; + indent(f_enum) << " return null;" << endl; + + indent_down(); + + indent(f_enum) << "}" << endl; + + indent_down(); + + indent(f_enum) << "}" << endl; + + scope_down(f_enum); + + f_enum.close(); +} + +/** + * Generates a class that holds all the constants. + */ +void t_javame_generator::generate_consts(std::vector<t_const*> consts) { + if (consts.empty()) { + return; + } + + string f_consts_name = package_dir_ + "/" + program_name_ + "Constants.java"; + ofstream_with_content_based_conditional_update f_consts; + f_consts.open(f_consts_name.c_str()); + + // Print header + f_consts << autogen_comment() << java_package() << java_type_imports(); + + f_consts << "public class " << program_name_ << "Constants {" << endl << endl; + indent_up(); + vector<t_const*>::iterator c_iter; + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + print_const_value(f_consts, + (*c_iter)->get_name(), + (*c_iter)->get_type(), + (*c_iter)->get_value(), + false); + } + indent_down(); + indent(f_consts) << "}" << endl; + f_consts.close(); +} + +/** + * Prints the value of a constant with the given type. Note that type checking + * is NOT performed in this function as it is always run beforehand using the + * validate_types method in main.cc + */ +void t_javame_generator::print_const_value(std::ostream& out, + string name, + t_type* type, + t_const_value* value, + bool in_static, + bool defval) { + type = get_true_type(type); + + indent(out); + if (!defval) { + out << (in_static ? "" : "public static final ") << type_name(type) << " "; + } + if (type->is_base_type()) { + string v2 = render_const_value(out, name, type, value); + out << name << " = " << v2 << ";" << endl << endl; + } else if (type->is_enum()) { + out << name << " = " << render_const_value(out, name, type, value) << ";" << endl << endl; + } else if (type->is_struct() || type->is_xception()) { + const vector<t_field*>& fields = ((t_struct*)type)->get_members(); + vector<t_field*>::const_iterator f_iter; + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + out << name << " = new " << type_name(type, false, true) << "();" << endl; + if (!in_static) { + indent(out) << "static {" << endl; + indent_up(); + } + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + t_type* field_type = NULL; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field_type = (*f_iter)->get_type(); + } + } + if (field_type == NULL) { + throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string(); + } + string val = render_const_value(out, name, field_type, v_iter->second); + indent(out) << name << "."; + std::string cap_name = get_cap_name(v_iter->first->get_string()); + out << "set" << cap_name << "(" << val << ");" << endl; + } + if (!in_static) { + indent_down(); + indent(out) << "}" << endl; + } + out << endl; + } else if (type->is_map()) { + out << name << " = new " << type_name(type, false, true) << "();" << endl; + if (!in_static) { + indent(out) << "static {" << endl; + indent_up(); + } + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string key = render_const_value(out, name, ktype, v_iter->first); + string val = render_const_value(out, name, vtype, v_iter->second); + indent(out) << name << ".put(" << box_type(ktype, key) << ", " << box_type(vtype, val) << ");" + << endl; + } + if (!in_static) { + indent_down(); + indent(out) << "}" << endl; + } + out << endl; + } else if (type->is_list() || type->is_set()) { + out << name << " = new " << type_name(type, false, true) << "();" << endl; + if (!in_static) { + indent(out) << "static {" << endl; + indent_up(); + } + t_type* etype; + if (type->is_list()) { + etype = ((t_list*)type)->get_elem_type(); + } else { + etype = ((t_set*)type)->get_elem_type(); + } + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string val = render_const_value(out, name, etype, *v_iter); + if (type->is_list()) { + indent(out) << name << ".addElement(" << box_type(etype, val) << ");" << endl; + } else { + indent(out) << name << ".put(" << box_type(etype, val) << ", " << box_type(etype, val) + << ");" << endl; + } + } + if (!in_static) { + indent_down(); + indent(out) << "}" << endl; + } + out << endl; + } else { + throw "compiler error: no const of type " + type->get_name(); + } +} + +string t_javame_generator::render_const_value(ostream& out, + string name, + t_type* type, + t_const_value* value) { + (void)name; + type = get_true_type(type); + std::ostringstream render; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + render << '"' << get_escaped_string(value) << '"'; + break; + case t_base_type::TYPE_BOOL: + render << ((value->get_integer() > 0) ? "true" : "false"); + break; + case t_base_type::TYPE_I8: + render << "(byte)" << value->get_integer(); + break; + case t_base_type::TYPE_I16: + render << "(short)" << value->get_integer(); + break; + case t_base_type::TYPE_I32: + render << value->get_integer(); + break; + case t_base_type::TYPE_I64: + render << value->get_integer() << "L"; + break; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) { + render << "(double)" << value->get_integer(); + } else { + render << value->get_double(); + } + break; + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + render << type_name(type, false, false) << "." << value->get_identifier(); + } else { + string t = tmp("tmp"); + print_const_value(out, t, type, value, true); + render << t; + } + + return render.str(); +} + +string t_javame_generator::box_type(t_type* type, string value) { + if (type->is_base_type()) { + switch (((t_base_type*)type)->get_base()) { + case t_base_type::TYPE_BOOL: + return "new Boolean(" + value + ")"; + case t_base_type::TYPE_I8: + return "new Byte(" + value + ")"; + case t_base_type::TYPE_I16: + return "new Short(" + value + ")"; + case t_base_type::TYPE_I32: + return "new Integer(" + value + ")"; + case t_base_type::TYPE_I64: + return "new Long(" + value + ")"; + case t_base_type::TYPE_DOUBLE: + return "new Double(" + value + ")"; + default: + break; + } + } + return value; +} + +/** + * Generates a struct definition for a thrift data type. This will be a TBase + * implementor. + * + * @param tstruct The struct definition + */ +void t_javame_generator::generate_struct(t_struct* tstruct) { + if (tstruct->is_union()) { + generate_java_union(tstruct); + } else { + generate_java_struct(tstruct, false); + } +} + +/** + * Exceptions are structs, but they inherit from Exception + * + * @param tstruct The struct definition + */ +void t_javame_generator::generate_xception(t_struct* txception) { + generate_java_struct(txception, true); +} + +/** + * Java struct definition. + * + * @param tstruct The struct definition + */ +void t_javame_generator::generate_java_struct(t_struct* tstruct, bool is_exception) { + // Make output file + string f_struct_name = package_dir_ + "/" + (tstruct->get_name()) + ".java"; + ofstream_with_content_based_conditional_update f_struct; + f_struct.open(f_struct_name.c_str()); + + f_struct << autogen_comment() << java_package() << java_type_imports() << java_thrift_imports(); + + generate_java_struct_definition(f_struct, tstruct, is_exception); + f_struct.close(); +} + +/** + * Java union definition. + * + * @param tstruct The struct definition + */ +void t_javame_generator::generate_java_union(t_struct* tstruct) { + // Make output file + string f_struct_name = package_dir_ + "/" + (tstruct->get_name()) + ".java"; + ofstream_with_content_based_conditional_update f_struct; + f_struct.open(f_struct_name.c_str()); + + f_struct << autogen_comment() << java_package() << java_type_imports() << java_thrift_imports(); + + generate_java_doc(f_struct, tstruct); + + bool is_final = (tstruct->annotations_.find("final") != tstruct->annotations_.end()); + + indent(f_struct) << "public " << (is_final ? "final " : "") << "class " << tstruct->get_name() + << " extends TUnion "; + + scope_up(f_struct); + + generate_struct_desc(f_struct, tstruct); + generate_field_descs(f_struct, tstruct); + + f_struct << endl; + + generate_union_constructor(f_struct, tstruct); + + f_struct << endl; + + generate_union_abstract_methods(f_struct, tstruct); + + f_struct << endl; + + generate_union_getters_and_setters(f_struct, tstruct); + + f_struct << endl; + + generate_union_comparisons(f_struct, tstruct); + + f_struct << endl; + + generate_union_hashcode(f_struct, tstruct); + + f_struct << endl; + + scope_down(f_struct); + + f_struct.close(); +} + +void t_javame_generator::generate_union_constructor(ostream& out, t_struct* tstruct) { + indent(out) << "public " << type_name(tstruct) << "() {" << endl; + indent(out) << " super();" << endl; + indent(out) << "}" << endl << endl; + + indent(out) << "public " << type_name(tstruct) << "(_Fields setField, Object value) {" << endl; + indent(out) << " super(setField, value);" << endl; + indent(out) << "}" << endl << endl; + + indent(out) << "public " << type_name(tstruct) << "(" << type_name(tstruct) << " other) {" + << endl; + indent(out) << " super(other);" << endl; + indent(out) << "}" << endl; + + indent(out) << "public " << tstruct->get_name() << " deepCopy() {" << endl; + indent(out) << " return new " << tstruct->get_name() << "(this);" << endl; + indent(out) << "}" << endl << endl; + + // generate "constructors" for each field + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + indent(out) << "public static " << type_name(tstruct) << " " << (*m_iter)->get_name() << "(" + << type_name((*m_iter)->get_type()) << " value) {" << endl; + indent(out) << " " << type_name(tstruct) << " x = new " << type_name(tstruct) << "();" << endl; + indent(out) << " x.set" << get_cap_name((*m_iter)->get_name()) << "(value);" << endl; + indent(out) << " return x;" << endl; + indent(out) << "}" << endl << endl; + } +} + +void t_javame_generator::generate_union_getters_and_setters(ostream& out, t_struct* tstruct) { + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + bool first = true; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if (first) { + first = false; + } else { + out << endl; + } + + t_field* field = (*m_iter); + + generate_java_doc(out, field); + indent(out) << "public " << type_name(field->get_type()) << " get" + << get_cap_name(field->get_name()) << "() {" << endl; + indent(out) << " if (getSetField() == _Fields." << constant_name(field->get_name()) << ") {" + << endl; + indent(out) << " return (" << type_name(field->get_type(), true) << ")getFieldValue();" + << endl; + indent(out) << " } else {" << endl; + indent(out) << " throw new RuntimeException(\"Cannot get field '" << field->get_name() + << "' because union is currently set to \" + getFieldDesc(getSetField()).name);" + << endl; + indent(out) << " }" << endl; + indent(out) << "}" << endl; + + out << endl; + + generate_java_doc(out, field); + indent(out) << "public void set" << get_cap_name(field->get_name()) << "(" + << type_name(field->get_type()) << " value) {" << endl; + if (type_can_be_null(field->get_type())) { + indent(out) << " if (value == null) throw new NullPointerException();" << endl; + } + indent(out) << " setField_ = _Fields." << constant_name(field->get_name()) << ";" << endl; + indent(out) << " value_ = value;" << endl; + indent(out) << "}" << endl; + } +} + +void t_javame_generator::generate_union_abstract_methods(ostream& out, t_struct* tstruct) { + generate_check_type(out, tstruct); + out << endl; + generate_read_value(out, tstruct); + out << endl; + generate_write_value(out, tstruct); + out << endl; + generate_get_field_desc(out, tstruct); + out << endl; + generate_get_struct_desc(out, tstruct); + out << endl; +} + +void t_javame_generator::generate_check_type(ostream& out, t_struct* tstruct) { + indent(out) + << "protected void checkType(_Fields setField, Object value) throws ClassCastException {" + << endl; + indent_up(); + + indent(out) << "switch (setField) {" << endl; + indent_up(); + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_field* field = (*m_iter); + + indent(out) << "case " << constant_name(field->get_name()) << ":" << endl; + indent(out) << " if (value instanceof " << type_name(field->get_type(), true, false, true) + << ") {" << endl; + indent(out) << " break;" << endl; + indent(out) << " }" << endl; + indent(out) << " throw new ClassCastException(\"Was expecting value of type " + << type_name(field->get_type(), true, false) << " for field '" << field->get_name() + << "', but got \" + value.getClass().getSimpleName());" << endl; + // do the real check here + } + + indent(out) << "default:" << endl; + indent(out) << " throw new IllegalArgumentException(\"Unknown field id \" + setField);" << endl; + + indent_down(); + indent(out) << "}" << endl; + + indent_down(); + indent(out) << "}" << endl; +} + +void t_javame_generator::generate_read_value(ostream& out, t_struct* tstruct) { + indent(out) << "protected Object readValue(TProtocol iprot, TField field) throws TException {" + << endl; + + indent_up(); + + indent(out) << "_Fields setField = _Fields.findByThriftId(field.id);" << endl; + indent(out) << "if (setField != null) {" << endl; + indent_up(); + indent(out) << "switch (setField) {" << endl; + indent_up(); + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_field* field = (*m_iter); + + indent(out) << "case " << constant_name(field->get_name()) << ":" << endl; + indent_up(); + indent(out) << "if (field.type == " << constant_name(field->get_name()) << "_FIELD_DESC.type) {" + << endl; + indent_up(); + indent(out) << type_name(field->get_type(), true, false) << " " << field->get_name() << ";" + << endl; + generate_deserialize_field(out, field, ""); + indent(out) << "return " << field->get_name() << ";" << endl; + indent_down(); + indent(out) << "} else {" << endl; + indent(out) << " TProtocolUtil.skip(iprot, field.type);" << endl; + indent(out) << " return null;" << endl; + indent(out) << "}" << endl; + indent_down(); + } + + indent(out) << "default:" << endl; + indent(out) << " throw new IllegalStateException(\"setField wasn't null, but didn't match any " + "of the case statements!\");" << endl; + + indent_down(); + indent(out) << "}" << endl; + + indent_down(); + indent(out) << "} else {" << endl; + indent_up(); + indent(out) << "TProtocolUtil.skip(iprot, field.type);" << endl; + indent(out) << "return null;" << endl; + indent_down(); + indent(out) << "}" << endl; + + indent_down(); + indent(out) << "}" << endl; +} + +void t_javame_generator::generate_write_value(ostream& out, t_struct* tstruct) { + indent(out) << "protected void writeValue(TProtocol oprot) throws TException {" << endl; + + indent_up(); + + indent(out) << "switch (setField_) {" << endl; + indent_up(); + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_field* field = (*m_iter); + + indent(out) << "case " << constant_name(field->get_name()) << ":" << endl; + indent_up(); + indent(out) << type_name(field->get_type(), true, false) << " " << field->get_name() << " = (" + << type_name(field->get_type(), true, false) << ")value_;" << endl; + generate_serialize_field(out, field, ""); + indent(out) << "return;" << endl; + indent_down(); + } + + indent(out) << "default:" << endl; + indent(out) << " throw new IllegalStateException(\"Cannot write union with unknown field \" + " + "setField_);" << endl; + + indent_down(); + indent(out) << "}" << endl; + + indent_down(); + + indent(out) << "}" << endl; +} + +void t_javame_generator::generate_get_field_desc(ostream& out, t_struct* tstruct) { + indent(out) << "protected TField getFieldDesc(_Fields setField) {" << endl; + indent_up(); + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + indent(out) << "switch (setField) {" << endl; + indent_up(); + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_field* field = (*m_iter); + indent(out) << "case " << constant_name(field->get_name()) << ":" << endl; + indent(out) << " return " << constant_name(field->get_name()) << "_FIELD_DESC;" << endl; + } + + indent(out) << "default:" << endl; + indent(out) << " throw new IllegalArgumentException(\"Unknown field id \" + setField);" << endl; + + indent_down(); + indent(out) << "}" << endl; + + indent_down(); + indent(out) << "}" << endl; +} + +void t_javame_generator::generate_get_struct_desc(ostream& out, t_struct* tstruct) { + (void)tstruct; + indent(out) << "protected TStruct getStructDesc() {" << endl; + indent(out) << " return STRUCT_DESC;" << endl; + indent(out) << "}" << endl; +} + +void t_javame_generator::generate_union_comparisons(ostream& out, t_struct* tstruct) { + // equality + indent(out) << "public boolean equals(Object other) {" << endl; + indent(out) << " if (other instanceof " << tstruct->get_name() << ") {" << endl; + indent(out) << " return equals((" << tstruct->get_name() << ")other);" << endl; + indent(out) << " } else {" << endl; + indent(out) << " return false;" << endl; + indent(out) << " }" << endl; + indent(out) << "}" << endl; + + out << endl; + + indent(out) << "public boolean equals(" << tstruct->get_name() << " other) {" << endl; + indent(out) << " return other != null && getSetField() == other.getSetField() && " + "getFieldValue().equals(other.getFieldValue());" << endl; + indent(out) << "}" << endl; + out << endl; + + indent(out) << "public int compareTo(" << type_name(tstruct) << " other) {" << endl; + indent(out) << " int lastComparison = TBaseHelper.compareTo(getSetField(), other.getSetField());" + << endl; + indent(out) << " if (lastComparison == 0) {" << endl; + indent(out) << " return TBaseHelper.compareTo(getFieldValue(), other.getFieldValue());" + << endl; + indent(out) << " }" << endl; + indent(out) << " return lastComparison;" << endl; + indent(out) << "}" << endl; + out << endl; +} + +void t_javame_generator::generate_union_hashcode(ostream& out, t_struct* tstruct) { + (void)tstruct; + indent(out) << "/**" << endl; + indent(out) + << " * If you'd like this to perform more respectably, use the hashcode generator option." + << endl; + indent(out) << " */" << endl; + indent(out) << "public int hashCode() {" << endl; + indent(out) << " return 0;" << endl; + indent(out) << "}" << endl; +} + +/** + * Java struct definition. This has various parameters, as it could be + * generated standalone or inside another class as a helper. If it + * is a helper than it is a static class. + * + * @param tstruct The struct definition + * @param is_exception Is this an exception? + * @param in_class If inside a class, needs to be static class + * @param is_result If this is a result it needs a different writer + */ +void t_javame_generator::generate_java_struct_definition(ostream& out, + t_struct* tstruct, + bool is_exception, + bool in_class, + bool is_result) { + generate_java_doc(out, tstruct); + + bool is_final = (tstruct->annotations_.find("final") != tstruct->annotations_.end()); + + indent(out) << "public " << (is_final ? "final " : "") << (in_class ? "static " : "") << "class " + << tstruct->get_name() << " "; + + if (is_exception) { + out << "extends Exception "; + } + out << "implements TBase "; + + scope_up(out); + + generate_struct_desc(out, tstruct); + + // Members are public for -java, private for -javabean + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + out << endl; + + generate_field_descs(out, tstruct); + + out << endl; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + indent(out) << "private "; + out << declare_field(*m_iter, false) << endl; + } + + // isset data + if (members.size() > 0) { + out << endl; + + indent(out) << "// isset id assignments" << endl; + + int i = 0; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if (!type_can_be_null((*m_iter)->get_type())) { + indent(out) << "private static final int " << isset_field_id(*m_iter) << " = " << i << ";" + << endl; + i++; + } + } + + if (i > 0) { + indent(out) << "private boolean[] __isset_vector = new boolean[" << i << "];" << endl; + } + + out << endl; + } + + bool all_optional_members = true; + + // Default constructor + indent(out) << "public " << tstruct->get_name() << "() {" << endl; + indent_up(); + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* t = get_true_type((*m_iter)->get_type()); + if ((*m_iter)->get_value() != NULL) { + print_const_value(out, + "this." + (*m_iter)->get_name(), + t, + (*m_iter)->get_value(), + true, + true); + } + if ((*m_iter)->get_req() != t_field::T_OPTIONAL) { + all_optional_members = false; + } + } + indent_down(); + indent(out) << "}" << endl << endl; + + if (!members.empty() && !all_optional_members) { + // Full constructor for all fields + indent(out) << "public " << tstruct->get_name() << "(" << endl; + indent_up(); + bool first = true; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if ((*m_iter)->get_req() != t_field::T_OPTIONAL) { + if (!first) { + out << "," << endl; + } + first = false; + indent(out) << type_name((*m_iter)->get_type()) << " " << (*m_iter)->get_name(); + } + } + out << ")" << endl; + indent_down(); + indent(out) << "{" << endl; + indent_up(); + indent(out) << "this();" << endl; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if ((*m_iter)->get_req() != t_field::T_OPTIONAL) { + indent(out) << "this." << (*m_iter)->get_name() << " = " << (*m_iter)->get_name() << ";" + << endl; + generate_isset_set(out, (*m_iter)); + } + } + indent_down(); + indent(out) << "}" << endl << endl; + } + + // copy constructor + indent(out) << "/**" << endl; + indent(out) << " * Performs a deep copy on <i>other</i>." << endl; + indent(out) << " */" << endl; + indent(out) << "public " << tstruct->get_name() << "(" << tstruct->get_name() << " other) {" + << endl; + indent_up(); + + if (has_bit_vector(tstruct)) { + indent(out) << "System.arraycopy(other.__isset_vector, 0, __isset_vector, 0, " + "other.__isset_vector.length);" << endl; + } + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_field* field = (*m_iter); + std::string field_name = field->get_name(); + t_type* type = field->get_type(); + bool can_be_null = type_can_be_null(type); + + if (can_be_null) { + indent(out) << "if (other." << generate_isset_check(field) << ") {" << endl; + indent_up(); + } + + if (type->is_container()) { + generate_deep_copy_container(out, "other", field_name, "__this__" + field_name, type); + indent(out) << "this." << field_name << " = __this__" << field_name << ";" << endl; + } else { + indent(out) << "this." << field_name << " = "; + generate_deep_copy_non_container(out, "other." + field_name, field_name, type); + out << ";" << endl; + } + + if (can_be_null) { + indent_down(); + indent(out) << "}" << endl; + } + } + + indent_down(); + indent(out) << "}" << endl << endl; + + // clone method, so that you can deep copy an object when you don't know its class. + indent(out) << "public " << tstruct->get_name() << " deepCopy() {" << endl; + indent(out) << " return new " << tstruct->get_name() << "(this);" << endl; + indent(out) << "}" << endl << endl; + + generate_java_struct_clear(out, tstruct); + + generate_java_bean_boilerplate(out, tstruct); + generate_generic_field_getters_setters(out, tstruct); + + generate_java_struct_equality(out, tstruct); + generate_java_struct_compare_to(out, tstruct); + + generate_java_struct_reader(out, tstruct); + if (is_result) { + generate_java_struct_result_writer(out, tstruct); + } else { + generate_java_struct_writer(out, tstruct); + } + generate_java_struct_tostring(out, tstruct); + generate_java_validator(out, tstruct); + scope_down(out); + out << endl; +} + +/** + * Generates equals methods and a hashCode method for a structure. + * + * @param tstruct The struct definition + */ +void t_javame_generator::generate_java_struct_equality(ostream& out, t_struct* tstruct) { + out << indent() << "public boolean equals(Object that) {" << endl; + indent_up(); + out << indent() << "if (that == null)" << endl << indent() << " return false;" << endl + << indent() << "if (that instanceof " << tstruct->get_name() << ")" << endl << indent() + << " return this.equals((" << tstruct->get_name() << ")that);" << endl << indent() + << "return false;" << endl; + scope_down(out); + out << endl; + + out << indent() << "public boolean equals(" << tstruct->get_name() << " that) {" << endl; + indent_up(); + out << indent() << "if (that == null)" << endl << indent() << " return false;" << endl + << indent() << "if (this == that)" << endl << indent() << " return true;" << endl; + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + out << endl; + + t_type* t = get_true_type((*m_iter)->get_type()); + // Most existing Thrift code does not use isset or optional/required, + // so we treat "default" fields as required. + bool is_optional = (*m_iter)->get_req() == t_field::T_OPTIONAL; + bool can_be_null = type_can_be_null(t); + string name = (*m_iter)->get_name(); + + string this_present = "true"; + string that_present = "true"; + string unequal; + + if (is_optional || can_be_null) { + this_present += " && this." + generate_isset_check(*m_iter); + that_present += " && that." + generate_isset_check(*m_iter); + } + + out << indent() << "boolean this_present_" << name << " = " << this_present << ";" << endl + << indent() << "boolean that_present_" << name << " = " << that_present << ";" << endl + << indent() << "if (" + << "this_present_" << name << " || that_present_" << name << ") {" << endl; + indent_up(); + out << indent() << "if (!(" + << "this_present_" << name << " && that_present_" << name << "))" << endl << indent() + << " return false;" << endl; + + if (t->is_binary()) { + unequal = "TBaseHelper.compareTo(this." + name + ", that." + name + ") != 0"; + } else if (can_be_null) { + unequal = "!this." + name + ".equals(that." + name + ")"; + } else { + unequal = "this." + name + " != that." + name; + } + + out << indent() << "if (" << unequal << ")" << endl << indent() << " return false;" << endl; + + scope_down(out); + } + out << endl; + indent(out) << "return true;" << endl; + scope_down(out); + out << endl; + + out << indent() << "public int hashCode() {" << endl; + indent_up(); + indent(out) << "return 0;" << endl; + indent_down(); + indent(out) << "}" << endl << endl; +} + +void t_javame_generator::generate_java_struct_compare_to(ostream& out, t_struct* tstruct) { + indent(out) << "public int compareTo(Object otherObject) {" << endl; + // indent(out) << "public int compareTo(" << type_name(tstruct) << " other) {" << endl; + indent_up(); + + indent(out) << "if (!getClass().equals(otherObject.getClass())) {" << endl; + indent(out) << " return getClass().getName().compareTo(otherObject.getClass().getName());" + << endl; + indent(out) << "}" << endl; + out << endl; + indent(out) << type_name(tstruct) << " other = (" << type_name(tstruct) << ")otherObject;"; + + indent(out) << "int lastComparison = 0;" << endl; + out << endl; + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_field* field = *m_iter; + indent(out) << "lastComparison = TBaseHelper.compareTo(" << generate_isset_check(field) + << ", other." << generate_isset_check(field) << ");" << endl; + indent(out) << "if (lastComparison != 0) {" << endl; + indent(out) << " return lastComparison;" << endl; + indent(out) << "}" << endl; + + indent(out) << "if (" << generate_isset_check(field) << ") {" << endl; + if (field->get_type()->is_struct() || field->get_type()->is_xception()) { + indent(out) << " lastComparison = this." << field->get_name() << ".compareTo(other." + << field->get_name() << ");" << endl; + } else { + indent(out) << " lastComparison = TBaseHelper.compareTo(this." << field->get_name() + << ", other." << field->get_name() << ");" << endl; + } + + indent(out) << " if (lastComparison != 0) {" << endl; + indent(out) << " return lastComparison;" << endl; + indent(out) << " }" << endl; + indent(out) << "}" << endl; + } + + indent(out) << "return 0;" << endl; + + indent_down(); + indent(out) << "}" << endl << endl; +} + +/** + * Generates a function to read all the fields of the struct. + * + * @param tstruct The struct definition + */ +void t_javame_generator::generate_java_struct_reader(ostream& out, t_struct* tstruct) { + out << indent() << "public void read(TProtocol iprot) throws TException {" << endl; + indent_up(); + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + // Declare stack tmp variables and read struct header + out << indent() << "TField field;" << endl << indent() << "iprot.readStructBegin();" << endl; + + // Loop over reading in fields + indent(out) << "while (true)" << endl; + scope_up(out); + + // Read beginning field marker + indent(out) << "field = iprot.readFieldBegin();" << endl; + + // Check for field STOP marker and break + indent(out) << "if (field.type == TType.STOP) { " << endl; + indent_up(); + indent(out) << "break;" << endl; + indent_down(); + indent(out) << "}" << endl; + + // Switch statement on the field we are reading + indent(out) << "switch (field.id) {" << endl; + + indent_up(); + + // Generate deserialization code for known cases + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + indent(out) << "case " << (*f_iter)->get_key() << ": // " + << constant_name((*f_iter)->get_name()) << endl; + indent_up(); + indent(out) << "if (field.type == " << type_to_enum((*f_iter)->get_type()) << ") {" << endl; + indent_up(); + + generate_deserialize_field(out, *f_iter, "this."); + generate_isset_set(out, *f_iter); + indent_down(); + out << indent() << "} else { " << endl << indent() << " TProtocolUtil.skip(iprot, field.type);" + << endl << indent() << "}" << endl << indent() << "break;" << endl; + indent_down(); + } + + indent(out) << "default:" << endl; + indent(out) << " TProtocolUtil.skip(iprot, field.type);" << endl; + + indent_down(); + indent(out) << "}" << endl; + + // Read field end marker + indent(out) << "iprot.readFieldEnd();" << endl; + + indent_down(); + indent(out) << "}" << endl; + + out << indent() << "iprot.readStructEnd();" << endl; + + // performs various checks (e.g. check that all required fields are set) + indent(out) << "validate();" << endl; + + indent_down(); + out << indent() << "}" << endl << endl; +} + +// generates java method to perform various checks +// (e.g. check that all required fields are set) +void t_javame_generator::generate_java_validator(ostream& out, t_struct* tstruct) { + indent(out) << "public void validate() throws TException {" << endl; + indent_up(); + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + out << indent() << "// check for required fields" << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_REQUIRED) { + out << indent() << "if (!" << generate_isset_check(*f_iter) << ") {" << endl << indent() + << " throw new TProtocolException(\"Required field '" << (*f_iter)->get_name() + << "' is unset! Struct:\" + toString());" << endl << indent() << "}" << endl << endl; + } + } + + indent_down(); + indent(out) << "}" << endl << endl; +} + +/** + * Generates a function to write all the fields of the struct + * + * @param tstruct The struct definition + */ +void t_javame_generator::generate_java_struct_writer(ostream& out, t_struct* tstruct) { + out << indent() << "public void write(TProtocol oprot) throws TException {" << endl; + indent_up(); + + string name = tstruct->get_name(); + const vector<t_field*>& fields = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator f_iter; + + // performs various checks (e.g. check that all required fields are set) + indent(out) << "validate();" << endl << endl; + + indent(out) << "oprot.writeStructBegin(STRUCT_DESC);" << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + bool null_allowed = type_can_be_null((*f_iter)->get_type()); + if (null_allowed) { + out << indent() << "if (this." << (*f_iter)->get_name() << " != null) {" << endl; + indent_up(); + } + bool optional = (*f_iter)->get_req() == t_field::T_OPTIONAL; + if (optional) { + indent(out) << "if (" << generate_isset_check((*f_iter)) << ") {" << endl; + indent_up(); + } + + indent(out) << "oprot.writeFieldBegin(" << constant_name((*f_iter)->get_name()) + << "_FIELD_DESC);" << endl; + + // Write field contents + generate_serialize_field(out, *f_iter, "this."); + + // Write field closer + indent(out) << "oprot.writeFieldEnd();" << endl; + + if (optional) { + indent_down(); + indent(out) << "}" << endl; + } + if (null_allowed) { + indent_down(); + indent(out) << "}" << endl; + } + } + // Write the struct map + out << indent() << "oprot.writeFieldStop();" << endl << indent() << "oprot.writeStructEnd();" + << endl; + + indent_down(); + out << indent() << "}" << endl << endl; +} + +/** + * Generates a function to write all the fields of the struct, + * which is a function result. These fields are only written + * if they are set in the Isset array, and only one of them + * can be set at a time. + * + * @param tstruct The struct definition + */ +void t_javame_generator::generate_java_struct_result_writer(ostream& out, t_struct* tstruct) { + out << indent() << "public void write(TProtocol oprot) throws TException {" << endl; + indent_up(); + + string name = tstruct->get_name(); + const vector<t_field*>& fields = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator f_iter; + + indent(out) << "oprot.writeStructBegin(STRUCT_DESC);" << endl; + + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + out << endl << indent() << "if "; + } else { + out << " else if "; + } + + out << "(this." << generate_isset_check(*f_iter) << ") {" << endl; + + indent_up(); + + indent(out) << "oprot.writeFieldBegin(" << constant_name((*f_iter)->get_name()) + << "_FIELD_DESC);" << endl; + + // Write field contents + generate_serialize_field(out, *f_iter, "this."); + + // Write field closer + indent(out) << "oprot.writeFieldEnd();" << endl; + + indent_down(); + indent(out) << "}"; + } + // Write the struct map + out << endl << indent() << "oprot.writeFieldStop();" << endl << indent() + << "oprot.writeStructEnd();" << endl; + + indent_down(); + out << indent() << "}" << endl << endl; +} + +void t_javame_generator::generate_reflection_getters(ostringstream& out, + t_type* type, + string field_name, + string cap_name) { + indent(out) << "case " << constant_name(field_name) << ":" << endl; + indent_up(); + + if (type->is_base_type() && !type->is_string()) { + t_base_type* base_type = (t_base_type*)type; + + indent(out) << "return new " << type_name(type, true, false) << "(" + << (base_type->is_bool() ? "is" : "get") << cap_name << "());" << endl << endl; + } else { + indent(out) << "return get" << cap_name << "();" << endl << endl; + } + + indent_down(); +} + +void t_javame_generator::generate_reflection_setters(ostringstream& out, + t_type* type, + string field_name, + string cap_name) { + indent(out) << "case " << constant_name(field_name) << ":" << endl; + indent_up(); + indent(out) << "if (value == null) {" << endl; + indent(out) << " unset" << get_cap_name(field_name) << "();" << endl; + indent(out) << "} else {" << endl; + indent(out) << " set" << cap_name << "((" << type_name(type, true, false) << ")value);" << endl; + indent(out) << "}" << endl; + indent(out) << "break;" << endl << endl; + + indent_down(); +} + +void t_javame_generator::generate_generic_field_getters_setters(std::ostream& out, + t_struct* tstruct) { + (void)out; + std::ostringstream getter_stream; + std::ostringstream setter_stream; + + // build up the bodies of both the getter and setter at once + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = *f_iter; + t_type* type = get_true_type(field->get_type()); + std::string field_name = field->get_name(); + std::string cap_name = get_cap_name(field_name); + + indent_up(); + generate_reflection_setters(setter_stream, type, field_name, cap_name); + generate_reflection_getters(getter_stream, type, field_name, cap_name); + indent_down(); + } +} + +/** + * Generates a set of Java Bean boilerplate functions (setters, getters, etc.) + * for the given struct. + * + * @param tstruct The struct definition + */ +void t_javame_generator::generate_java_bean_boilerplate(ostream& out, t_struct* tstruct) { + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = *f_iter; + t_type* type = get_true_type(field->get_type()); + std::string field_name = field->get_name(); + std::string cap_name = get_cap_name(field_name); + + if (type->is_container()) { + // Method to return the size of the collection + indent(out) << "public int get" << cap_name; + out << get_cap_name("size() {") << endl; + + indent_up(); + indent(out) << "return (this." << field_name << " == null) ? 0 : " + << "this." << field_name << ".size();" << endl; + indent_down(); + indent(out) << "}" << endl << endl; + } + + if (type->is_set() || type->is_list()) { + + t_type* element_type; + if (type->is_set()) { + element_type = ((t_set*)type)->get_elem_type(); + } else { + element_type = ((t_list*)type)->get_elem_type(); + } + + // Iterator getter for sets and lists + indent(out) << "public Enumeration get" << cap_name; + out << get_cap_name("Enumeration() {") << endl; + + indent_up(); + indent(out) << "return (this." << field_name << " == null) ? null : " + << "this." << field_name << ".elements();" << endl; + indent_down(); + indent(out) << "}" << endl << endl; + + // Add to set or list, create if the set/list is null + indent(out); + out << "public void add" << get_cap_name("to"); + out << cap_name << "(" << type_name(element_type) << " elem) {" << endl; + + indent_up(); + indent(out) << "if (this." << field_name << " == null) {" << endl; + indent_up(); + indent(out) << "this." << field_name << " = new " << type_name(type, false, true) << "();" + << endl; + indent_down(); + indent(out) << "}" << endl; + if (type->is_set()) { + indent(out) << "this." << field_name << ".put(" << box_type(element_type, "elem") << ", " + << box_type(element_type, "elem") << ");" << endl; + } else { + indent(out) << "this." << field_name << ".addElement(" << box_type(element_type, "elem") + << ");" << endl; + } + indent_down(); + indent(out) << "}" << endl << endl; + + } else if (type->is_map()) { + // Put to map + t_type* key_type = ((t_map*)type)->get_key_type(); + t_type* val_type = ((t_map*)type)->get_val_type(); + + indent(out); + out << "public void putTo" << cap_name << "(" << type_name(key_type, true) << " key, " + << type_name(val_type, true) << " val) {" << endl; + + indent_up(); + indent(out) << "if (this." << field_name << " == null) {" << endl; + indent_up(); + indent(out) << "this." << field_name << " = new " << type_name(type, false, true) << "();" + << endl; + indent_down(); + indent(out) << "}" << endl; + indent(out) << "this." << field_name << ".put(key, val);" << endl; + indent_down(); + indent(out) << "}" << endl << endl; + } + + // Simple getter + generate_java_doc(out, field); + indent(out) << "public " << type_name(type); + if (type->is_base_type() && ((t_base_type*)type)->get_base() == t_base_type::TYPE_BOOL) { + out << " is"; + } else { + out << " get"; + } + out << cap_name << "() {" << endl; + indent_up(); + indent(out) << "return this." << field_name << ";" << endl; + indent_down(); + indent(out) << "}" << endl << endl; + + // Simple setter + generate_java_doc(out, field); + indent(out) << "public "; + out << "void"; + out << " set" << cap_name << "(" << type_name(type) << " " << field_name << ") {" << endl; + indent_up(); + indent(out) << "this." << field_name << " = " << field_name << ";" << endl; + generate_isset_set(out, field); + + indent_down(); + indent(out) << "}" << endl << endl; + + // Unsetter + indent(out) << "public void unset" << cap_name << "() {" << endl; + indent_up(); + if (type_can_be_null(type)) { + indent(out) << "this." << field_name << " = null;" << endl; + } else { + indent(out) << "__isset_vector[" << isset_field_id(field) << "] = false;" << endl; + } + indent_down(); + indent(out) << "}" << endl << endl; + + // isSet method + indent(out) << "/** Returns true if field " << field_name + << " is set (has been assigned a value) and false otherwise */" << endl; + indent(out) << "public boolean is" << get_cap_name("set") << cap_name << "() {" << endl; + indent_up(); + if (type_can_be_null(type)) { + indent(out) << "return this." << field_name << " != null;" << endl; + } else { + indent(out) << "return __isset_vector[" << isset_field_id(field) << "];" << endl; + } + indent_down(); + indent(out) << "}" << endl << endl; + + indent(out) << "public void set" << cap_name << get_cap_name("isSet") << "(boolean value) {" + << endl; + indent_up(); + if (type_can_be_null(type)) { + indent(out) << "if (!value) {" << endl; + indent(out) << " this." << field_name << " = null;" << endl; + indent(out) << "}" << endl; + } else { + indent(out) << "__isset_vector[" << isset_field_id(field) << "] = value;" << endl; + } + indent_down(); + indent(out) << "}" << endl << endl; + } +} + +/** + * Generates a toString() method for the given struct + * + * @param tstruct The struct definition + */ +void t_javame_generator::generate_java_struct_tostring(ostream& out, t_struct* tstruct) { + out << indent() << "public String toString() {" << endl; + indent_up(); + + out << indent() << "StringBuffer sb = new StringBuffer(\"" << tstruct->get_name() << "(\");" + << endl; + out << indent() << "boolean first = true;" << endl << endl; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + bool could_be_unset = (*f_iter)->get_req() == t_field::T_OPTIONAL; + if (could_be_unset) { + indent(out) << "if (" << generate_isset_check(*f_iter) << ") {" << endl; + indent_up(); + } + + t_field* field = (*f_iter); + + if (!first) { + indent(out) << "if (!first) sb.append(\", \");" << endl; + } + indent(out) << "sb.append(\"" << (*f_iter)->get_name() << ":\");" << endl; + bool can_be_null = type_can_be_null(field->get_type()); + if (can_be_null) { + indent(out) << "if (this." << (*f_iter)->get_name() << " == null) {" << endl; + indent(out) << " sb.append(\"null\");" << endl; + indent(out) << "} else {" << endl; + indent_up(); + } + + if (field->get_type()->is_binary()) { + indent(out) << "TBaseHelper.toString(this." << field->get_name() << ", sb);" << endl; + } else { + indent(out) << "sb.append(this." << (*f_iter)->get_name() << ");" << endl; + } + + if (can_be_null) { + indent_down(); + indent(out) << "}" << endl; + } + indent(out) << "first = false;" << endl; + + if (could_be_unset) { + indent_down(); + indent(out) << "}" << endl; + } + first = false; + } + out << indent() << "sb.append(\")\");" << endl << indent() << "return sb.toString();" << endl; + + indent_down(); + indent(out) << "}" << endl << endl; +} + +/** + * Returns a string with the java representation of the given thrift type + * (e.g. for the type struct it returns "TType.STRUCT") + */ +std::string t_javame_generator::get_java_type_string(t_type* type) { + if (type->is_list()) { + return "TType.LIST"; + } else if (type->is_map()) { + return "TType.MAP"; + } else if (type->is_set()) { + return "TType.SET"; + } else if (type->is_struct() || type->is_xception()) { + return "TType.STRUCT"; + } else if (type->is_enum()) { + return "TType.ENUM"; + } else if (type->is_typedef()) { + return get_java_type_string(((t_typedef*)type)->get_type()); + } else if (type->is_base_type()) { + switch (((t_base_type*)type)->get_base()) { + case t_base_type::TYPE_VOID: + return "TType.VOID"; + break; + case t_base_type::TYPE_STRING: + return "TType.STRING"; + break; + case t_base_type::TYPE_BOOL: + return "TType.BOOL"; + break; + case t_base_type::TYPE_I8: + return "TType.BYTE"; + break; + case t_base_type::TYPE_I16: + return "TType.I16"; + break; + case t_base_type::TYPE_I32: + return "TType.I32"; + break; + case t_base_type::TYPE_I64: + return "TType.I64"; + break; + case t_base_type::TYPE_DOUBLE: + return "TType.DOUBLE"; + break; + default: + throw std::runtime_error("Unknown thrift type \"" + type->get_name() + + "\" passed to t_javame_generator::get_java_type_string!"); + break; // This should never happen! + } + } else { + throw std::runtime_error( + "Unknown thrift type \"" + type->get_name() + + "\" passed to t_javame_generator::get_java_type_string!"); // This should never happen! + } +} + +void t_javame_generator::generate_field_value_meta_data(std::ostream& out, t_type* type) { + out << endl; + indent_up(); + indent_up(); + if (type->is_struct() || type->is_xception()) { + indent(out) << "new StructMetaData(TType.STRUCT, " << type_name(type) << ".class"; + } else if (type->is_container()) { + if (type->is_list()) { + indent(out) << "new ListMetaData(TType.LIST, "; + t_type* elem_type = ((t_list*)type)->get_elem_type(); + generate_field_value_meta_data(out, elem_type); + } else if (type->is_set()) { + indent(out) << "new SetMetaData(TType.SET, "; + t_type* elem_type = ((t_list*)type)->get_elem_type(); + generate_field_value_meta_data(out, elem_type); + } else { // map + indent(out) << "new MapMetaData(TType.MAP, "; + t_type* key_type = ((t_map*)type)->get_key_type(); + t_type* val_type = ((t_map*)type)->get_val_type(); + generate_field_value_meta_data(out, key_type); + out << ", "; + generate_field_value_meta_data(out, val_type); + } + } else if (type->is_enum()) { + indent(out) << "new EnumMetaData(TType.ENUM, " << type_name(type) << ".class"; + } else { + indent(out) << "new FieldValueMetaData(" << get_java_type_string(type); + if (type->is_typedef()) { + indent(out) << ", \"" << ((t_typedef*)type)->get_symbolic() << "\""; + } + } + out << ")"; + indent_down(); + indent_down(); +} + +/** + * Generates a thrift service. In C++, this comprises an entirely separate + * header and source file. The header file defines the methods and includes + * the data types defined in the main header file, and the implementation + * file contains implementations of the basic printer and default interfaces. + * + * @param tservice The service definition + */ +void t_javame_generator::generate_service(t_service* tservice) { + // Make output file + string f_service_name = package_dir_ + "/" + service_name_ + ".java"; + f_service_.open(f_service_name.c_str()); + + f_service_ << autogen_comment() << java_package() << java_type_imports() << java_thrift_imports(); + + f_service_ << "public class " << service_name_ << " {" << endl << endl; + indent_up(); + + // Generate the three main parts of the service + generate_service_interface(tservice); + generate_service_client(tservice); + generate_service_server(tservice); + generate_service_helpers(tservice); + + indent_down(); + f_service_ << "}" << endl; + f_service_.close(); +} + +/** + * Generates a service interface definition. + * + * @param tservice The service to generate a header definition for + */ +void t_javame_generator::generate_primitive_service_interface(t_service* tservice) { + f_service_ << indent() << "public interface Iface extends " << service_name_ << "Iface { }" + << endl << endl; + + string f_interface_name = package_dir_ + "/" + service_name_ + "Iface.java"; + ofstream_with_content_based_conditional_update f_iface; + f_iface.open(f_interface_name.c_str()); + + string extends_iface = ""; + if (tservice->get_extends() != NULL) { + extends_iface = " extends " + type_name(tservice->get_extends()) + "Iface"; + } + + f_iface << autogen_comment() << java_package() << java_type_imports() << java_thrift_imports(); + generate_java_doc(f_iface, tservice); + f_iface << "public interface " << service_name_ << "Iface" << extends_iface << " {" << endl + << endl; + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_java_doc(f_iface, *f_iter); + f_iface << " public " << function_signature(*f_iter) << ";" << endl << endl; + } + f_iface << "}" << endl << endl; +} + +/** + * Generates a service interface definition. + * + * @param tservice The service to generate a header definition for + */ +void t_javame_generator::generate_service_interface(t_service* tservice) { + string extends = ""; + string extends_iface = ""; + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + extends_iface = " extends " + extends + ".Iface"; + } + + generate_java_doc(f_service_, tservice); + f_service_ << indent() << "public interface Iface" << extends_iface << " {" << endl << endl; + indent_up(); + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_java_doc(f_service_, *f_iter); + indent(f_service_) << "public " << function_signature(*f_iter) << ";" << endl << endl; + } + indent_down(); + f_service_ << indent() << "}" << endl << endl; +} + +/** + * Generates structs for all the service args and return types + * + * @param tservice The service + */ +void t_javame_generator::generate_service_helpers(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* ts = (*f_iter)->get_arglist(); + generate_java_struct_definition(f_service_, ts, false, true); + generate_function_helpers(*f_iter); + } +} + +/** + * Generates a service client definition. + * + * @param tservice The service to generate a server for. + */ +void t_javame_generator::generate_service_client(t_service* tservice) { + string extends = ""; + string extends_client = ""; + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + extends_client = " extends " + extends + ".Client"; + } + + indent(f_service_) << "public static class Client" << extends_client + << " implements TServiceClient, Iface {" << endl; + indent_up(); + + indent(f_service_) << "public Client(TProtocol prot)" << endl; + scope_up(f_service_); + indent(f_service_) << "this(prot, prot);" << endl; + scope_down(f_service_); + f_service_ << endl; + + indent(f_service_) << "public Client(TProtocol iprot, TProtocol oprot)" << endl; + scope_up(f_service_); + if (extends.empty()) { + f_service_ << indent() << "iprot_ = iprot;" << endl << indent() << "oprot_ = oprot;" << endl; + } else { + f_service_ << indent() << "super(iprot, oprot);" << endl; + } + scope_down(f_service_); + f_service_ << endl; + + if (extends.empty()) { + f_service_ << indent() << "protected TProtocol iprot_;" << endl << indent() + << "protected TProtocol oprot_;" << endl << endl << indent() + << "protected int seqid_;" << endl << endl; + + indent(f_service_) << "public TProtocol getInputProtocol()" << endl; + scope_up(f_service_); + indent(f_service_) << "return this.iprot_;" << endl; + scope_down(f_service_); + f_service_ << endl; + + indent(f_service_) << "public TProtocol getOutputProtocol()" << endl; + scope_up(f_service_); + indent(f_service_) << "return this.oprot_;" << endl; + scope_down(f_service_); + f_service_ << endl; + } + + // Generate client method implementations + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + string funname = (*f_iter)->get_name(); + + // Open function + indent(f_service_) << "public " << function_signature(*f_iter) << endl; + scope_up(f_service_); + indent(f_service_) << "send_" << funname << "("; + + // Get the struct of function call params + t_struct* arg_struct = (*f_iter)->get_arglist(); + + // Declare the function arguments + const vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator fld_iter; + bool first = true; + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << (*fld_iter)->get_name(); + } + f_service_ << ");" << endl; + + if (!(*f_iter)->is_oneway()) { + f_service_ << indent(); + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_ << "return "; + } + f_service_ << "recv_" << funname << "();" << endl; + } + scope_down(f_service_); + f_service_ << endl; + + t_function send_function(g_type_void, + string("send_") + (*f_iter)->get_name(), + (*f_iter)->get_arglist()); + + string argsname = (*f_iter)->get_name() + "_args"; + + // Open function + indent(f_service_) << "public " << function_signature(&send_function) << endl; + scope_up(f_service_); + + // Serialize the request + f_service_ << indent() << "oprot_.writeMessageBegin(new TMessage(\"" << funname << "\", " + << ((*f_iter)->is_oneway() ? "TMessageType.ONEWAY" : "TMessageType.CALL") + << ", ++seqid_));" << endl << indent() << argsname << " args = new " << argsname + << "();" << endl; + + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + f_service_ << indent() << "args.set" << get_cap_name((*fld_iter)->get_name()) << "(" + << (*fld_iter)->get_name() << ");" << endl; + } + + f_service_ << indent() << "args.write(oprot_);" << endl << indent() + << "oprot_.writeMessageEnd();" << endl << indent() + << "oprot_.getTransport().flush();" << endl; + + scope_down(f_service_); + f_service_ << endl; + + if (!(*f_iter)->is_oneway()) { + string resultname = (*f_iter)->get_name() + "_result"; + + t_struct noargs(program_); + t_function recv_function((*f_iter)->get_returntype(), + string("recv_") + (*f_iter)->get_name(), + &noargs, + (*f_iter)->get_xceptions()); + // Open function + indent(f_service_) << "public " << function_signature(&recv_function) << endl; + scope_up(f_service_); + + f_service_ << indent() << "TMessage msg = iprot_.readMessageBegin();" << endl << indent() + << "if (msg.type == TMessageType.EXCEPTION) {" << endl << indent() + << " TApplicationException x = TApplicationException.read(iprot_);" << endl + << indent() << " iprot_.readMessageEnd();" << endl << indent() << " throw x;" + << endl << indent() << "}" << endl << indent() << "if (msg.seqid != seqid_) {" + << endl << indent() + << " throw new TApplicationException(TApplicationException.BAD_SEQUENCE_ID, \"" + << (*f_iter)->get_name() << " failed: out of sequence response\");" << endl + << indent() << "}" << endl << indent() << resultname << " result = new " + << resultname << "();" << endl << indent() << "result.read(iprot_);" << endl + << indent() << "iprot_.readMessageEnd();" << endl; + + // Careful, only return _result if not a void function + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_ << indent() << "if (result." << generate_isset_check("success") << ") {" << endl + << indent() << " return result.success;" << endl << indent() << "}" << endl; + } + + t_struct* xs = (*f_iter)->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + f_service_ << indent() << "if (result." << (*x_iter)->get_name() << " != null) {" << endl + << indent() << " throw result." << (*x_iter)->get_name() << ";" << endl + << indent() << "}" << endl; + } + + // If you get here it's an exception, unless a void function + if ((*f_iter)->get_returntype()->is_void()) { + indent(f_service_) << "return;" << endl; + } else { + f_service_ << indent() + << "throw new TApplicationException(TApplicationException.MISSING_RESULT, \"" + << (*f_iter)->get_name() << " failed: unknown result\");" << endl; + } + + // Close function + scope_down(f_service_); + f_service_ << endl; + } + } + + indent_down(); + indent(f_service_) << "}" << endl; +} + +/** + * Generates a service server definition. + * + * @param tservice The service to generate a server for. + */ +void t_javame_generator::generate_service_server(t_service* tservice) { + // Generate the dispatch methods + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + // Extends stuff + string extends = ""; + string extends_processor = ""; + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + extends_processor = " extends " + extends + ".Processor"; + } + + // Generate the header portion + indent(f_service_) << "public static class Processor" << extends_processor + << " implements TProcessor {" << endl; + indent_up(); + + indent(f_service_) << "public Processor(Iface iface)" << endl; + scope_up(f_service_); + if (!extends.empty()) { + f_service_ << indent() << "super(iface);" << endl; + } + f_service_ << indent() << "iface_ = iface;" << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + f_service_ << indent() << "processMap_.put(\"" << (*f_iter)->get_name() << "\", new " + << (*f_iter)->get_name() << "());" << endl; + } + + scope_down(f_service_); + f_service_ << endl; + + if (extends.empty()) { + f_service_ + << indent() << "protected static interface ProcessFunction {" << endl << indent() + << " public void process(int seqid, TProtocol iprot, TProtocol oprot) throws TException;" + << endl << indent() << "}" << endl << endl; + } + + f_service_ << indent() << "private Iface iface_;" << endl; + + if (extends.empty()) { + f_service_ << indent() << "protected final Hashtable processMap_ = new Hashtable();" << endl; + } + + f_service_ << endl; + + // Generate the server implementation + indent(f_service_) << "public boolean process(TProtocol iprot, TProtocol oprot) throws TException" + << endl; + scope_up(f_service_); + + f_service_ << indent() << "TMessage msg = iprot.readMessageBegin();" << endl; + + // TODO(mcslee): validate message, was the seqid etc. legit? + + f_service_ + << indent() << "ProcessFunction fn = (ProcessFunction)processMap_.get(msg.name);" << endl + << indent() << "if (fn == null) {" << endl << indent() + << " TProtocolUtil.skip(iprot, TType.STRUCT);" << endl << indent() + << " iprot.readMessageEnd();" << endl << indent() + << " TApplicationException x = new " + "TApplicationException(TApplicationException.UNKNOWN_METHOD, \"Invalid method name: " + "'\"+msg.name+\"'\");" << endl << indent() + << " oprot.writeMessageBegin(new TMessage(msg.name, TMessageType.EXCEPTION, msg.seqid));" + << endl << indent() << " x.write(oprot);" << endl << indent() << " oprot.writeMessageEnd();" + << endl << indent() << " oprot.getTransport().flush();" << endl << indent() + << " return true;" << endl << indent() << "}" << endl << indent() + << "fn.process(msg.seqid, iprot, oprot);" << endl; + + f_service_ << indent() << "return true;" << endl; + + scope_down(f_service_); + f_service_ << endl; + + // Generate the process subfunctions + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_process_function(tservice, *f_iter); + } + + indent_down(); + indent(f_service_) << "}" << endl << endl; +} + +/** + * Generates a struct and helpers for a function. + * + * @param tfunction The function + */ +void t_javame_generator::generate_function_helpers(t_function* tfunction) { + if (tfunction->is_oneway()) { + return; + } + + t_struct result(program_, tfunction->get_name() + "_result"); + t_field success(tfunction->get_returntype(), "success", 0); + if (!tfunction->get_returntype()->is_void()) { + result.append(&success); + } + + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& fields = xs->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + result.append(*f_iter); + } + + generate_java_struct_definition(f_service_, &result, false, true, true); +} + +/** + * Generates a process function definition. + * + * @param tfunction The function to write a dispatcher for + */ +void t_javame_generator::generate_process_function(t_service* tservice, t_function* tfunction) { + (void)tservice; + // Open class + indent(f_service_) << "private class " << tfunction->get_name() << " implements ProcessFunction {" + << endl; + indent_up(); + + // Open function + indent(f_service_) + << "public void process(int seqid, TProtocol iprot, TProtocol oprot) throws TException" + << endl; + scope_up(f_service_); + + string argsname = tfunction->get_name() + "_args"; + string resultname = tfunction->get_name() + "_result"; + + f_service_ << indent() << argsname << " args = new " << argsname << "();" << endl << indent() + << "try {" << endl; + indent_up(); + f_service_ << indent() << "args.read(iprot);" << endl; + indent_down(); + f_service_ << indent() << "} catch (TProtocolException e) {" << endl; + indent_up(); + f_service_ << indent() << "iprot.readMessageEnd();" << endl << indent() + << "TApplicationException x = new " + "TApplicationException(TApplicationException.PROTOCOL_ERROR, e.getMessage());" + << endl << indent() << "oprot.writeMessageBegin(new TMessage(\"" + << tfunction->get_name() << "\", TMessageType.EXCEPTION, seqid));" << endl << indent() + << "x.write(oprot);" << endl << indent() << "oprot.writeMessageEnd();" << endl + << indent() << "oprot.getTransport().flush();" << endl << indent() << "return;" + << endl; + indent_down(); + f_service_ << indent() << "}" << endl; + f_service_ << indent() << "iprot.readMessageEnd();" << endl; + + t_struct* xs = tfunction->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + + // Declare result for non oneway function + if (!tfunction->is_oneway()) { + f_service_ << indent() << resultname << " result = new " << resultname << "();" << endl; + } + + // Try block for a function with exceptions + if (xceptions.size() > 0) { + f_service_ << indent() << "try {" << endl; + indent_up(); + } + + // Generate the function call + t_struct* arg_struct = tfunction->get_arglist(); + const std::vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + + f_service_ << indent(); + if (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void()) { + f_service_ << "result.success = "; + } + f_service_ << "iface_." << tfunction->get_name() << "("; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << "args." << (*f_iter)->get_name(); + } + f_service_ << ");" << endl; + + // Set isset on success field + if (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void() + && !type_can_be_null(tfunction->get_returntype())) { + f_service_ << indent() << "result.set" << get_cap_name("success") << get_cap_name("isSet") + << "(true);" << endl; + } + + if (!tfunction->is_oneway() && xceptions.size() > 0) { + indent_down(); + f_service_ << indent() << "}"; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + f_service_ << " catch (" << type_name((*x_iter)->get_type(), false, false) << " " + << (*x_iter)->get_name() << ") {" << endl; + if (!tfunction->is_oneway()) { + indent_up(); + f_service_ << indent() << "result." << (*x_iter)->get_name() << " = " + << (*x_iter)->get_name() << ";" << endl; + indent_down(); + f_service_ << indent() << "}"; + } else { + f_service_ << "}"; + } + } + f_service_ << " catch (Throwable th) {" << endl; + indent_up(); + f_service_ << indent() << "TApplicationException x = new " + "TApplicationException(TApplicationException.INTERNAL_ERROR, " + "\"Internal error processing " << tfunction->get_name() << "\");" + << endl << indent() << "oprot.writeMessageBegin(new TMessage(\"" + << tfunction->get_name() << "\", TMessageType.EXCEPTION, seqid));" << endl + << indent() << "x.write(oprot);" << endl << indent() << "oprot.writeMessageEnd();" + << endl << indent() << "oprot.getTransport().flush();" << endl << indent() + << "return;" << endl; + indent_down(); + f_service_ << indent() << "}" << endl; + } + + // Shortcut out here for oneway functions + if (tfunction->is_oneway()) { + f_service_ << indent() << "return;" << endl; + scope_down(f_service_); + + // Close class + indent_down(); + f_service_ << indent() << "}" << endl << endl; + return; + } + + f_service_ << indent() << "oprot.writeMessageBegin(new TMessage(\"" << tfunction->get_name() + << "\", TMessageType.REPLY, seqid));" << endl << indent() << "result.write(oprot);" + << endl << indent() << "oprot.writeMessageEnd();" << endl << indent() + << "oprot.getTransport().flush();" << endl; + + // Close function + scope_down(f_service_); + f_service_ << endl; + + // Close class + indent_down(); + f_service_ << indent() << "}" << endl << endl; +} + +/** + * Deserializes a field of any type. + * + * @param tfield The field + * @param prefix The variable name or container for this field + */ +void t_javame_generator::generate_deserialize_field(ostream& out, t_field* tfield, string prefix) { + t_type* type = get_true_type(tfield->get_type()); + + if (type->is_void()) { + throw "CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name(); + } + + string name = prefix + tfield->get_name(); + + if (type->is_struct() || type->is_xception()) { + generate_deserialize_struct(out, (t_struct*)type, name); + } else if (type->is_container()) { + generate_deserialize_container(out, type, name); + } else if (type->is_base_type()) { + indent(out) << name << " = iprot."; + + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + if (!type->is_binary()) { + out << "readString();"; + } else { + out << "readBinary();"; + } + break; + case t_base_type::TYPE_BOOL: + out << "readBool();"; + break; + case t_base_type::TYPE_I8: + out << "readByte();"; + break; + case t_base_type::TYPE_I16: + out << "readI16();"; + break; + case t_base_type::TYPE_I32: + out << "readI32();"; + break; + case t_base_type::TYPE_I64: + out << "readI64();"; + break; + case t_base_type::TYPE_DOUBLE: + out << "readDouble();"; + break; + default: + throw "compiler error: no Java name for base type " + t_base_type::t_base_name(tbase); + } + out << endl; + } else if (type->is_enum()) { + indent(out) << name << " = " + << type_name(tfield->get_type(), true, false) + ".findByValue(iprot.readI32());" + << endl; + } else { + printf("DO NOT KNOW HOW TO DESERIALIZE FIELD '%s' TYPE '%s'\n", + tfield->get_name().c_str(), + type_name(type).c_str()); + } +} + +/** + * Generates an unserializer for a struct, invokes read() + */ +void t_javame_generator::generate_deserialize_struct(ostream& out, + t_struct* tstruct, + string prefix) { + out << indent() << prefix << " = new " << type_name(tstruct) << "();" << endl << indent() + << prefix << ".read(iprot);" << endl; +} + +/** + * Deserializes a container by reading its size and then iterating + */ +void t_javame_generator::generate_deserialize_container(ostream& out, + t_type* ttype, + string prefix) { + scope_up(out); + + string obj; + + if (ttype->is_map()) { + obj = tmp("_map"); + } else if (ttype->is_set()) { + obj = tmp("_set"); + } else if (ttype->is_list()) { + obj = tmp("_list"); + } + + // Declare variables, read header + if (ttype->is_map()) { + indent(out) << "TMap " << obj << " = iprot.readMapBegin();" << endl; + } else if (ttype->is_set()) { + indent(out) << "TSet " << obj << " = iprot.readSetBegin();" << endl; + } else if (ttype->is_list()) { + indent(out) << "TList " << obj << " = iprot.readListBegin();" << endl; + } + + indent(out) << prefix << " = new " << type_name(ttype, false, true) + // size the collection correctly + << "(" << (ttype->is_list() ? "" : "2*") << obj << ".size" + << ");" << endl; + + // For loop iterates over elements + string i = tmp("_i"); + indent(out) << "for (int " << i << " = 0; " << i << " < " << obj << ".size" + << "; " + << "++" << i << ")" << endl; + + scope_up(out); + + if (ttype->is_map()) { + generate_deserialize_map_element(out, (t_map*)ttype, prefix); + } else if (ttype->is_set()) { + generate_deserialize_set_element(out, (t_set*)ttype, prefix); + } else if (ttype->is_list()) { + generate_deserialize_list_element(out, (t_list*)ttype, prefix); + } + + scope_down(out); + + // Read container end + if (ttype->is_map()) { + indent(out) << "iprot.readMapEnd();" << endl; + } else if (ttype->is_set()) { + indent(out) << "iprot.readSetEnd();" << endl; + } else if (ttype->is_list()) { + indent(out) << "iprot.readListEnd();" << endl; + } + + scope_down(out); +} + +/** + * Generates code to deserialize a map + */ +void t_javame_generator::generate_deserialize_map_element(ostream& out, + t_map* tmap, + string prefix) { + string key = tmp("_key"); + string val = tmp("_val"); + t_field fkey(tmap->get_key_type(), key); + t_field fval(tmap->get_val_type(), val); + + indent(out) << declare_field(&fkey) << endl; + indent(out) << declare_field(&fval) << endl; + + generate_deserialize_field(out, &fkey); + generate_deserialize_field(out, &fval); + + indent(out) << prefix << ".put(" << box_type(tmap->get_key_type(), key) << ", " + << box_type(tmap->get_val_type(), val) << ");" << endl; +} + +/** + * Deserializes a set element + */ +void t_javame_generator::generate_deserialize_set_element(ostream& out, + t_set* tset, + string prefix) { + string elem = tmp("_elem"); + t_field felem(tset->get_elem_type(), elem); + + indent(out) << declare_field(&felem) << endl; + + generate_deserialize_field(out, &felem); + + indent(out) << prefix << ".put(" << box_type(tset->get_elem_type(), elem) << ", " + << box_type(tset->get_elem_type(), elem) << ");" << endl; +} + +/** + * Deserializes a list element + */ +void t_javame_generator::generate_deserialize_list_element(ostream& out, + t_list* tlist, + string prefix) { + string elem = tmp("_elem"); + t_field felem(tlist->get_elem_type(), elem); + + indent(out) << declare_field(&felem) << endl; + + generate_deserialize_field(out, &felem); + + indent(out) << prefix << ".addElement(" << box_type(tlist->get_elem_type(), elem) << ");" << endl; +} + +/** + * Serializes a field of any type. + * + * @param tfield The field to serialize + * @param prefix Name to prepend to field name + */ +void t_javame_generator::generate_serialize_field(ostream& out, t_field* tfield, string prefix) { + t_type* type = get_true_type(tfield->get_type()); + + // Do nothing for void types + if (type->is_void()) { + throw "CANNOT GENERATE SERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name(); + } + + if (type->is_struct() || type->is_xception()) { + generate_serialize_struct(out, (t_struct*)type, prefix + tfield->get_name()); + } else if (type->is_container()) { + generate_serialize_container(out, type, prefix + tfield->get_name()); + } else if (type->is_enum()) { + indent(out) << "oprot.writeI32(" << prefix + tfield->get_name() << ".getValue());" << endl; + } else if (type->is_base_type()) { + string name = prefix + tfield->get_name(); + indent(out) << "oprot."; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + out << "writeBinary(" << name << ");"; + } else { + out << "writeString(" << name << ");"; + } + break; + case t_base_type::TYPE_BOOL: + out << "writeBool(" << name << ");"; + break; + case t_base_type::TYPE_I8: + out << "writeByte(" << name << ");"; + break; + case t_base_type::TYPE_I16: + out << "writeI16(" << name << ");"; + break; + case t_base_type::TYPE_I32: + out << "writeI32(" << name << ");"; + break; + case t_base_type::TYPE_I64: + out << "writeI64(" << name << ");"; + break; + case t_base_type::TYPE_DOUBLE: + out << "writeDouble(" << name << ");"; + break; + default: + throw "compiler error: no Java name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "writeI32(" << name << ");"; + } + out << endl; + } else { + printf("DO NOT KNOW HOW TO SERIALIZE FIELD '%s%s' TYPE '%s'\n", + prefix.c_str(), + tfield->get_name().c_str(), + type_name(type).c_str()); + } +} + +/** + * Serializes all the members of a struct. + * + * @param tstruct The struct to serialize + * @param prefix String prefix to attach to all fields + */ +void t_javame_generator::generate_serialize_struct(ostream& out, + t_struct* tstruct, + string prefix) { + (void)tstruct; + out << indent() << prefix << ".write(oprot);" << endl; +} + +/** + * Serializes a container by writing its size then the elements. + * + * @param ttype The type of container + * @param prefix String prefix for fields + */ +void t_javame_generator::generate_serialize_container(ostream& out, t_type* ttype, string prefix) { + scope_up(out); + + if (ttype->is_map()) { + indent(out) << "oprot.writeMapBegin(new TMap(" << type_to_enum(((t_map*)ttype)->get_key_type()) + << ", " << type_to_enum(((t_map*)ttype)->get_val_type()) << ", " << prefix + << ".size()));" << endl; + } else if (ttype->is_set()) { + indent(out) << "oprot.writeSetBegin(new TSet(" << type_to_enum(((t_set*)ttype)->get_elem_type()) + << ", " << prefix << ".size()));" << endl; + } else if (ttype->is_list()) { + indent(out) << "oprot.writeListBegin(new TList(" + << type_to_enum(((t_list*)ttype)->get_elem_type()) << ", " << prefix << ".size()));" + << endl; + } + + string iter = tmp("_iter"); + if (ttype->is_map()) { + string enumer = iter + "_enum"; + string key_type = type_name(((t_map*)ttype)->get_key_type(), true, false); + indent(out) << "for (Enumeration " << enumer << " = " << prefix << ".keys(); " << enumer + << ".hasMoreElements(); ) "; + scope_up(out); + indent(out) << key_type << " " << iter << " = (" << key_type << ")" << enumer + << ".nextElement();" << endl; + } else if (ttype->is_set()) { + string enumer = iter + "_enum"; + string ele_type = type_name(((t_list*)ttype)->get_elem_type(), true); + indent(out) << "for (Enumeration " << enumer << " = " << prefix << ".keys(); " << enumer + << ".hasMoreElements(); ) "; + scope_up(out); + indent(out) << ele_type << " " << iter << " = (" << ele_type << ")" << enumer + << ".nextElement();" << endl; + } else if (ttype->is_list()) { + string enumer = iter + "_enum"; + indent(out) << "for (Enumeration " << enumer << " = " << prefix << ".elements(); " << enumer + << ".hasMoreElements(); ) "; + scope_up(out); + string ele_type = type_name(((t_list*)ttype)->get_elem_type(), true); + indent(out) << ele_type << " " << iter << " = (" << ele_type << ")" << enumer + << ".nextElement();" << endl; + } + + if (ttype->is_map()) { + generate_serialize_map_element(out, (t_map*)ttype, iter, prefix); + } else if (ttype->is_set()) { + generate_serialize_set_element(out, (t_set*)ttype, iter); + } else if (ttype->is_list()) { + generate_serialize_list_element(out, (t_list*)ttype, iter); + } + scope_down(out); + + if (ttype->is_map()) { + indent(out) << "oprot.writeMapEnd();" << endl; + } else if (ttype->is_set()) { + indent(out) << "oprot.writeSetEnd();" << endl; + } else if (ttype->is_list()) { + indent(out) << "oprot.writeListEnd();" << endl; + } + + scope_down(out); +} + +/** + * Serializes the members of a map. + */ +void t_javame_generator::generate_serialize_map_element(ostream& out, + t_map* tmap, + string iter, + string map) { + t_field kfield(tmap->get_key_type(), iter); + generate_serialize_field(out, &kfield, ""); + string val_type = type_name(tmap->get_val_type(), true, false); + t_field vfield(tmap->get_val_type(), "((" + val_type + ")" + map + ".get(" + iter + "))"); + generate_serialize_field(out, &vfield, ""); +} + +/** + * Serializes the members of a set. + */ +void t_javame_generator::generate_serialize_set_element(ostream& out, t_set* tset, string iter) { + t_field efield(tset->get_elem_type(), iter); + generate_serialize_field(out, &efield, ""); +} + +/** + * Serializes the members of a list. + */ +void t_javame_generator::generate_serialize_list_element(ostream& out, + t_list* tlist, + string iter) { + t_field efield(tlist->get_elem_type(), iter); + generate_serialize_field(out, &efield, ""); +} + +/** + * Returns a Java type name + * + * @param ttype The type + * @param container Is the type going inside a container? + * @return Java type name, i.e. Vector + */ +string t_javame_generator::type_name(t_type* ttype, + bool in_container, + bool in_init, + bool skip_generic) { + (void)in_init; + (void)skip_generic; + // In Java typedefs are just resolved to their real type + ttype = get_true_type(ttype); + string prefix; + + if (ttype->is_base_type()) { + return base_type_name((t_base_type*)ttype, in_container); + } else if (ttype->is_map()) { + return "Hashtable"; + } else if (ttype->is_set()) { + return "Hashtable"; + } else if (ttype->is_list()) { + return "Vector"; + } + + // Check for namespacing + t_program* program = ttype->get_program(); + if (program != NULL && program != program_) { + string package = program->get_namespace("java"); + if (!package.empty()) { + return package + "." + ttype->get_name(); + } + } + + return ttype->get_name(); +} + +/** + * Returns the C++ type that corresponds to the thrift type. + * + * @param tbase The base type + * @param container Is it going in a Java container? + */ +string t_javame_generator::base_type_name(t_base_type* type, bool in_container) { + t_base_type::t_base tbase = type->get_base(); + + switch (tbase) { + case t_base_type::TYPE_VOID: + return "void"; + case t_base_type::TYPE_STRING: + if (!type->is_binary()) { + return "String"; + } else { + return "byte[]"; + } + case t_base_type::TYPE_BOOL: + return (in_container ? "Boolean" : "boolean"); + case t_base_type::TYPE_I8: + return (in_container ? "Byte" : "byte"); + case t_base_type::TYPE_I16: + return (in_container ? "Short" : "short"); + case t_base_type::TYPE_I32: + return (in_container ? "Integer" : "int"); + case t_base_type::TYPE_I64: + return (in_container ? "Long" : "long"); + case t_base_type::TYPE_DOUBLE: + return (in_container ? "Double" : "double"); + default: + throw "compiler error: no Java name for base type " + t_base_type::t_base_name(tbase); + } +} + +/** + * Declares a field, which may include initialization as necessary. + * + * @param ttype The type + */ +string t_javame_generator::declare_field(t_field* tfield, bool init) { + // TODO(mcslee): do we ever need to initialize the field? + string result = type_name(tfield->get_type()) + " " + tfield->get_name(); + if (init) { + t_type* ttype = get_true_type(tfield->get_type()); + if (ttype->is_base_type() && tfield->get_value() != NULL) { + std::ofstream dummy; + result += " = " + render_const_value(dummy, tfield->get_name(), ttype, tfield->get_value()); + } else if (ttype->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)ttype)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + result += " = null"; + break; + case t_base_type::TYPE_BOOL: + result += " = false"; + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + result += " = 0"; + break; + case t_base_type::TYPE_DOUBLE: + result += " = (double)0"; + break; + } + + } else if (ttype->is_enum()) { + result += " = 0"; + } else if (ttype->is_container()) { + result += " = new " + type_name(ttype, false, true) + "()"; + } else { + result += " = new " + type_name(ttype, false, true) + "()"; + ; + } + } + return result + ";"; +} + +/** + * Renders a function signature of the form 'type name(args)' + * + * @param tfunction Function definition + * @return String of rendered function definition + */ +string t_javame_generator::function_signature(t_function* tfunction, string prefix) { + t_type* ttype = tfunction->get_returntype(); + std::string result = type_name(ttype) + " " + prefix + tfunction->get_name() + "(" + + argument_list(tfunction->get_arglist()) + ") throws "; + t_struct* xs = tfunction->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + result += type_name((*x_iter)->get_type(), false, false) + ", "; + } + result += "TException"; + return result; +} + +/** + * Renders a comma separated field list, with type names + */ +string t_javame_generator::argument_list(t_struct* tstruct, bool include_types) { + string result = ""; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + result += ", "; + } + if (include_types) { + result += type_name((*f_iter)->get_type()) + " "; + } + result += (*f_iter)->get_name(); + } + return result; +} + +/** + * Converts the parse type to a C++ enum string for the given type. + */ +string t_javame_generator::type_to_enum(t_type* type) { + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + return "TType.STRING"; + case t_base_type::TYPE_BOOL: + return "TType.BOOL"; + case t_base_type::TYPE_I8: + return "TType.BYTE"; + case t_base_type::TYPE_I16: + return "TType.I16"; + case t_base_type::TYPE_I32: + return "TType.I32"; + case t_base_type::TYPE_I64: + return "TType.I64"; + case t_base_type::TYPE_DOUBLE: + return "TType.DOUBLE"; + } + } else if (type->is_enum()) { + return "TType.I32"; + } else if (type->is_struct() || type->is_xception()) { + return "TType.STRUCT"; + } else if (type->is_map()) { + return "TType.MAP"; + } else if (type->is_set()) { + return "TType.SET"; + } else if (type->is_list()) { + return "TType.LIST"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +/** + * Applies the correct style to a string based on the value of nocamel_style_ + */ +std::string t_javame_generator::get_cap_name(std::string name) { + name[0] = toupper(name[0]); + return name; +} + +string t_javame_generator::constant_name(string name) { + string constant_name; + + bool is_first = true; + bool was_previous_char_upper = false; + for (char character : name) { + bool is_upper = isupper(character); + + if (is_upper && !is_first && !was_previous_char_upper) { + constant_name += '_'; + } + constant_name += toupper(character); + + is_first = false; + was_previous_char_upper = is_upper; + } + + return constant_name; +} + +void t_javame_generator::generate_java_docstring_comment(ostream& out, string contents) { + generate_docstring_comment(out, "/**\n", " * ", contents, " */\n"); +} + +void t_javame_generator::generate_java_doc(ostream& out, t_field* field) { + if (field->get_type()->is_enum()) { + string combined_message = field->get_doc() + "\n@see " + get_enum_class_name(field->get_type()); + generate_java_docstring_comment(out, combined_message); + } else { + generate_java_doc(out, (t_doc*)field); + } +} + +/** + * Emits a JavaDoc comment if the provided object has a doc in Thrift + */ +void t_javame_generator::generate_java_doc(ostream& out, t_doc* tdoc) { + if (tdoc->has_doc()) { + generate_java_docstring_comment(out, tdoc->get_doc()); + } +} + +/** + * Emits a JavaDoc comment if the provided function object has a doc in Thrift + */ +void t_javame_generator::generate_java_doc(ostream& out, t_function* tfunction) { + if (tfunction->has_doc()) { + stringstream ss; + ss << tfunction->get_doc(); + const vector<t_field*>& fields = tfunction->get_arglist()->get_members(); + vector<t_field*>::const_iterator p_iter; + for (p_iter = fields.begin(); p_iter != fields.end(); ++p_iter) { + t_field* p = *p_iter; + ss << "\n@param " << p->get_name(); + if (p->has_doc()) { + ss << " " << p->get_doc(); + } + } + generate_docstring_comment(out, "/**\n", " * ", ss.str(), " */\n"); + } +} + +void t_javame_generator::generate_deep_copy_container(ostream& out, + std::string source_name_p1, + std::string source_name_p2, + std::string result_name, + t_type* type) { + + t_container* container = (t_container*)type; + std::string source_name; + if (source_name_p2 == "") + source_name = source_name_p1; + else + source_name = source_name_p1 + "." + source_name_p2; + + indent(out) << type_name(type, true, false) << " " << result_name << " = new " + << type_name(container, false, true) << "();" << endl; + + std::string iterator_element_name = source_name_p1 + "_element"; + std::string enumeration_name = source_name_p1 + "_enum"; + std::string result_element_name = result_name + "_copy"; + + if (container->is_map()) { + t_type* key_type = ((t_map*)container)->get_key_type(); + t_type* val_type = ((t_map*)container)->get_val_type(); + + indent(out) << "for (Enumeration " << enumeration_name << " = " << source_name << ".keys(); " + << enumeration_name << ".hasMoreElements(); ) {" << endl; + indent_up(); + + out << endl; + + indent(out) << type_name(key_type, true, false) << " " << iterator_element_name << "_key = (" + << type_name(key_type, true, false) << ")" << enumeration_name << ".nextElement();" + << endl; + indent(out) << type_name(val_type, true, false) << " " << iterator_element_name << "_value = (" + << type_name(val_type, true, false) << ")" << source_name << ".get(" + << iterator_element_name << "_key);" << endl; + + out << endl; + + if (key_type->is_container()) { + generate_deep_copy_container(out, + iterator_element_name + "_key", + "", + result_element_name + "_key", + key_type); + } else { + indent(out) << type_name(key_type, true, false) << " " << result_element_name << "_key = "; + generate_deep_copy_non_container(out, + iterator_element_name + "_key", + result_element_name + "_key", + key_type); + out << ";" << endl; + } + + out << endl; + + if (val_type->is_container()) { + generate_deep_copy_container(out, + iterator_element_name + "_value", + "", + result_element_name + "_value", + val_type); + } else { + indent(out) << type_name(val_type, true, false) << " " << result_element_name << "_value = "; + generate_deep_copy_non_container(out, + iterator_element_name + "_value", + result_element_name + "_value", + val_type); + out << ";" << endl; + } + + out << endl; + + indent(out) << result_name << ".put(" << result_element_name << "_key, " << result_element_name + << "_value);" << endl; + + indent_down(); + indent(out) << "}" << endl; + + } else { + t_type* elem_type; + + if (container->is_set()) { + elem_type = ((t_set*)container)->get_elem_type(); + } else { + elem_type = ((t_list*)container)->get_elem_type(); + } + + indent(out) << "for (Enumeration " << enumeration_name << " = " << source_name + << ".elements(); " << enumeration_name << ".hasMoreElements(); ) {" << endl; + indent_up(); + indent(out) << type_name(elem_type, true, false) << " " << iterator_element_name << " = (" + << type_name(elem_type, true, false) << ")" << enumeration_name << ".nextElement();" + << endl; + if (elem_type->is_container()) { + // recursive deep copy + generate_deep_copy_container(out, iterator_element_name, "", result_element_name, elem_type); + if (elem_type->is_list()) { + indent(out) << result_name << ".addElement(" << result_element_name << ");" << endl; + } else { + indent(out) << result_name << ".put(" << result_element_name << ", " << result_element_name + << ");" << endl; + } + } else { + // iterative copy + if (elem_type->is_binary()) { + indent(out) << type_name(elem_type, true, false) << " temp_binary_element = "; + generate_deep_copy_non_container(out, + iterator_element_name, + "temp_binary_element", + elem_type); + out << ";" << endl; + if (elem_type->is_list()) { + indent(out) << result_name << ".addElement(temp_binary_element);" << endl; + } else { + indent(out) << result_name << ".put(temp_binary_element, temp_binary_element);" << endl; + } + } else { + indent(out) << result_name << ".addElement("; + generate_deep_copy_non_container(out, iterator_element_name, result_name, elem_type); + out << ");" << endl; + } + } + + indent_down(); + + indent(out) << "}" << endl; + } +} + +void t_javame_generator::generate_deep_copy_non_container(ostream& out, + std::string source_name, + std::string dest_name, + t_type* type) { + if (type->is_base_type() || type->is_enum() || type->is_typedef()) { + // binary fields need to be copied with System.arraycopy + if (type->is_binary()) { + out << "new byte[" << source_name << ".length];" << endl; + indent(out) << "System.arraycopy(" << source_name << ", 0, " << dest_name << ", 0, " + << source_name << ".length)"; + } + // everything else can be copied directly + else + out << source_name; + } else { + out << "new " << type_name(type, true, true) << "(" << source_name << ")"; + } +} + +std::string t_javame_generator::generate_isset_check(t_field* field) { + return generate_isset_check(field->get_name()); +} + +std::string t_javame_generator::isset_field_id(t_field* field) { + return "__" + upcase_string(field->get_name() + "_isset_id"); +} + +std::string t_javame_generator::generate_isset_check(std::string field_name) { + return "is" + get_cap_name("set") + get_cap_name(field_name) + "()"; +} + +void t_javame_generator::generate_isset_set(ostream& out, t_field* field) { + if (!type_can_be_null(field->get_type())) { + indent(out) << "set" << get_cap_name(field->get_name()) << get_cap_name("isSet") << "(true);" + << endl; + } +} + +std::string t_javame_generator::get_enum_class_name(t_type* type) { + string package = ""; + t_program* program = type->get_program(); + if (program != NULL && program != program_) { + package = program->get_namespace("java") + "."; + } + return package + type->get_name(); +} + +void t_javame_generator::generate_struct_desc(ostream& out, t_struct* tstruct) { + indent(out) << "private static final TStruct STRUCT_DESC = new TStruct(\"" << tstruct->get_name() + << "\");" << endl; +} + +void t_javame_generator::generate_field_descs(ostream& out, t_struct* tstruct) { + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + indent(out) << "private static final TField " << constant_name((*m_iter)->get_name()) + << "_FIELD_DESC = new TField(\"" << (*m_iter)->get_name() << "\", " + << type_to_enum((*m_iter)->get_type()) << ", " + << "(short)" << (*m_iter)->get_key() << ");" << endl; + } +} + +bool t_javame_generator::has_bit_vector(t_struct* tstruct) { + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if (!type_can_be_null(get_true_type((*m_iter)->get_type()))) { + return true; + } + } + return false; +} + +void t_javame_generator::generate_java_struct_clear(std::ostream& out, t_struct* tstruct) { + indent(out) << "public void clear() {" << endl; + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + indent_up(); + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* t = get_true_type((*m_iter)->get_type()); + if ((*m_iter)->get_value() != NULL) { + print_const_value(out, + "this." + (*m_iter)->get_name(), + t, + (*m_iter)->get_value(), + true, + true); + } else { + if (type_can_be_null(t)) { + indent(out) << "this." << (*m_iter)->get_name() << " = null;" << endl; + } else { + // must be a base type + // means it also needs to be explicitly unset + indent(out) << "set" << get_cap_name((*m_iter)->get_name()) << get_cap_name("isSet") + << "(false);" << endl; + switch (((t_base_type*)t)->get_base()) { + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + indent(out) << "this." << (*m_iter)->get_name() << " = 0;" << endl; + break; + case t_base_type::TYPE_DOUBLE: + indent(out) << "this." << (*m_iter)->get_name() << " = 0.0;" << endl; + break; + case t_base_type::TYPE_BOOL: + indent(out) << "this." << (*m_iter)->get_name() << " = false;" << endl; + break; + default: // prevent gcc compiler warning + break; + } + } + } + } + indent_down(); + + indent(out) << "}" << endl << endl; +} + +THRIFT_REGISTER_GENERATOR(javame, "Java ME", "") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_js_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_js_generator.cc new file mode 100644 index 000000000..dfc398695 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_js_generator.cc @@ -0,0 +1,2948 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <map> +#include <string> +#include <fstream> +#include <iomanip> +#include <iostream> +#include <limits> +#include <vector> +#include <list> +#include <cassert> +#include <unordered_map> + +#include <stdlib.h> +#include <sys/stat.h> +#include <sstream> +#include "thrift/platform.h" +#include "thrift/version.h" + +using std::map; +using std::ostream; +using std::ostringstream; +using std::string; +using std::stringstream; +using std::unordered_map; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes +static const string episode_file_name = "thrift.js.episode"; +// largest consecutive integer representable by a double (2 ^ 53 - 1) +static const int64_t max_safe_integer = 0x1fffffffffffff; +// smallest consecutive number representable by a double (-2 ^ 53 + 1) +static const int64_t min_safe_integer = -max_safe_integer; + +#include "thrift/generate/t_oop_generator.h" + + +/** + * JS code generator. + */ +class t_js_generator : public t_oop_generator { +public: + t_js_generator(t_program* program, + const std::map<std::string, std::string>& parsed_options, + const std::string& option_string) + : t_oop_generator(program) { + (void)option_string; + std::map<std::string, std::string>::const_iterator iter; + + gen_node_ = false; + gen_jquery_ = false; + gen_ts_ = false; + gen_es6_ = false; + gen_episode_file_ = false; + + bool with_ns_ = false; + + for (iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + if( iter->first.compare("node") == 0) { + gen_node_ = true; + } else if( iter->first.compare("jquery") == 0) { + gen_jquery_ = true; + } else if( iter->first.compare("ts") == 0) { + gen_ts_ = true; + } else if( iter->first.compare("with_ns") == 0) { + with_ns_ = true; + } else if( iter->first.compare("es6") == 0) { + gen_es6_ = true; + } else if( iter->first.compare("imports") == 0) { + parse_imports(program, iter->second); + } else if (iter->first.compare("thrift_package_output_directory") == 0) { + parse_thrift_package_output_directory(iter->second); + } else { + throw std::invalid_argument("unknown option js:" + iter->first); + } + } + + if (gen_es6_ && gen_jquery_) { + throw std::invalid_argument("invalid switch: [-gen js:es6,jquery] options not compatible"); + } + + if (gen_node_ && gen_jquery_) { + throw std::invalid_argument("invalid switch: [-gen js:node,jquery] options not compatible, try: [-gen js:node -gen " + "js:jquery]"); + } + + if (!gen_node_ && with_ns_) { + throw std::invalid_argument("invalid switch: [-gen js:with_ns] is only valid when using node.js"); + } + + // Depending on the processing flags, we will update these to be ES6 compatible + js_const_type_ = "var "; + js_let_type_ = "var "; + js_var_type_ = "var "; + if (gen_es6_) { + js_const_type_ = "const "; + js_let_type_ = "let "; + } + + if (gen_node_) { + out_dir_base_ = "gen-nodejs"; + no_ns_ = !with_ns_; + } else { + out_dir_base_ = "gen-js"; + no_ns_ = false; + } + + escape_['\''] = "\\'"; + } + + /** + * Init and close methods + */ + + void init_generator() override; + void close_generator() override; + + /** + * Program-level generation functions + */ + + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_const(t_const* tconst) override; + void generate_struct(t_struct* tstruct) override; + void generate_xception(t_struct* txception) override; + void generate_service(t_service* tservice) override; + + std::string render_recv_throw(std::string var); + std::string render_recv_return(std::string var); + + std::string render_const_value(t_type* type, t_const_value* value); + + /** + * Structs! + */ + void generate_js_struct(t_struct* tstruct, bool is_exception); + void generate_js_struct_definition(std::ostream& out, + t_struct* tstruct, + bool is_xception = false, + bool is_exported = true); + void generate_js_struct_reader(std::ostream& out, t_struct* tstruct); + void generate_js_struct_writer(std::ostream& out, t_struct* tstruct); + void generate_js_function_helpers(t_function* tfunction); + + /** + * Service-level generation functions + */ + void generate_service_helpers(t_service* tservice); + void generate_service_interface(t_service* tservice); + void generate_service_rest(t_service* tservice); + void generate_service_client(t_service* tservice); + void generate_service_processor(t_service* tservice); + void generate_process_function(t_service* tservice, t_function* tfunction); + + /** + * Serialization constructs + */ + + void generate_deserialize_field(std::ostream& out, + t_field* tfield, + std::string prefix = "", + bool inclass = false); + + void generate_deserialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); + + void generate_deserialize_container(std::ostream& out, t_type* ttype, std::string prefix = ""); + + void generate_deserialize_set_element(std::ostream& out, t_set* tset, std::string prefix = ""); + + void generate_deserialize_map_element(std::ostream& out, t_map* tmap, std::string prefix = ""); + + void generate_deserialize_list_element(std::ostream& out, + t_list* tlist, + std::string prefix = ""); + + void generate_serialize_field(std::ostream& out, t_field* tfield, std::string prefix = ""); + + void generate_serialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); + + void generate_serialize_container(std::ostream& out, t_type* ttype, std::string prefix = ""); + + void generate_serialize_map_element(std::ostream& out, + t_map* tmap, + std::string kiter, + std::string viter); + + void generate_serialize_set_element(std::ostream& out, t_set* tmap, std::string iter); + + void generate_serialize_list_element(std::ostream& out, t_list* tlist, std::string iter); + + /** + * Helper rendering functions + */ + + std::string js_includes(); + std::string ts_includes(); + std::string ts_service_includes(); + std::string render_includes(); + std::string render_ts_includes(); + std::string get_import_path(t_program* program); + std::string declare_field(t_field* tfield, bool init = false, bool obj = false); + std::string function_signature(t_function* tfunction, + std::string prefix = "", + bool include_callback = false); + std::string argument_list(t_struct* tstruct, bool include_callback = false); + std::string type_to_enum(t_type* ttype); + std::string make_valid_nodeJs_identifier(std::string const& name); + + /** + * Helper parser functions + */ + + void parse_imports(t_program* program, const std::string& imports_string); + void parse_thrift_package_output_directory(const std::string& thrift_package_output_directory); + + std::string autogen_comment() override { + return std::string("//\n") + "// Autogenerated by Thrift Compiler (" + THRIFT_VERSION + ")\n" + + "//\n" + "// DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n" + + "//\n"; + } + + t_type* get_contained_type(t_type* t); + + std::vector<std::string> js_namespace_pieces(t_program* p) { + std::string ns = p->get_namespace("js"); + + std::string::size_type loc; + std::vector<std::string> pieces; + + if (no_ns_) { + return pieces; + } + + if (ns.size() > 0) { + while ((loc = ns.find(".")) != std::string::npos) { + pieces.push_back(ns.substr(0, loc)); + ns = ns.substr(loc + 1); + } + } + + if (ns.size() > 0) { + pieces.push_back(ns); + } + + return pieces; + } + + std::string js_type_namespace(t_program* p) { + if (gen_node_) { + if (p != NULL && p != program_) { + return make_valid_nodeJs_identifier(p->get_name()) + "_ttypes."; + } + return "ttypes."; + } + return js_namespace(p); + } + + std::string js_export_namespace(t_program* p) { + if (gen_node_) { + return "exports."; + } + return js_namespace(p); + } + + bool has_js_namespace(t_program* p) { + if (no_ns_) { + return false; + } + std::string ns = p->get_namespace("js"); + return (ns.size() > 0); + } + + std::string js_namespace(t_program* p) { + if (no_ns_) { + return ""; + } + std::string ns = p->get_namespace("js"); + if (ns.size() > 0) { + ns += "."; + } + + return ns; + } + + /** + * TypeScript Definition File helper functions + */ + + string ts_function_signature(t_function* tfunction, bool include_callback); + string ts_get_type(t_type* type); + + /** + * Special indentation for TypeScript Definitions because of the module. + * Returns the normal indentation + " " if a module was defined. + * @return string + */ + string ts_indent() { return indent() + (!ts_module_.empty() ? " " : ""); } + + /** + * Returns "declare " if no module was defined. + * @return string + */ + string ts_declare() { return (ts_module_.empty() ? (gen_node_ ? "declare " : "export declare ") : ""); } + + /** + * Returns "?" if the given field is optional or has a default value. + * @param t_field The field to check + * @return string + */ + string ts_get_req(t_field* field) {return (field->get_req() == t_field::T_OPTIONAL || field->get_value() != NULL ? "?" : ""); } + + /** + * Returns the documentation, if the provided documentable object has one. + * @param t_doc The object to get the documentation from + * @return string The documentation + */ + string ts_print_doc(t_doc* tdoc) { + string result = endl; + + if (tdoc->has_doc()) { + std::stringstream doc(tdoc->get_doc()); + string item; + + result += ts_indent() + "/**" + endl; + while (std::getline(doc, item)) { + result += ts_indent() + " * " + item + endl; + } + result += ts_indent() + " */" + endl; + } + return result; + } + +private: + /** + * True if we should generate NodeJS-friendly RPC services. + */ + bool gen_node_; + + /** + * True if we should generate services that use jQuery ajax (async/sync). + */ + bool gen_jquery_; + + /** + * True if we should generate a TypeScript Definition File for each service. + */ + bool gen_ts_; + + /** + * True if we should generate ES6 code, i.e. with Promises + */ + bool gen_es6_; + + /** + * True if we will generate an episode file. + */ + bool gen_episode_file_; + + /** + * The name of the defined module(s), for TypeScript Definition Files. + */ + string ts_module_; + + /** + * True if we should not generate namespace objects for node. + */ + bool no_ns_; + + /** + * The node modules to use when importing the previously generated files. + */ + vector<string> imports; + + /** + * Cache for imported modules. + */ + unordered_map<string, string> module_name_2_import_path; + + /** + * Cache for TypeScript includes to generated import name. + */ + unordered_map<t_program*, string> include_2_import_name; + + /** + * The prefix to use when generating the episode file. + */ + string thrift_package_output_directory_; + + /** + * The variable decorator for "const" variables. Will default to "var" if in an incompatible language. + */ + string js_const_type_; + + /** + * The variable decorator for "let" variables. Will default to "var" if in an incompatible language. + */ + string js_let_type_; + + /** + * The default variable decorator. Supports all javascript languages, but is not scoped to functions or closures. + */ + string js_var_type_; + + /** + * File streams + */ + ofstream_with_content_based_conditional_update f_episode_; + ofstream_with_content_based_conditional_update f_types_; + ofstream_with_content_based_conditional_update f_service_; + ofstream_with_content_based_conditional_update f_types_ts_; + ofstream_with_content_based_conditional_update f_service_ts_; +}; + +/** + * Prepares for file generation by opening up the necessary file output + * streams. + * + * @param tprogram The program to generate + */ +void t_js_generator::init_generator() { + // Make output directory + MKDIR(get_out_dir().c_str()); + + const auto outdir = get_out_dir(); + + // Make output file(s) + if (gen_episode_file_) { + const auto f_episode_file_path = outdir + episode_file_name; + f_episode_.open(f_episode_file_path); + } + + const auto f_types_name = outdir + program_->get_name() + "_types.js"; + f_types_.open(f_types_name.c_str()); + if (gen_episode_file_) { + const auto types_module = program_->get_name() + "_types"; + f_episode_ << types_module << ":" << thrift_package_output_directory_ << "/" << types_module << endl; + } + + if (gen_ts_) { + const auto f_types_ts_name = outdir + program_->get_name() + "_types.d.ts"; + f_types_ts_.open(f_types_ts_name.c_str()); + } + + // Print header + f_types_ << autogen_comment(); + + if ((gen_node_ || gen_es6_) && no_ns_) { + f_types_ << "\"use strict\";" << endl << endl; + } + + f_types_ << js_includes() << endl << render_includes() << endl; + + if (gen_ts_) { + f_types_ts_ << autogen_comment() << ts_includes() << endl << render_ts_includes() << endl; + } + + if (gen_node_) { + f_types_ << js_const_type_ << "ttypes = module.exports = {};" << endl; + } + + string pns; + + // setup the namespace + // TODO should the namespace just be in the directory structure for node? + vector<string> ns_pieces = js_namespace_pieces(program_); + if (ns_pieces.size() > 0) { + for (size_t i = 0; i < ns_pieces.size(); ++i) { + pns += ((i == 0) ? "" : ".") + ns_pieces[i]; + f_types_ << "if (typeof " << pns << " === 'undefined') {" << endl; + f_types_ << " " << pns << " = {};" << endl; + f_types_ << "}" << endl; + f_types_ << "" << "if (typeof module !== 'undefined' && module.exports) {" << endl; + f_types_ << " module.exports." << pns << " = " << pns << ";" << endl << "}" << endl; + } + if (gen_ts_) { + ts_module_ = pns; + f_types_ts_ << "declare module " << ts_module_ << " {"; + } + } +} + +/** + * Prints standard js imports + */ +string t_js_generator::js_includes() { + if (gen_node_) { + string result = js_const_type_ + "thrift = require('thrift');\n" + + js_const_type_ + "Thrift = thrift.Thrift;\n"; + if (!gen_es6_) { + result += js_const_type_ + "Q = thrift.Q;\n"; + } + result += js_const_type_ + "Int64 = require('node-int64');\n"; + return result; + } + string result = "if (typeof Int64 === 'undefined' && typeof require === 'function') {\n " + js_const_type_ + "Int64 = require('node-int64');\n}\n"; + return result; +} + +/** + * Prints standard ts imports + */ +string t_js_generator::ts_includes() { + if (gen_node_) { + return string( + "import thrift = require('thrift');\n" + "import Thrift = thrift.Thrift;\n" + "import Q = thrift.Q;\n" + "import Int64 = require('node-int64');"); + } + return string("import Int64 = require('node-int64');"); +} + +/** + * Prints service ts imports + */ +string t_js_generator::ts_service_includes() { + if (gen_node_) { + return string( + "import thrift = require('thrift');\n" + "import Thrift = thrift.Thrift;\n" + "import Q = thrift.Q;\n" + "import Int64 = require('node-int64');"); + } + return string("import Int64 = require('node-int64');"); +} + +/** + * Renders all the imports necessary for including another Thrift program + */ +string t_js_generator::render_includes() { + string result = ""; + + if (gen_node_) { + const vector<t_program*>& includes = program_->get_includes(); + for (auto include : includes) { + result += js_const_type_ + make_valid_nodeJs_identifier(include->get_name()) + "_ttypes = require('" + get_import_path(include) + "');\n"; + } + if (includes.size() > 0) { + result += "\n"; + } + } + + return result; +} + +/** + * Renders all the imports necessary for including another Thrift program + */ +string t_js_generator::render_ts_includes() { + string result; + + if (!gen_node_) { + return result; + } + const vector<t_program*>& includes = program_->get_includes(); + for (auto include : includes) { + string include_name = make_valid_nodeJs_identifier(include->get_name()) + "_ttypes"; + include_2_import_name.insert({include, include_name}); + result += "import " + include_name + " = require('" + get_import_path(include) + "');\n"; + } + if (includes.size() > 0) { + result += "\n"; + } + + return result; +} + +string t_js_generator::get_import_path(t_program* program) { + const string import_file_name(program->get_name() + "_types"); + if (program->get_recursive()) { + return "./" + import_file_name; + } + + const string import_file_name_with_extension = import_file_name + ".js"; + + auto module_name_and_import_path_iterator = module_name_2_import_path.find(import_file_name); + if (module_name_and_import_path_iterator != module_name_2_import_path.end()) { + return module_name_and_import_path_iterator->second; + } + return "./" + import_file_name; +} + +/** + * Close up (or down) some filez. + */ +void t_js_generator::close_generator() { + // Close types file(s) + + f_types_.close(); + + if (gen_ts_) { + if (!ts_module_.empty()) { + f_types_ts_ << "}"; + } + f_types_ts_.close(); + } + if (gen_episode_file_){ + f_episode_.close(); + } +} + +/** + * Generates a typedef. This is not done in JS, types are all implicit. + * + * @param ttypedef The type definition + */ +void t_js_generator::generate_typedef(t_typedef* ttypedef) { + (void)ttypedef; +} + +/** + * Generates code for an enumerated type. Since define is expensive to lookup + * in JS, we use a global array for this. + * + * @param tenum The enumeration + */ +void t_js_generator::generate_enum(t_enum* tenum) { + f_types_ << js_type_namespace(tenum->get_program()) << tenum->get_name() << " = {" << endl; + + if (gen_ts_) { + f_types_ts_ << ts_print_doc(tenum) << ts_indent() << ts_declare() << "enum " + << tenum->get_name() << " {" << endl; + } + + indent_up(); + + vector<t_enum_value*> const& constants = tenum->get_constants(); + vector<t_enum_value*>::const_iterator c_iter; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + int value = (*c_iter)->get_value(); + if (gen_ts_) { + f_types_ts_ << ts_indent() << (*c_iter)->get_name() << " = " << value << "," << endl; + // add 'value: key' in addition to 'key: value' for TypeScript enums + f_types_ << indent() << "'" << value << "' : '" << (*c_iter)->get_name() << "'," << endl; + } + f_types_ << indent() << "'" << (*c_iter)->get_name() << "' : " << value; + if (c_iter != constants.end() - 1) { + f_types_ << ","; + } + f_types_ << endl; + } + + indent_down(); + + f_types_ << "};" << endl; + + if (gen_ts_) { + f_types_ts_ << ts_indent() << "}" << endl; + } +} + +/** + * Generate a constant value + */ +void t_js_generator::generate_const(t_const* tconst) { + t_type* type = tconst->get_type(); + string name = tconst->get_name(); + t_const_value* value = tconst->get_value(); + + f_types_ << js_type_namespace(program_) << name << " = "; + f_types_ << render_const_value(type, value) << ";" << endl; + + if (gen_ts_) { + f_types_ts_ << ts_print_doc(tconst) << ts_indent() << ts_declare() << js_const_type_ << name << ": " + << ts_get_type(type) << ";" << endl; + } +} + +/** + * Prints the value of a constant with the given type. Note that type checking + * is NOT performed in this function as it is always run beforehand using the + * validate_types method in main.cc + */ +string t_js_generator::render_const_value(t_type* type, t_const_value* value) { + std::ostringstream out; + + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + out << "'" << get_escaped_string(value) << "'"; + break; + case t_base_type::TYPE_BOOL: + out << (value->get_integer() > 0 ? "true" : "false"); + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + out << value->get_integer(); + break; + case t_base_type::TYPE_I64: + { + int64_t const& integer_value = value->get_integer(); + if (integer_value <= max_safe_integer && integer_value >= min_safe_integer) { + out << "new Int64(" << integer_value << ")"; + } else { + out << "new Int64('" << std::hex << integer_value << std::dec << "')"; + } + } + break; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) { + out << value->get_integer(); + } else { + out << emit_double_as_string(value->get_double()); + } + break; + default: + throw std::runtime_error("compiler error: no const of base type " + t_base_type::t_base_name(tbase)); + } + } else if (type->is_enum()) { + out << value->get_integer(); + } else if (type->is_struct() || type->is_xception()) { + out << "new " << js_type_namespace(type->get_program()) << type->get_name() << "({"; + indent_up(); + const vector<t_field*>& fields = ((t_struct*)type)->get_members(); + vector<t_field*>::const_iterator f_iter; + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + t_type* field_type = NULL; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field_type = (*f_iter)->get_type(); + } + } + if (field_type == NULL) { + throw std::runtime_error("type error: " + type->get_name() + " has no field " + v_iter->first->get_string()); + } + if (v_iter != val.begin()) + out << ","; + out << endl << indent() << render_const_value(g_type_string, v_iter->first); + out << " : "; + out << render_const_value(field_type, v_iter->second); + } + indent_down(); + out << endl << indent() << "})"; + } else if (type->is_map()) { + t_type* ktype = ((t_map*)type)->get_key_type(); + + t_type* vtype = ((t_map*)type)->get_val_type(); + out << "{" << endl; + indent_up(); + + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + if (v_iter != val.begin()) + out << "," << endl; + + if (ktype->is_base_type() && ((t_base_type*)get_true_type(ktype))->get_base() == t_base_type::TYPE_I64){ + out << indent() << "\"" << v_iter->first->get_integer() << "\""; + } else { + out << indent() << render_const_value(ktype, v_iter->first); + } + + out << " : "; + out << render_const_value(vtype, v_iter->second); + } + indent_down(); + out << endl << indent() << "}"; + } else if (type->is_list() || type->is_set()) { + t_type* etype; + if (type->is_list()) { + etype = ((t_list*)type)->get_elem_type(); + } else { + etype = ((t_set*)type)->get_elem_type(); + } + out << "["; + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + if (v_iter != val.begin()) + out << ","; + out << render_const_value(etype, *v_iter); + } + out << "]"; + } + return out.str(); +} + +/** + * Make a struct + */ +void t_js_generator::generate_struct(t_struct* tstruct) { + generate_js_struct(tstruct, false); +} + +/** + * Generates a struct definition for a thrift exception. Basically the same + * as a struct but extends the Exception class. + * + * @param txception The struct definition + */ +void t_js_generator::generate_xception(t_struct* txception) { + generate_js_struct(txception, true); +} + +/** + * Structs can be normal or exceptions. + */ +void t_js_generator::generate_js_struct(t_struct* tstruct, bool is_exception) { + generate_js_struct_definition(f_types_, tstruct, is_exception); +} + +/** + * Return type of contained elements for a container type. For maps + * this is type of value (keys are always strings in js) + */ +t_type* t_js_generator::get_contained_type(t_type* t) { + t_type* etype; + if (t->is_list()) { + etype = ((t_list*)t)->get_elem_type(); + } else if (t->is_set()) { + etype = ((t_set*)t)->get_elem_type(); + } else { + etype = ((t_map*)t)->get_val_type(); + } + return etype; +} + +/** + * Generates a struct definition for a thrift data type. This is nothing in JS + * where the objects are all just associative arrays (unless of course we + * decide to start using objects for them...) + * + * @param tstruct The struct definition + */ +void t_js_generator::generate_js_struct_definition(ostream& out, + t_struct* tstruct, + bool is_exception, + bool is_exported) { + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + if (gen_node_) { + string prefix = has_js_namespace(tstruct->get_program()) ? js_namespace(tstruct->get_program()) : js_const_type_; + out << prefix << tstruct->get_name() << + (is_exported ? " = module.exports." + tstruct->get_name() : ""); + if (gen_ts_) { + f_types_ts_ << ts_print_doc(tstruct) << ts_indent() << ts_declare() << "class " + << tstruct->get_name() << (is_exception ? " extends Thrift.TException" : "") + << " {" << endl; + } + } else { + out << js_namespace(tstruct->get_program()) << tstruct->get_name(); + if (gen_ts_) { + f_types_ts_ << ts_print_doc(tstruct) << ts_indent() << ts_declare() << "class " + << tstruct->get_name() << (is_exception ? " extends Thrift.TException" : "") + << " {" << endl; + } + } + + if (gen_es6_) { + if (gen_node_ && is_exception) { + out << " = class extends Thrift.TException {" << endl; + } else { + out << " = class {" << endl; + } + indent_up(); + indent(out) << "constructor(args) {" << endl; + } else { + out << " = function(args) {" << endl; + } + + indent_up(); + + // Call super() method on inherited Error class + if (gen_node_ && is_exception) { + if (gen_es6_) { + indent(out) << "super(args);" << endl; + } else { + indent(out) << "Thrift.TException.call(this, \"" << js_namespace(tstruct->get_program()) + << tstruct->get_name() << "\");" << endl; + } + out << indent() << "this.name = \"" << js_namespace(tstruct->get_program()) + << tstruct->get_name() << "\";" << endl; + } + + // members with arguments + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + string dval = declare_field(*m_iter, false, true); + t_type* t = get_true_type((*m_iter)->get_type()); + if ((*m_iter)->get_value() != NULL && !(t->is_struct() || t->is_xception())) { + dval = render_const_value((*m_iter)->get_type(), (*m_iter)->get_value()); + out << indent() << "this." << (*m_iter)->get_name() << " = " << dval << ";" << endl; + } else { + out << indent() << dval << ";" << endl; + } + if (gen_ts_) { + if (gen_node_) { + f_types_ts_ << ts_indent() << "public " << (*m_iter)->get_name() << ": " + << ts_get_type((*m_iter)->get_type()) << ";" << endl; + } else { + f_types_ts_ << ts_indent() << (*m_iter)->get_name() << ": " + << ts_get_type((*m_iter)->get_type()) << ";" << endl; + } + } + } + + // Generate constructor from array + if (members.size() > 0) { + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* t = get_true_type((*m_iter)->get_type()); + if ((*m_iter)->get_value() != NULL && (t->is_struct() || t->is_xception())) { + indent(out) << "this." << (*m_iter)->get_name() << " = " + << render_const_value(t, (*m_iter)->get_value()) << ";" << endl; + } + } + + // Early returns for exceptions + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* t = get_true_type((*m_iter)->get_type()); + if (t->is_xception()) { + out << indent() << "if (args instanceof " << js_type_namespace(t->get_program()) + << t->get_name() << ") {" << endl << indent() << indent() << "this." + << (*m_iter)->get_name() << " = args;" << endl << indent() << indent() << "return;" + << endl << indent() << "}" << endl; + } + } + + indent(out) << "if (args) {" << endl; + indent_up(); + if (gen_ts_) { + f_types_ts_ << endl << ts_indent() << "constructor(args?: { "; + } + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* t = get_true_type((*m_iter)->get_type()); + indent(out) << "if (args." << (*m_iter)->get_name() << " !== undefined && args." << (*m_iter)->get_name() << " !== null) {" << endl; + indent_up(); + indent(out) << "this." << (*m_iter)->get_name(); + + if (t->is_struct()) { + out << (" = new " + js_type_namespace(t->get_program()) + t->get_name() + + "(args."+(*m_iter)->get_name() +");"); + out << endl; + } else if (t->is_container()) { + t_type* etype = get_contained_type(t); + string copyFunc = t->is_map() ? "Thrift.copyMap" : "Thrift.copyList"; + string type_list = ""; + + while (etype->is_container()) { + if (type_list.length() > 0) { + type_list += ", "; + } + type_list += etype->is_map() ? "Thrift.copyMap" : "Thrift.copyList"; + etype = get_contained_type(etype); + } + + if (etype->is_struct()) { + if (type_list.length() > 0) { + type_list += ", "; + } + type_list += js_type_namespace(etype->get_program()) + etype->get_name(); + } + else { + if (type_list.length() > 0) { + type_list += ", "; + } + type_list += "null"; + } + + out << (" = " + copyFunc + "(args." + (*m_iter)->get_name() + + ", [" + type_list + "]);"); + out << endl; + } else { + out << " = args." << (*m_iter)->get_name() << ";" << endl; + } + + indent_down(); + if (!(*m_iter)->get_req()) { + indent(out) << "} else {" << endl; + indent(out) + << " throw new Thrift.TProtocolException(Thrift.TProtocolExceptionType.UNKNOWN, " + "'Required field " << (*m_iter)->get_name() << " is unset!');" << endl; + } + indent(out) << "}" << endl; + if (gen_ts_) { + f_types_ts_ << (*m_iter)->get_name() << ts_get_req(*m_iter) << ": " + << ts_get_type((*m_iter)->get_type()) << "; "; + } + } + indent_down(); + out << indent() << "}" << endl; + if (gen_ts_) { + f_types_ts_ << "});" << endl; + } + } + + // Done with constructor + indent_down(); + if (gen_es6_) { + indent(out) << "}" << endl << endl; + } else { + indent(out) << "};" << endl; + } + + if (gen_ts_) { + f_types_ts_ << ts_indent() << "}" << endl; + } + + if (!gen_es6_) { + if (is_exception) { + out << "Thrift.inherits(" << js_namespace(tstruct->get_program()) << tstruct->get_name() + << ", Thrift.TException);" << endl; + out << js_namespace(tstruct->get_program()) << tstruct->get_name() << ".prototype.name = '" + << tstruct->get_name() << "';" << endl; + } else { + // init prototype manually if we aren't using es6 + out << js_namespace(tstruct->get_program()) << tstruct->get_name() << ".prototype = {};" + << endl; + } + + } + + generate_js_struct_reader(out, tstruct); + generate_js_struct_writer(out, tstruct); + + // Close out the class definition + if (gen_es6_) { + indent_down(); + indent(out) << "};" << endl; + } +} + +/** + * Generates the read() method for a struct + */ +void t_js_generator::generate_js_struct_reader(ostream& out, t_struct* tstruct) { + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + if (gen_es6_) { + indent(out) << "read (input) {" << endl; + } else { + indent(out) << js_namespace(tstruct->get_program()) << tstruct->get_name() + << ".prototype.read = function(input) {" << endl; + } + + indent_up(); + + indent(out) << "input.readStructBegin();" << endl; + + // Loop over reading in fields + indent(out) << "while (true) {" << endl; + + indent_up(); + + indent(out) << js_const_type_ << "ret = input.readFieldBegin();" << endl; + indent(out) << js_const_type_ << "ftype = ret.ftype;" << endl; + if (!fields.empty()) { + indent(out) << js_const_type_ << "fid = ret.fid;" << endl; + } + + // Check for field STOP marker and break + indent(out) << "if (ftype == Thrift.Type.STOP) {" << endl; + indent_up(); + indent(out) << "break;" << endl; + indent_down(); + indent(out) << "}" << endl; + if (!fields.empty()) { + // Switch statement on the field we are reading + indent(out) << "switch (fid) {" << endl; + + indent_up(); + + // Generate deserialization code for known cases + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + + indent(out) << "case " << (*f_iter)->get_key() << ":" << endl; + indent(out) << "if (ftype == " << type_to_enum((*f_iter)->get_type()) << ") {" << endl; + + indent_up(); + generate_deserialize_field(out, *f_iter, "this."); + indent_down(); + + indent(out) << "} else {" << endl; + + indent(out) << " input.skip(ftype);" << endl; + + out << indent() << "}" << endl << indent() << "break;" << endl; + } + if (fields.size() == 1) { + // pseudo case to make jslint happy + indent(out) << "case 0:" << endl; + indent(out) << " input.skip(ftype);" << endl; + indent(out) << " break;" << endl; + } + // In the default case we skip the field + indent(out) << "default:" << endl; + indent(out) << " input.skip(ftype);" << endl; + + scope_down(out); + } else { + indent(out) << "input.skip(ftype);" << endl; + } + + indent(out) << "input.readFieldEnd();" << endl; + + scope_down(out); + + indent(out) << "input.readStructEnd();" << endl; + + indent(out) << "return;" << endl; + + indent_down(); + + if (gen_es6_) { + indent(out) << "}" << endl << endl; + } else { + indent(out) << "};" << endl << endl; + } +} + +/** + * Generates the write() method for a struct + */ +void t_js_generator::generate_js_struct_writer(ostream& out, t_struct* tstruct) { + string name = tstruct->get_name(); + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + if (gen_es6_) { + indent(out) << "write (output) {" << endl; + } else { + indent(out) << js_namespace(tstruct->get_program()) << tstruct->get_name() + << ".prototype.write = function(output) {" << endl; + } + + indent_up(); + + indent(out) << "output.writeStructBegin('" << name << "');" << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + out << indent() << "if (this." << (*f_iter)->get_name() << " !== null && this." + << (*f_iter)->get_name() << " !== undefined) {" << endl; + indent_up(); + + indent(out) << "output.writeFieldBegin(" + << "'" << (*f_iter)->get_name() << "', " << type_to_enum((*f_iter)->get_type()) + << ", " << (*f_iter)->get_key() << ");" << endl; + + // Write field contents + generate_serialize_field(out, *f_iter, "this."); + + indent(out) << "output.writeFieldEnd();" << endl; + + indent_down(); + indent(out) << "}" << endl; + } + + out << indent() << "output.writeFieldStop();" << endl << indent() << "output.writeStructEnd();" + << endl; + + out << indent() << "return;" << endl; + + indent_down(); + if (gen_es6_) { + out << indent() << "}" << endl << endl; + } else { + out << indent() << "};" << endl << endl; + } +} + +/** + * Generates a thrift service. + * + * @param tservice The service definition + */ +void t_js_generator::generate_service(t_service* tservice) { + string f_service_name = get_out_dir() + service_name_ + ".js"; + f_service_.open(f_service_name.c_str()); + if (gen_episode_file_) { + f_episode_ << service_name_ << ":" << thrift_package_output_directory_ << "/" << service_name_ << endl; + } + + if (gen_ts_) { + string f_service_ts_name = get_out_dir() + service_name_ + ".d.ts"; + f_service_ts_.open(f_service_ts_name.c_str()); + } + + f_service_ << autogen_comment(); + + if ((gen_node_ || gen_es6_) && no_ns_) { + f_service_ << "\"use strict\";" << endl << endl; + } + + f_service_ << js_includes() << endl << render_includes() << endl; + + if (gen_ts_) { + if (tservice->get_extends() != NULL) { + f_service_ts_ << "/// <reference path=\"" << tservice->get_extends()->get_name() + << ".d.ts\" />" << endl; + } + f_service_ts_ << autogen_comment() << endl << ts_includes() << endl << render_ts_includes() << endl; + if (gen_node_) { + f_service_ts_ << "import ttypes = require('./" + program_->get_name() + "_types');" << endl; + // Generate type aliases + // enum + vector<t_enum*> const& enums = program_->get_enums(); + vector<t_enum*>::const_iterator e_iter; + for (e_iter = enums.begin(); e_iter != enums.end(); ++e_iter) { + f_service_ts_ << "import " << (*e_iter)->get_name() << " = ttypes." + << js_namespace(program_) << (*e_iter)->get_name() << endl; + } + // const + vector<t_const*> const& consts = program_->get_consts(); + vector<t_const*>::const_iterator c_iter; + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + f_service_ts_ << "import " << (*c_iter)->get_name() << " = ttypes." + << js_namespace(program_) << (*c_iter)->get_name() << endl; + } + // exception + vector<t_struct*> const& exceptions = program_->get_xceptions(); + vector<t_struct*>::const_iterator x_iter; + for (x_iter = exceptions.begin(); x_iter != exceptions.end(); ++x_iter) { + f_service_ts_ << "import " << (*x_iter)->get_name() << " = ttypes." + << js_namespace(program_) << (*x_iter)->get_name() << endl; + } + // structs + vector<t_struct*> const& structs = program_->get_structs(); + vector<t_struct*>::const_iterator s_iter; + for (s_iter = structs.begin(); s_iter != structs.end(); ++s_iter) { + f_service_ts_ << "import " << (*s_iter)->get_name() << " = ttypes." + << js_namespace(program_) << (*s_iter)->get_name() << endl; + } + } else { + f_service_ts_ << "import { " << program_->get_name() << " } from \"./" << program_->get_name() << "_types\";" << endl << endl; + } + if (!ts_module_.empty()) { + if (gen_node_) { + f_service_ts_ << "declare module " << ts_module_ << " {"; + } else { + f_service_ts_ << "declare module \"./" << program_->get_name() << "_types\" {" << endl; + indent_up(); + f_service_ts_ << ts_indent() << "module " << program_->get_name() << " {" << endl; + indent_up(); + } + } + } + + if (gen_node_) { + if (tservice->get_extends() != NULL) { + f_service_ << js_const_type_ << tservice->get_extends()->get_name() << " = require('./" + << tservice->get_extends()->get_name() << "');" << endl << js_const_type_ + << tservice->get_extends()->get_name() + << "Client = " << tservice->get_extends()->get_name() << ".Client;" << endl + << js_const_type_ << tservice->get_extends()->get_name() + << "Processor = " << tservice->get_extends()->get_name() << ".Processor;" << endl; + } + + f_service_ << js_const_type_ << "ttypes = require('./" + program_->get_name() + "_types');" << endl; + } + + generate_service_helpers(tservice); + generate_service_interface(tservice); + generate_service_client(tservice); + + if (gen_node_) { + generate_service_processor(tservice); + } + + f_service_.close(); + if (gen_ts_) { + if (!ts_module_.empty()) { + if (gen_node_) { + f_service_ts_ << "}" << endl; + } else { + indent_down(); + f_service_ts_ << ts_indent() << "}" << endl; + f_service_ts_ << "}" << endl; + } + } + f_service_ts_.close(); + } +} + +/** + * Generates a service server definition. + * + * @param tservice The service to generate a server for. + */ +void t_js_generator::generate_service_processor(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + if (gen_node_) { + string prefix = has_js_namespace(tservice->get_program()) ? js_namespace(tservice->get_program()) : js_const_type_; + f_service_ << prefix << service_name_ << "Processor = " << "exports.Processor"; + if (gen_ts_) { + f_service_ts_ << endl << "declare class Processor "; + if (tservice->get_extends() != NULL) { + f_service_ts_ << "extends " << tservice->get_extends()->get_name() << "Processor "; + } + f_service_ts_ << "{" << endl; + indent_up(); + f_service_ts_ << ts_indent() << "private _handler: object;" << endl << endl; + f_service_ts_ << ts_indent() << "constructor(handler: object);" << endl; + f_service_ts_ << ts_indent() << "process(input: thrift.TProtocol, output: thrift.TProtocol): void;" << endl; + indent_down(); + } + } else { + f_service_ << js_namespace(tservice->get_program()) << service_name_ << "Processor = " + << "exports.Processor"; + } + + bool is_subclass_service = tservice->get_extends() != NULL; + + // ES6 Constructor + if (gen_es6_) { + if (is_subclass_service) { + f_service_ << " = class extends " << tservice->get_extends()->get_name() << "Processor {" << endl; + } else { + f_service_ << " = class {" << endl; + } + indent_up(); + indent(f_service_) << "constructor(handler) {" << endl; + } else { + f_service_ << " = function(handler) {" << endl; + } + + indent_up(); + if (gen_es6_ && is_subclass_service) { + indent(f_service_) << "super(handler);" << endl; + } + indent(f_service_) << "this._handler = handler;" << endl; + indent_down(); + + // Done with constructor + if (gen_es6_) { + indent(f_service_) << "}" << endl; + } else { + indent(f_service_) << "};" << endl; + } + + // ES5 service inheritance + if (!gen_es6_ && is_subclass_service) { + indent(f_service_) << "Thrift.inherits(" << js_namespace(tservice->get_program()) + << service_name_ << "Processor, " << tservice->get_extends()->get_name() + << "Processor);" << endl; + } + + // Generate the server implementation + if (gen_es6_) { + indent(f_service_) << "process (input, output) {" << endl; + } else { + indent(f_service_) << js_namespace(tservice->get_program()) << service_name_ + << "Processor.prototype.process = function(input, output) {" << endl; + } + + indent_up(); + + indent(f_service_) << js_const_type_ << "r = input.readMessageBegin();" << endl << indent() + << "if (this['process_' + r.fname]) {" << endl << indent() + << " return this['process_' + r.fname].call(this, r.rseqid, input, output);" << endl + << indent() << "} else {" << endl << indent() << " input.skip(Thrift.Type.STRUCT);" + << endl << indent() << " input.readMessageEnd();" << endl << indent() + << " " << js_const_type_ << "x = new " + "Thrift.TApplicationException(Thrift.TApplicationExceptionType.UNKNOWN_METHOD, " + "'Unknown function ' + r.fname);" << endl << indent() + << " output.writeMessageBegin(r.fname, Thrift.MessageType.EXCEPTION, r.rseqid);" + << endl << indent() << " x.write(output);" << endl << indent() + << " output.writeMessageEnd();" << endl << indent() << " output.flush();" << endl + << indent() << "}" << endl; + + indent_down(); + if (gen_es6_) { + indent(f_service_) << "}" << endl; + } else { + indent(f_service_) << "};" << endl; + } + + // Generate the process subfunctions + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_process_function(tservice, *f_iter); + } + + // Close off the processor class definition + if (gen_es6_) { + indent_down(); + indent(f_service_) << "};" << endl; + } + if (gen_node_ && gen_ts_) { + f_service_ts_ << "}" << endl; + } +} + +/** + * Generates a process function definition. + * + * @param tfunction The function to write a dispatcher for + */ +void t_js_generator::generate_process_function(t_service* tservice, t_function* tfunction) { + if (gen_es6_) { + indent(f_service_) << "process_" + tfunction->get_name() + " (seqid, input, output) {" << endl; + } else { + indent(f_service_) << js_namespace(tservice->get_program()) << service_name_ + << "Processor.prototype.process_" + tfunction->get_name() + + " = function(seqid, input, output) {" << endl; + } + if (gen_ts_) { + indent_up(); + f_service_ts_ << ts_indent() << "process_" << tfunction->get_name() << "(seqid: number, input: thrift.TProtocol, output: thrift.TProtocol): void;" << endl; + indent_down(); + } + + indent_up(); + + string argsname = js_namespace(program_) + service_name_ + "_" + tfunction->get_name() + "_args"; + string resultname = js_namespace(program_) + service_name_ + "_" + tfunction->get_name() + + "_result"; + + indent(f_service_) << js_const_type_ << "args = new " << argsname << "();" << endl << indent() + << "args.read(input);" << endl << indent() << "input.readMessageEnd();" << endl; + + // Generate the function call + t_struct* arg_struct = tfunction->get_arglist(); + const std::vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + + // Shortcut out here for oneway functions + if (tfunction->is_oneway()) { + indent(f_service_) << "this._handler." << tfunction->get_name() << "("; + + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << "args." << (*f_iter)->get_name(); + } + + f_service_ << ");" << endl; + indent_down(); + + if (gen_es6_) { + indent(f_service_) << "}" << endl; + } else { + indent(f_service_) << "};" << endl; + } + return; + } + + // Promise style invocation + indent(f_service_) << "if (this._handler." << tfunction->get_name() + << ".length === " << fields.size() << ") {" << endl; + indent_up(); + + if (gen_es6_) { + indent(f_service_) << "Promise.resolve(this._handler." << tfunction->get_name() << ".bind(this._handler)(" << endl; + } else { + string maybeComma = (fields.size() > 0 ? "," : ""); + indent(f_service_) << "Q.fcall(this._handler." << tfunction->get_name() << ".bind(this._handler)" + << maybeComma << endl; + } + + indent_up(); + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + string maybeComma = (f_iter != fields.end() - 1 ? "," : ""); + indent(f_service_) << "args." << (*f_iter)->get_name() << maybeComma << endl; + } + indent_down(); + + if (gen_es6_) { + indent(f_service_) << ")).then(result => {" << endl; + } else { + indent(f_service_) << ").then(function(result) {" << endl; + } + + indent_up(); + f_service_ << indent() << js_const_type_ << "result_obj = new " << resultname << "({success: result});" << endl + << indent() << "output.writeMessageBegin(\"" << tfunction->get_name() + << "\", Thrift.MessageType.REPLY, seqid);" << endl << indent() + << "result_obj.write(output);" << endl << indent() << "output.writeMessageEnd();" << endl + << indent() << "output.flush();" << endl; + indent_down(); + + if (gen_es6_) { + indent(f_service_) << "}).catch(err => {" << endl; + } else { + indent(f_service_) << "}).catch(function (err) {" << endl; + } + indent_up(); + indent(f_service_) << js_let_type_ << "result;" << endl; + + bool has_exception = false; + t_struct* exceptions = tfunction->get_xceptions(); + if (exceptions) { + const vector<t_field*>& members = exceptions->get_members(); + for (auto member : members) { + t_type* t = get_true_type(member->get_type()); + if (t->is_xception()) { + if (!has_exception) { + has_exception = true; + indent(f_service_) << "if (err instanceof " << js_type_namespace(t->get_program()) + << t->get_name(); + } else { + f_service_ << " || err instanceof " << js_type_namespace(t->get_program()) + << t->get_name(); + } + } + } + } + + if (has_exception) { + f_service_ << ") {" << endl; + indent_up(); + f_service_ << indent() << "result = new " << resultname << "(err);" << endl << indent() + << "output.writeMessageBegin(\"" << tfunction->get_name() + << "\", Thrift.MessageType.REPLY, seqid);" << endl; + + indent_down(); + indent(f_service_) << "} else {" << endl; + indent_up(); + } + + f_service_ << indent() << "result = new " + "Thrift.TApplicationException(Thrift.TApplicationExceptionType.UNKNOWN," + " err.message);" << endl << indent() << "output.writeMessageBegin(\"" + << tfunction->get_name() << "\", Thrift.MessageType.EXCEPTION, seqid);" << endl; + + if (has_exception) { + indent_down(); + indent(f_service_) << "}" << endl; + } + + f_service_ << indent() << "result.write(output);" << endl << indent() + << "output.writeMessageEnd();" << endl << indent() << "output.flush();" << endl; + indent_down(); + indent(f_service_) << "});" << endl; + indent_down(); + // End promise style invocation + + // Callback style invocation + indent(f_service_) << "} else {" << endl; + indent_up(); + indent(f_service_) << "this._handler." << tfunction->get_name() << "("; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + f_service_ << "args." << (*f_iter)->get_name() << ", "; + } + + if (gen_es6_) { + f_service_ << "(err, result) => {" << endl; + } else { + f_service_ << "function (err, result) {" << endl; + } + indent_up(); + indent(f_service_) << js_let_type_ << "result_obj;" << endl; + + indent(f_service_) << "if ((err === null || typeof err === 'undefined')"; + if (has_exception) { + const vector<t_field*>& members = exceptions->get_members(); + for (auto member : members) { + t_type* t = get_true_type(member->get_type()); + if (t->is_xception()) { + f_service_ << " || err instanceof " << js_type_namespace(t->get_program()) << t->get_name(); + } + } + } + f_service_ << ") {" << endl; + indent_up(); + f_service_ << indent() << "result_obj = new " << resultname + << "((err !== null || typeof err === 'undefined') ? err : {success: result});" << endl << indent() + << "output.writeMessageBegin(\"" << tfunction->get_name() + << "\", Thrift.MessageType.REPLY, seqid);" << endl; + indent_down(); + indent(f_service_) << "} else {" << endl; + indent_up(); + f_service_ << indent() << "result_obj = new " + "Thrift.TApplicationException(Thrift.TApplicationExceptionType.UNKNOWN," + " err.message);" << endl << indent() << "output.writeMessageBegin(\"" + << tfunction->get_name() << "\", Thrift.MessageType.EXCEPTION, seqid);" << endl; + indent_down(); + f_service_ << indent() << "}" << endl << indent() << "result_obj.write(output);" << endl << indent() + << "output.writeMessageEnd();" << endl << indent() << "output.flush();" << endl; + + indent_down(); + indent(f_service_) << "});" << endl; + indent_down(); + indent(f_service_) << "}" << endl; + // End callback style invocation + + indent_down(); + + if (gen_es6_) { + indent(f_service_) << "}" << endl; + } else { + indent(f_service_) << "};" << endl; + } +} + +/** + * Generates helper functions for a service. + * + * @param tservice The service to generate a header definition for + */ +void t_js_generator::generate_service_helpers(t_service* tservice) { + // Do not generate TS definitions for helper functions + bool gen_ts_tmp = gen_ts_; + gen_ts_ = false; + + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + f_service_ << "//HELPER FUNCTIONS AND STRUCTURES" << endl << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* ts = (*f_iter)->get_arglist(); + string name = ts->get_name(); + ts->set_name(service_name_ + "_" + name); + generate_js_struct_definition(f_service_, ts, false, false); + generate_js_function_helpers(*f_iter); + ts->set_name(name); + } + + gen_ts_ = gen_ts_tmp; +} + +/** + * Generates a struct and helpers for a function. + * + * @param tfunction The function + */ +void t_js_generator::generate_js_function_helpers(t_function* tfunction) { + t_struct result(program_, service_name_ + "_" + tfunction->get_name() + "_result"); + t_field success(tfunction->get_returntype(), "success", 0); + if (!tfunction->get_returntype()->is_void()) { + result.append(&success); + } + + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& fields = xs->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + result.append(*f_iter); + } + + generate_js_struct_definition(f_service_, &result, false, false); +} + +/** + * Generates a service interface definition. + * + * @param tservice The service to generate a header definition for + */ +void t_js_generator::generate_service_interface(t_service* tservice) { + (void)tservice; +} + +/** + * Generates a REST interface + */ +void t_js_generator::generate_service_rest(t_service* tservice) { + (void)tservice; +} + +/** + * Generates a service client definition. + * + * @param tservice The service to generate a server for. + */ +void t_js_generator::generate_service_client(t_service* tservice) { + + bool is_subclass_service = tservice->get_extends() != NULL; + + if (gen_node_) { + string prefix = has_js_namespace(tservice->get_program()) ? js_namespace(tservice->get_program()) : js_const_type_; + f_service_ << prefix << service_name_ << "Client = " << "exports.Client"; + if (gen_ts_) { + f_service_ts_ << ts_print_doc(tservice) << ts_indent() << ts_declare() << "class " + << "Client "; + if (tservice->get_extends() != NULL) { + f_service_ts_ << "extends " << tservice->get_extends()->get_name() << "Client "; + } + f_service_ts_ << "{" << endl; + } + } else { + f_service_ << js_namespace(tservice->get_program()) << service_name_ + << "Client"; + if (gen_ts_) { + f_service_ts_ << ts_print_doc(tservice) << ts_indent() << ts_declare() << "class " + << service_name_ << "Client "; + if (is_subclass_service) { + f_service_ts_ << "extends " << tservice->get_extends()->get_name() << "Client "; + } + f_service_ts_ << "{" << endl; + } + } + + // ES6 Constructor + if (gen_es6_) { + if (is_subclass_service) { + f_service_ << " = class extends " << js_namespace(tservice->get_extends()->get_program()) + << tservice->get_extends()->get_name() << "Client {" << endl; + } else { + f_service_ << " = class {" << endl; + } + indent_up(); + if (gen_node_) { + indent(f_service_) << "constructor(output, pClass) {" << endl; + } else { + indent(f_service_) << "constructor(input, output) {" << endl; + } + } else { + if (gen_node_) { + f_service_ << " = function(output, pClass) {" << endl; + } else { + f_service_ << " = function(input, output) {" << endl; + } + } + + indent_up(); + + if (gen_node_) { + indent(f_service_) << "this.output = output;" << endl; + indent(f_service_) << "this.pClass = pClass;" << endl; + indent(f_service_) << "this._seqid = 0;" << endl; + indent(f_service_) << "this._reqs = {};" << endl; + if (gen_ts_) { + f_service_ts_ << ts_indent() << "private output: thrift.TTransport;" << endl + << ts_indent() << "private pClass: thrift.TProtocol;" << endl + << ts_indent() << "private _seqid: number;" << endl + << endl + << ts_indent() << "constructor(output: thrift.TTransport, pClass: { new(trans: thrift.TTransport): thrift.TProtocol });" + << endl; + } + } else { + indent(f_service_) << "this.input = input;" << endl; + indent(f_service_) << "this.output = (!output) ? input : output;" << endl; + indent(f_service_) << "this.seqid = 0;" << endl; + if (gen_ts_) { + f_service_ts_ << ts_indent() << "input: Thrift.TJSONProtocol;" << endl << ts_indent() + << "output: Thrift.TJSONProtocol;" << endl << ts_indent() << "seqid: number;" + << endl << endl << ts_indent() + << "constructor(input: Thrift.TJSONProtocol, output?: Thrift.TJSONProtocol);" + << endl; + } + } + + indent_down(); + + if (gen_es6_) { + indent(f_service_) << "}" << endl; + } else { + indent(f_service_) << "};" << endl; + if (is_subclass_service) { + indent(f_service_) << "Thrift.inherits(" << js_namespace(tservice->get_program()) + << service_name_ << "Client, " + << js_namespace(tservice->get_extends()->get_program()) + << tservice->get_extends()->get_name() << "Client);" << endl; + } else { + // init prototype + indent(f_service_) << js_namespace(tservice->get_program()) << service_name_ + << "Client.prototype = {};" << endl; + } + } + + // utils for multiplexed services + if (gen_node_) { + if (gen_es6_) { + indent(f_service_) << "seqid () { return this._seqid; }" << endl; + indent(f_service_) << "new_seqid () { return this._seqid += 1; }" << endl; + } else { + indent(f_service_) << js_namespace(tservice->get_program()) << service_name_ + << "Client.prototype.seqid = function() { return this._seqid; };" << endl + << js_namespace(tservice->get_program()) << service_name_ + << "Client.prototype.new_seqid = function() { return this._seqid += 1; };" + << endl; + } + } + + // Generate client method implementations + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* arg_struct = (*f_iter)->get_arglist(); + const vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator fld_iter; + string funname = (*f_iter)->get_name(); + string arglist = argument_list(arg_struct); + + // Open function + f_service_ << endl; + if (gen_es6_) { + indent(f_service_) << funname << " (" << arglist << ") {" << endl; + } else { + indent(f_service_) << js_namespace(tservice->get_program()) << service_name_ << "Client.prototype." + << function_signature(*f_iter, "", !gen_es6_) << " {" << endl; + } + + indent_up(); + + if (gen_ts_) { + // function definition without callback + f_service_ts_ << ts_print_doc(*f_iter) << ts_indent() << ts_function_signature(*f_iter, false) << endl; + if (!gen_es6_) { + // overload with callback + f_service_ts_ << ts_print_doc(*f_iter) << ts_indent() << ts_function_signature(*f_iter, true) << endl; + } else { + // overload with callback + f_service_ts_ << ts_print_doc(*f_iter) << ts_indent() << ts_function_signature(*f_iter, true) << endl; + } + } + + if (gen_es6_ && gen_node_) { + indent(f_service_) << "this._seqid = this.new_seqid();" << endl; + indent(f_service_) << js_const_type_ << "self = this;" << endl << indent() + << "return new Promise((resolve, reject) => {" << endl; + indent_up(); + indent(f_service_) << "self._reqs[self.seqid()] = (error, result) => {" << endl; + indent_up(); + indent(f_service_) << "return error ? reject(error) : resolve(result);" << endl; + indent_down(); + indent(f_service_) << "};" << endl; + indent(f_service_) << "self.send_" << funname << "(" << arglist << ");" << endl; + indent_down(); + indent(f_service_) << "});" << endl; + } else if (gen_node_) { // Node.js output ./gen-nodejs + f_service_ << indent() << "this._seqid = this.new_seqid();" << endl << indent() + << "if (callback === undefined) {" << endl; + indent_up(); + f_service_ << indent() << js_const_type_ << "_defer = Q.defer();" << endl << indent() + << "this._reqs[this.seqid()] = function(error, result) {" << endl; + indent_up(); + indent(f_service_) << "if (error) {" << endl; + indent_up(); + indent(f_service_) << "_defer.reject(error);" << endl; + indent_down(); + indent(f_service_) << "} else {" << endl; + indent_up(); + indent(f_service_) << "_defer.resolve(result);" << endl; + indent_down(); + indent(f_service_) << "}" << endl; + indent_down(); + indent(f_service_) << "};" << endl; + f_service_ << indent() << "this.send_" << funname << "(" << arglist << ");" << endl + << indent() << "return _defer.promise;" << endl; + indent_down(); + indent(f_service_) << "} else {" << endl; + indent_up(); + f_service_ << indent() << "this._reqs[this.seqid()] = callback;" << endl << indent() + << "this.send_" << funname << "(" << arglist << ");" << endl; + indent_down(); + indent(f_service_) << "}" << endl; + } else if (gen_es6_) { + f_service_ << indent() << js_const_type_ << "self = this;" << endl << indent() + << "return new Promise((resolve, reject) => {" << endl; + indent_up(); + f_service_ << indent() << "self.send_" << funname << "(" << arglist + << (arglist.empty() ? "" : ", ") << "(error, result) => {" << endl; + indent_up(); + indent(f_service_) << "return error ? reject(error) : resolve(result);" << endl; + indent_down(); + f_service_ << indent() << "});" << endl; + indent_down(); + f_service_ << indent() << "});" << endl; + + } else if (gen_jquery_) { // jQuery output ./gen-js + f_service_ << indent() << "if (callback === undefined) {" << endl; + indent_up(); + f_service_ << indent() << "this.send_" << funname << "(" << arglist << ");" << endl; + if (!(*f_iter)->is_oneway()) { + f_service_ << indent(); + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_ << "return "; + } + f_service_ << "this.recv_" << funname << "();" << endl; + } + indent_down(); + f_service_ << indent() << "} else {" << endl; + indent_up(); + f_service_ << indent() << js_const_type_ << "postData = this.send_" << funname << "(" << arglist + << (arglist.empty() ? "" : ", ") << "true);" << endl; + f_service_ << indent() << "return this.output.getTransport()" << endl; + indent_up(); + f_service_ << indent() << ".jqRequest(this, postData, arguments, this.recv_" << funname + << ");" << endl; + indent_down(); + indent_down(); + f_service_ << indent() << "}" << endl; + } else { // Standard JavaScript ./gen-js + f_service_ << indent() << "this.send_" << funname << "(" << arglist + << (arglist.empty() ? "" : ", ") << "callback); " << endl; + if (!(*f_iter)->is_oneway()) { + f_service_ << indent() << "if (!callback) {" << endl; + f_service_ << indent(); + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_ << " return "; + } + f_service_ << "this.recv_" << funname << "();" << endl; + f_service_ << indent() << "}" << endl; + } + } + + indent_down(); + + if (gen_es6_) { + indent(f_service_) << "}" << endl << endl; + } else { + indent(f_service_) << "};" << endl << endl; + } + + // Send function + if (gen_es6_) { + if (gen_node_) { + indent(f_service_) << "send_" << funname << " (" << arglist << ") {" << endl; + } else { + // ES6 js still uses callbacks here. Should refactor this to promise style later.. + indent(f_service_) << "send_" << funname << " (" << argument_list(arg_struct, true) << ") {" << endl; + } + } else { + indent(f_service_) << js_namespace(tservice->get_program()) << service_name_ << "Client.prototype.send_" + << function_signature(*f_iter, "", !gen_node_) << " {" << endl; + } + + indent_up(); + + std::string outputVar; + if (gen_node_) { + f_service_ << indent() << js_const_type_ << "output = new this.pClass(this.output);" << endl; + outputVar = "output"; + } else { + outputVar = "this.output"; + } + + std::string argsname = js_namespace(program_) + service_name_ + "_" + (*f_iter)->get_name() + + "_args"; + + std::string messageType = (*f_iter)->is_oneway() ? "Thrift.MessageType.ONEWAY" + : "Thrift.MessageType.CALL"; + // Build args + if (fields.size() > 0){ + f_service_ << indent() << js_const_type_ << "params = {" << endl; + indent_up(); + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + indent(f_service_) << (*fld_iter)->get_name() << ": " << (*fld_iter)->get_name(); + if (fld_iter != fields.end()-1) { + f_service_ << "," << endl; + } else { + f_service_ << endl; + } + } + indent_down(); + indent(f_service_) << "};" << endl; + indent(f_service_) << js_const_type_ << "args = new " << argsname << "(params);" << endl; + } else { + indent(f_service_) << js_const_type_ << "args = new " << argsname << "();" << endl; + } + + + // Serialize the request header within try/catch + indent(f_service_) << "try {" << endl; + indent_up(); + + if (gen_node_) { + f_service_ << indent() << outputVar << ".writeMessageBegin('" << (*f_iter)->get_name() + << "', " << messageType << ", this.seqid());" << endl; + } else { + f_service_ << indent() << outputVar << ".writeMessageBegin('" << (*f_iter)->get_name() + << "', " << messageType << ", this.seqid);" << endl; + } + + + // Write to the stream + f_service_ << indent() << "args.write(" << outputVar << ");" << endl << indent() << outputVar + << ".writeMessageEnd();" << endl; + + if (gen_node_) { + if((*f_iter)->is_oneway()) { + f_service_ << indent() << "this.output.flush();" << endl; + f_service_ << indent() << js_const_type_ << "callback = this._reqs[this.seqid()] || function() {};" << endl; + f_service_ << indent() << "delete this._reqs[this.seqid()];" << endl; + f_service_ << indent() << "callback(null);" << endl; + } else { + f_service_ << indent() << "return this.output.flush();" << endl; + } + } else { + if (gen_jquery_) { + f_service_ << indent() << "return this.output.getTransport().flush(callback);" << endl; + } else if (gen_es6_) { + f_service_ << indent() << js_const_type_ << "self = this;" << endl; + if((*f_iter)->is_oneway()) { + f_service_ << indent() << "this.output.getTransport().flush(true, null);" << endl; + f_service_ << indent() << "callback();" << endl; + } else { + f_service_ << indent() << "this.output.getTransport().flush(true, () => {" << endl; + indent_up(); + f_service_ << indent() << js_let_type_ << "error = null, result = null;" << endl; + f_service_ << indent() << "try {" << endl; + f_service_ << indent() << " result = self.recv_" << funname << "();" << endl; + f_service_ << indent() << "} catch (e) {" << endl; + f_service_ << indent() << " error = e;" << endl; + f_service_ << indent() << "}" << endl; + f_service_ << indent() << "callback(error, result);" << endl; + indent_down(); + f_service_ << indent() << "});" << endl; + } + } else { + f_service_ << indent() << "if (callback) {" << endl; + indent_up(); + if((*f_iter)->is_oneway()) { + f_service_ << indent() << "this.output.getTransport().flush(true, null);" << endl; + f_service_ << indent() << "callback();" << endl; + } else { + f_service_ << indent() << js_const_type_ << "self = this;" << endl; + f_service_ << indent() << "this.output.getTransport().flush(true, function() {" << endl; + indent_up(); + f_service_ << indent() << js_let_type_ << "result = null;" << endl; + f_service_ << indent() << "try {" << endl; + f_service_ << indent() << " result = self.recv_" << funname << "();" << endl; + f_service_ << indent() << "} catch (e) {" << endl; + f_service_ << indent() << " result = e;" << endl; + f_service_ << indent() << "}" << endl; + f_service_ << indent() << "callback(result);" << endl; + indent_down(); + f_service_ << indent() << "});" << endl; + } + indent_down(); + f_service_ << indent() << "} else {" << endl; + f_service_ << indent() << " return this.output.getTransport().flush();" << endl; + f_service_ << indent() << "}" << endl; + } + } + + indent_down(); + f_service_ << indent() << "}" << endl; + + // Reset the transport and delete registered callback if there was a serialization error + f_service_ << indent() << "catch (e) {" << endl; + indent_up(); + if (gen_node_) { + f_service_ << indent() << "delete this._reqs[this.seqid()];" << endl; + f_service_ << indent() << "if (typeof " << outputVar << ".reset === 'function') {" << endl; + f_service_ << indent() << " " << outputVar << ".reset();" << endl; + f_service_ << indent() << "}" << endl; + } else { + f_service_ << indent() << "if (typeof " << outputVar << ".getTransport().reset === 'function') {" << endl; + f_service_ << indent() << " " << outputVar << ".getTransport().reset();" << endl; + f_service_ << indent() << "}" << endl; + } + f_service_ << indent() << "throw e;" << endl; + indent_down(); + f_service_ << indent() << "}" << endl; + + indent_down(); + + // Close send function + if (gen_es6_) { + indent(f_service_) << "}" << endl; + } else { + indent(f_service_) << "};" << endl; + } + + // Receive function + if (!(*f_iter)->is_oneway()) { + std::string resultname = js_namespace(tservice->get_program()) + service_name_ + "_" + + (*f_iter)->get_name() + "_result"; + + f_service_ << endl; + // Open receive function + if (gen_node_) { + if (gen_es6_) { + indent(f_service_) << "recv_" << (*f_iter)->get_name() << " (input, mtype, rseqid) {" << endl; + } else { + indent(f_service_) << js_namespace(tservice->get_program()) << service_name_ + << "Client.prototype.recv_" << (*f_iter)->get_name() + << " = function(input,mtype,rseqid) {" << endl; + } + } else { + if (gen_es6_) { + indent(f_service_) << "recv_" << (*f_iter)->get_name() << " () {" << endl; + } else { + t_struct noargs(program_); + + t_function recv_function((*f_iter)->get_returntype(), + string("recv_") + (*f_iter)->get_name(), + &noargs); + indent(f_service_) << js_namespace(tservice->get_program()) << service_name_ + << "Client.prototype." << function_signature(&recv_function) << " {" << endl; + } + } + + indent_up(); + + std::string inputVar; + if (gen_node_) { + inputVar = "input"; + } else { + inputVar = "this.input"; + } + + if (gen_node_) { + f_service_ << indent() << js_const_type_ << "callback = this._reqs[rseqid] || function() {};" << endl + << indent() << "delete this._reqs[rseqid];" << endl; + } else { + f_service_ << indent() << js_const_type_ << "ret = this.input.readMessageBegin();" << endl + << indent() << js_const_type_ << "mtype = ret.mtype;" << endl; + } + + f_service_ << indent() << "if (mtype == Thrift.MessageType.EXCEPTION) {" << endl; + + indent_up(); + f_service_ << indent() << js_const_type_ << "x = new Thrift.TApplicationException();" << endl + << indent() << "x.read(" << inputVar << ");" << endl + << indent() << inputVar << ".readMessageEnd();" << endl + << indent() << render_recv_throw("x") << endl; + scope_down(f_service_); + + f_service_ << indent() << js_const_type_ << "result = new " << resultname << "();" << endl << indent() + << "result.read(" << inputVar << ");" << endl; + + f_service_ << indent() << inputVar << ".readMessageEnd();" << endl << endl; + + t_struct* xs = (*f_iter)->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + f_service_ << indent() << "if (null !== result." << (*x_iter)->get_name() << ") {" << endl + << indent() << " " << render_recv_throw("result." + (*x_iter)->get_name()) + << endl << indent() << "}" << endl; + } + + // Careful, only return result if not a void function + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_ << indent() << "if (null !== result.success) {" << endl << indent() << " " + << render_recv_return("result.success") << endl << indent() << "}" << endl; + f_service_ << indent() + << render_recv_throw("'" + (*f_iter)->get_name() + " failed: unknown result'") + << endl; + } else { + if (gen_node_) { + indent(f_service_) << "callback(null);" << endl; + } else { + indent(f_service_) << "return;" << endl; + } + } + + // Close receive function + indent_down(); + if (gen_es6_) { + indent(f_service_) << "}" << endl; + } else { + indent(f_service_) << "};" << endl; + } + } + } + + // Finish class definitions + if (gen_ts_) { + f_service_ts_ << ts_indent() << "}" << endl; + } + if (gen_es6_) { + indent_down(); + f_service_ << "};" << endl; + } +} + +std::string t_js_generator::render_recv_throw(std::string var) { + if (gen_node_) { + return "return callback(" + var + ");"; + } else { + return "throw " + var + ";"; + } +} + +std::string t_js_generator::render_recv_return(std::string var) { + if (gen_node_) { + return "return callback(null, " + var + ");"; + } else { + return "return " + var + ";"; + } +} + +/** + * Deserializes a field of any type. + */ +void t_js_generator::generate_deserialize_field(ostream& out, + t_field* tfield, + string prefix, + bool inclass) { + (void)inclass; + t_type* type = get_true_type(tfield->get_type()); + + if (type->is_void()) { + throw std::runtime_error("CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name()); + } + + string name = prefix + tfield->get_name(); + + if (type->is_struct() || type->is_xception()) { + generate_deserialize_struct(out, (t_struct*)type, name); + } else if (type->is_container()) { + generate_deserialize_container(out, type, name); + } else if (type->is_base_type() || type->is_enum()) { + indent(out) << name << " = input."; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw std::runtime_error("compiler error: cannot serialize void field in a struct: " + name); + break; + case t_base_type::TYPE_STRING: + out << (type->is_binary() ? "readBinary()" : "readString()"); + break; + case t_base_type::TYPE_BOOL: + out << "readBool()"; + break; + case t_base_type::TYPE_I8: + out << "readByte()"; + break; + case t_base_type::TYPE_I16: + out << "readI16()"; + break; + case t_base_type::TYPE_I32: + out << "readI32()"; + break; + case t_base_type::TYPE_I64: + out << "readI64()"; + break; + case t_base_type::TYPE_DOUBLE: + out << "readDouble()"; + break; + default: + throw std::runtime_error("compiler error: no JS name for base type " + t_base_type::t_base_name(tbase)); + } + } else if (type->is_enum()) { + out << "readI32()"; + } + + if (!gen_node_) { + out << ".value"; + } + + out << ";" << endl; + } else { + printf("DO NOT KNOW HOW TO DESERIALIZE FIELD '%s' TYPE '%s'\n", + tfield->get_name().c_str(), + type->get_name().c_str()); + } +} + +/** + * Generates an unserializer for a variable. This makes two key assumptions, + * first that there is a const char* variable named data that points to the + * buffer for deserialization, and that there is a variable protocol which + * is a reference to a TProtocol serialization object. + */ +void t_js_generator::generate_deserialize_struct(ostream& out, t_struct* tstruct, string prefix) { + out << indent() << prefix << " = new " << js_type_namespace(tstruct->get_program()) + << tstruct->get_name() << "();" << endl << indent() << prefix << ".read(input);" << endl; +} + +void t_js_generator::generate_deserialize_container(ostream& out, t_type* ttype, string prefix) { + string size = tmp("_size"); + string rtmp3 = tmp("_rtmp3"); + + t_field fsize(g_type_i32, size); + + // Declare variables, read header + if (ttype->is_map()) { + out << indent() << prefix << " = {};" << endl; + + out << indent() << js_const_type_ << rtmp3 << " = input.readMapBegin();" << endl; + out << indent() << js_const_type_ << size << " = " << rtmp3 << ".size || 0;" << endl; + + } else if (ttype->is_set()) { + + out << indent() << prefix << " = [];" << endl + << indent() << js_const_type_ << rtmp3 << " = input.readSetBegin();" << endl + << indent() << js_const_type_ << size << " = " << rtmp3 << ".size || 0;" << endl; + + } else if (ttype->is_list()) { + + out << indent() << prefix << " = [];" << endl + << indent() << js_const_type_ << rtmp3 << " = input.readListBegin();" << endl + << indent() << js_const_type_ << size << " = " << rtmp3 << ".size || 0;" << endl; + } + + // For loop iterates over elements + string i = tmp("_i"); + indent(out) << "for (" << js_let_type_ << i << " = 0; " << i << " < " << size << "; ++" << i << ") {" << endl; + + indent_up(); + + if (ttype->is_map()) { + if (!gen_node_) { + out << indent() << "if (" << i << " > 0 ) {" << endl << indent() + << " if (input.rstack.length > input.rpos[input.rpos.length -1] + 1) {" << endl + << indent() << " input.rstack.pop();" << endl << indent() << " }" << endl << indent() + << "}" << endl; + } + + generate_deserialize_map_element(out, (t_map*)ttype, prefix); + } else if (ttype->is_set()) { + generate_deserialize_set_element(out, (t_set*)ttype, prefix); + } else if (ttype->is_list()) { + generate_deserialize_list_element(out, (t_list*)ttype, prefix); + } + + scope_down(out); + + // Read container end + if (ttype->is_map()) { + indent(out) << "input.readMapEnd();" << endl; + } else if (ttype->is_set()) { + indent(out) << "input.readSetEnd();" << endl; + } else if (ttype->is_list()) { + indent(out) << "input.readListEnd();" << endl; + } +} + +/** + * Generates code to deserialize a map + */ +void t_js_generator::generate_deserialize_map_element(ostream& out, t_map* tmap, string prefix) { + string key = tmp("key"); + string val = tmp("val"); + t_field fkey(tmap->get_key_type(), key); + t_field fval(tmap->get_val_type(), val); + + indent(out) << declare_field(&fkey, false, false) << ";" << endl; + indent(out) << declare_field(&fval, false, false) << ";" << endl; + + generate_deserialize_field(out, &fkey); + generate_deserialize_field(out, &fval); + + indent(out) << prefix << "[" << key << "] = " << val << ";" << endl; +} + +void t_js_generator::generate_deserialize_set_element(ostream& out, t_set* tset, string prefix) { + string elem = tmp("elem"); + t_field felem(tset->get_elem_type(), elem); + + indent(out) << js_let_type_ << elem << " = null;" << endl; + + generate_deserialize_field(out, &felem); + + indent(out) << prefix << ".push(" << elem << ");" << endl; +} + +void t_js_generator::generate_deserialize_list_element(ostream& out, + t_list* tlist, + string prefix) { + string elem = tmp("elem"); + t_field felem(tlist->get_elem_type(), elem); + + indent(out) << js_let_type_ << elem << " = null;" << endl; + + generate_deserialize_field(out, &felem); + + indent(out) << prefix << ".push(" << elem << ");" << endl; +} + +/** + * Serializes a field of any type. + * + * @param tfield The field to serialize + * @param prefix Name to prepend to field name + */ +void t_js_generator::generate_serialize_field(ostream& out, t_field* tfield, string prefix) { + t_type* type = get_true_type(tfield->get_type()); + + // Do nothing for void types + if (type->is_void()) { + throw std::runtime_error("CANNOT GENERATE SERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name()); + } + + if (type->is_struct() || type->is_xception()) { + generate_serialize_struct(out, (t_struct*)type, prefix + tfield->get_name()); + } else if (type->is_container()) { + generate_serialize_container(out, type, prefix + tfield->get_name()); + } else if (type->is_base_type() || type->is_enum()) { + + string name = tfield->get_name(); + + // Hack for when prefix is defined (always a hash ref) + if (!prefix.empty()) + name = prefix + tfield->get_name(); + + indent(out) << "output."; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw std::runtime_error("compiler error: cannot serialize void field in a struct: " + name); + break; + case t_base_type::TYPE_STRING: + out << (type->is_binary() ? "writeBinary(" : "writeString(") << name << ")"; + break; + case t_base_type::TYPE_BOOL: + out << "writeBool(" << name << ")"; + break; + case t_base_type::TYPE_I8: + out << "writeByte(" << name << ")"; + break; + case t_base_type::TYPE_I16: + out << "writeI16(" << name << ")"; + break; + case t_base_type::TYPE_I32: + out << "writeI32(" << name << ")"; + break; + case t_base_type::TYPE_I64: + out << "writeI64(" << name << ")"; + break; + case t_base_type::TYPE_DOUBLE: + out << "writeDouble(" << name << ")"; + break; + default: + throw std::runtime_error("compiler error: no JS name for base type " + t_base_type::t_base_name(tbase)); + } + } else if (type->is_enum()) { + out << "writeI32(" << name << ")"; + } + out << ";" << endl; + + } else { + printf("DO NOT KNOW HOW TO SERIALIZE FIELD '%s%s' TYPE '%s'\n", + prefix.c_str(), + tfield->get_name().c_str(), + type->get_name().c_str()); + } +} + +/** + * Serializes all the members of a struct. + * + * @param tstruct The struct to serialize + * @param prefix String prefix to attach to all fields + */ +void t_js_generator::generate_serialize_struct(ostream& out, t_struct* tstruct, string prefix) { + (void)tstruct; + indent(out) << prefix << ".write(output);" << endl; +} + +/** + * Writes out a container + */ +void t_js_generator::generate_serialize_container(ostream& out, t_type* ttype, string prefix) { + if (ttype->is_map()) { + indent(out) << "output.writeMapBegin(" << type_to_enum(((t_map*)ttype)->get_key_type()) << ", " + << type_to_enum(((t_map*)ttype)->get_val_type()) << ", " + << "Thrift.objectLength(" << prefix << "));" << endl; + } else if (ttype->is_set()) { + indent(out) << "output.writeSetBegin(" << type_to_enum(((t_set*)ttype)->get_elem_type()) << ", " + << prefix << ".length);" << endl; + + } else if (ttype->is_list()) { + + indent(out) << "output.writeListBegin(" << type_to_enum(((t_list*)ttype)->get_elem_type()) + << ", " << prefix << ".length);" << endl; + } + + if (ttype->is_map()) { + string kiter = tmp("kiter"); + string viter = tmp("viter"); + indent(out) << "for (" << js_let_type_ << kiter << " in " << prefix << ") {" << endl; + indent_up(); + indent(out) << "if (" << prefix << ".hasOwnProperty(" << kiter << ")) {" << endl; + indent_up(); + indent(out) << js_let_type_ << viter << " = " << prefix << "[" << kiter << "];" << endl; + generate_serialize_map_element(out, (t_map*)ttype, kiter, viter); + scope_down(out); + scope_down(out); + + } else if (ttype->is_set()) { + string iter = tmp("iter"); + indent(out) << "for (" << js_let_type_ << iter << " in " << prefix << ") {" << endl; + indent_up(); + indent(out) << "if (" << prefix << ".hasOwnProperty(" << iter << ")) {" << endl; + indent_up(); + indent(out) << iter << " = " << prefix << "[" << iter << "];" << endl; + generate_serialize_set_element(out, (t_set*)ttype, iter); + scope_down(out); + scope_down(out); + + } else if (ttype->is_list()) { + string iter = tmp("iter"); + indent(out) << "for (" << js_let_type_ << iter << " in " << prefix << ") {" << endl; + indent_up(); + indent(out) << "if (" << prefix << ".hasOwnProperty(" << iter << ")) {" << endl; + indent_up(); + indent(out) << iter << " = " << prefix << "[" << iter << "];" << endl; + generate_serialize_list_element(out, (t_list*)ttype, iter); + scope_down(out); + scope_down(out); + } + + if (ttype->is_map()) { + indent(out) << "output.writeMapEnd();" << endl; + } else if (ttype->is_set()) { + indent(out) << "output.writeSetEnd();" << endl; + } else if (ttype->is_list()) { + indent(out) << "output.writeListEnd();" << endl; + } +} + +/** + * Serializes the members of a map. + * + */ +void t_js_generator::generate_serialize_map_element(ostream& out, + t_map* tmap, + string kiter, + string viter) { + t_field kfield(tmap->get_key_type(), kiter); + generate_serialize_field(out, &kfield); + + t_field vfield(tmap->get_val_type(), viter); + generate_serialize_field(out, &vfield); +} + +/** + * Serializes the members of a set. + */ +void t_js_generator::generate_serialize_set_element(ostream& out, t_set* tset, string iter) { + t_field efield(tset->get_elem_type(), iter); + generate_serialize_field(out, &efield); +} + +/** + * Serializes the members of a list. + */ +void t_js_generator::generate_serialize_list_element(ostream& out, t_list* tlist, string iter) { + t_field efield(tlist->get_elem_type(), iter); + generate_serialize_field(out, &efield); +} + +/** + * Declares a field, which may include initialization as necessary. + * + * @param ttype The type + */ +string t_js_generator::declare_field(t_field* tfield, bool init, bool obj) { + string result = "this." + tfield->get_name(); + + if (!obj) { + result = js_let_type_ + tfield->get_name(); + } + + if (init) { + t_type* type = get_true_type(tfield->get_type()); + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + break; + case t_base_type::TYPE_STRING: + case t_base_type::TYPE_BOOL: + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + case t_base_type::TYPE_DOUBLE: + result += " = null"; + break; + default: + throw std::runtime_error("compiler error: no JS initializer for base type " + t_base_type::t_base_name(tbase)); + } + } else if (type->is_enum()) { + result += " = null"; + } else if (type->is_map()) { + result += " = null"; + } else if (type->is_container()) { + result += " = null"; + } else if (type->is_struct() || type->is_xception()) { + if (obj) { + result += " = new " + js_type_namespace(type->get_program()) + type->get_name() + "()"; + } else { + result += " = null"; + } + } + } else { + result += " = null"; + } + return result; +} + +/** + * Renders a function signature of the form 'type name(args)' + * + * @param tfunction Function definition + * @return String of rendered function definition + */ +string t_js_generator::function_signature(t_function* tfunction, + string prefix, + bool include_callback) { + + string str; + + str = prefix + tfunction->get_name() + " = function("; + + str += argument_list(tfunction->get_arglist(), include_callback); + + str += ")"; + return str; +} + +/** + * Renders a field list + */ +string t_js_generator::argument_list(t_struct* tstruct, bool include_callback) { + string result = ""; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + result += ", "; + } + result += (*f_iter)->get_name(); + } + + if (include_callback) { + if (!fields.empty()) { + result += ", "; + } + result += "callback"; + } + + return result; +} + +/** + * Converts the parse type to a C++ enum string for the given type. + */ +string t_js_generator::type_to_enum(t_type* type) { + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw std::runtime_error("NO T_VOID CONSTRUCT"); + case t_base_type::TYPE_STRING: + return "Thrift.Type.STRING"; + case t_base_type::TYPE_BOOL: + return "Thrift.Type.BOOL"; + case t_base_type::TYPE_I8: + return "Thrift.Type.BYTE"; + case t_base_type::TYPE_I16: + return "Thrift.Type.I16"; + case t_base_type::TYPE_I32: + return "Thrift.Type.I32"; + case t_base_type::TYPE_I64: + return "Thrift.Type.I64"; + case t_base_type::TYPE_DOUBLE: + return "Thrift.Type.DOUBLE"; + } + } else if (type->is_enum()) { + return "Thrift.Type.I32"; + } else if (type->is_struct() || type->is_xception()) { + return "Thrift.Type.STRUCT"; + } else if (type->is_map()) { + return "Thrift.Type.MAP"; + } else if (type->is_set()) { + return "Thrift.Type.SET"; + } else if (type->is_list()) { + return "Thrift.Type.LIST"; + } + + throw std::runtime_error("INVALID TYPE IN type_to_enum: " + type->get_name()); +} + +/** + * Converts a t_type to a TypeScript type (string). + * @param t_type Type to convert to TypeScript + * @return String TypeScript type + */ +string t_js_generator::ts_get_type(t_type* type) { + std::string ts_type; + + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + ts_type = "string"; + break; + case t_base_type::TYPE_BOOL: + ts_type = "boolean"; + break; + case t_base_type::TYPE_I8: + ts_type = "any"; + break; + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_DOUBLE: + ts_type = "number"; + break; + case t_base_type::TYPE_I64: + ts_type = "Int64"; + break; + case t_base_type::TYPE_VOID: + ts_type = "void"; + } + } else if (type->is_enum() || type->is_struct() || type->is_xception()) { + std::string type_name; + + if (type->get_program()) { + type_name = js_namespace(type->get_program()); + + // If the type is not defined within the current program, we need to prefix it with the same name as + // the generated "import" statement for the types containing program + if(type->get_program() != program_) { + auto prefix = include_2_import_name.find(type->get_program()); + + if(prefix != include_2_import_name.end()) { + type_name.append(prefix->second); + type_name.append("."); + } + } + } + + type_name.append(type->get_name()); + ts_type = type_name; + } else if (type->is_list() || type->is_set()) { + t_type* etype; + + if (type->is_list()) { + etype = ((t_list*)type)->get_elem_type(); + } else { + etype = ((t_set*)type)->get_elem_type(); + } + + ts_type = ts_get_type(etype) + "[]"; + } else if (type->is_map()) { + string ktype = ts_get_type(((t_map*)type)->get_key_type()); + string vtype = ts_get_type(((t_map*)type)->get_val_type()); + + + if (ktype == "number" || ktype == "string" ) { + ts_type = "{ [k: " + ktype + "]: " + vtype + "; }"; + } else if ((((t_map*)type)->get_key_type())->is_enum()) { + // Not yet supported (enum map): https://github.com/Microsoft/TypeScript/pull/2652 + //ts_type = "{ [k: " + ktype + "]: " + vtype + "; }"; + ts_type = "{ [k: number /*" + ktype + "*/]: " + vtype + "; }"; + } else { + ts_type = "any"; + } + } + + return ts_type; +} + +/** + * Renders a TypeScript function signature of the form 'name(args: types): type;' + * + * @param t_function Function definition + * @param bool in-/exclude the callback argument + * @return String of rendered function definition + */ +std::string t_js_generator::ts_function_signature(t_function* tfunction, bool include_callback) { + string str; + const vector<t_field*>& fields = tfunction->get_arglist()->get_members(); + vector<t_field*>::const_iterator f_iter; + + str = tfunction->get_name() + "("; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + str += (*f_iter)->get_name() + ts_get_req(*f_iter) + ": " + ts_get_type((*f_iter)->get_type()); + + if (f_iter + 1 != fields.end() || (include_callback && fields.size() > 0)) { + str += ", "; + } + } + + if (include_callback) { + if (gen_node_) { + t_struct* exceptions = tfunction->get_xceptions(); + string exception_types; + if (exceptions) { + const vector<t_field*>& members = exceptions->get_members(); + for (vector<t_field*>::const_iterator it = members.begin(); it != members.end(); ++it) { + t_type* t = get_true_type((*it)->get_type()); + if (it == members.begin()) { + exception_types = js_type_namespace(t->get_program()) + t->get_name(); + } else { + exception_types += " | " + js_type_namespace(t->get_program()) + t->get_name(); + } + } + } + if (exception_types == "") { + str += "callback?: (error: void, response: " + ts_get_type(tfunction->get_returntype()) + ")=>void): "; + } else { + str += "callback?: (error: " + exception_types + ", response: " + ts_get_type(tfunction->get_returntype()) + ")=>void): "; + } + } else { + str += "callback?: (data: " + ts_get_type(tfunction->get_returntype()) + ")=>void): "; + } + + if (gen_jquery_) { + str += "JQueryPromise<" + ts_get_type(tfunction->get_returntype()) +">;"; + } else { + str += "void;"; + } + } else { + if (gen_es6_) { + str += "): Promise<" + ts_get_type(tfunction->get_returntype()) + ">;"; + } + else { + str += "): " + ts_get_type(tfunction->get_returntype()) + ";"; + } + } + + return str; +} + +/** + * Takes a name and produces a valid NodeJS identifier from it + * + * @param name The name which shall become a valid NodeJS identifier + * @return The modified name with the updated identifier + */ +std::string t_js_generator::make_valid_nodeJs_identifier(std::string const& name) { + std::string str = name; + if (str.empty()) { + return str; + } + + // tests rely on this + assert(('A' < 'Z') && ('a' < 'z') && ('0' < '9')); + + // if the first letter is a number, we add an additional underscore in front of it + char c = str.at(0); + if (('0' <= c) && (c <= '9')) { + str = "_" + str; + } + + // following chars: letter, number or underscore + for (size_t i = 0; i < str.size(); ++i) { + c = str.at(i); + if ((('A' > c) || (c > 'Z')) && (('a' > c) || (c > 'z')) && (('0' > c) || (c > '9')) + && ('_' != c) && ('$' != c)) { + str.replace(i, 1, "_"); + } + } + + return str; +} + +void t_js_generator::parse_imports(t_program* program, const std::string& imports_string) { + if (program->get_recursive()) { + throw std::invalid_argument("[-gen js:imports=] option is not usable in recursive code generation mode"); + } + std::stringstream sstream(imports_string); + std::string import; + while (std::getline(sstream, import, ':')) { + imports.emplace_back(import); + } + if (imports.empty()) { + throw std::invalid_argument("invalid usage: [-gen js:imports=] requires at least one path " + "(multiple paths are separated by ':')"); + } + for (auto& import : imports) { + // Strip trailing '/' + if (!import.empty() && import[import.size() - 1] == '/') { + import = import.substr(0, import.size() - 1); + } + if (import.empty()) { + throw std::invalid_argument("empty paths are not allowed in imports"); + } + std::ifstream episode_file; + string line; + const auto episode_file_path = import + "/" + episode_file_name; + episode_file.open(episode_file_path); + if (!episode_file) { + throw std::runtime_error("failed to open the file '" + episode_file_path + "'"); + } + while (std::getline(episode_file, line)) { + const auto separator_position = line.find(':'); + if (separator_position == string::npos) { + // could not find the separator in the line + throw std::runtime_error("the episode file '" + episode_file_path + "' is malformed, the line '" + line + + "' does not have a key:value separator ':'"); + } + const auto module_name = line.substr(0, separator_position); + const auto import_path = line.substr(separator_position + 1); + if (module_name.empty()) { + throw std::runtime_error("the episode file '" + episode_file_path + "' is malformed, the module name is empty"); + } + if (import_path.empty()) { + throw std::runtime_error("the episode file '" + episode_file_path + "' is malformed, the import path is empty"); + } + const auto module_import_path = import.substr(import.find_last_of('/') + 1) + "/" + import_path; + const auto result = module_name_2_import_path.emplace(module_name, module_import_path); + if (!result.second) { + throw std::runtime_error("multiple providers of import path found for " + module_name + + "\n\t" + module_import_path + "\n\t" + result.first->second); + } + } + } +} +void t_js_generator::parse_thrift_package_output_directory(const std::string& thrift_package_output_directory) { + thrift_package_output_directory_ = thrift_package_output_directory; + // Strip trailing '/' + if (!thrift_package_output_directory_.empty() && thrift_package_output_directory_[thrift_package_output_directory_.size() - 1] == '/') { + thrift_package_output_directory_ = thrift_package_output_directory_.substr(0, thrift_package_output_directory_.size() - 1); + } + // Check that the thrift_package_output_directory is not empty after stripping + if (thrift_package_output_directory_.empty()) { + throw std::invalid_argument("the thrift_package_output_directory argument must not be empty"); + } else { + gen_episode_file_ = true; + } +} + +THRIFT_REGISTER_GENERATOR(js, + "Javascript", + " jquery: Generate jQuery compatible code.\n" + " node: Generate node.js compatible code.\n" + " ts: Generate TypeScript definition files.\n" + " with_ns: Create global namespace objects when using node.js\n" + " es6: Create ES6 code with Promises\n" + " thrift_package_output_directory=<path>:\n" + " Generate episode file and use the <path> as prefix\n" + " imports=<paths_to_modules>:\n" + " ':' separated list of paths of modules that has episode files in their root\n") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_json_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_json_generator.cc new file mode 100644 index 000000000..eb4ef790b --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_json_generator.cc @@ -0,0 +1,793 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +#include <fstream> +#include <iostream> +#include <sstream> +#include <limits> + +#include <stdlib.h> +#include <sys/stat.h> +#include <sstream> + +#include "thrift/platform.h" +#include "thrift/generate/t_generator.h" + +using std::map; +using std::ofstream; +using std::ostream; +using std::ostringstream; +using std::string; +using std::stringstream; +using std::vector; +using std::stack; + +static const string endl = "\n"; +static const string quot = "\""; +static const bool NO_INDENT = false; +static const bool FORCE_STRING = true; + +class t_json_generator : public t_generator { +public: + t_json_generator(t_program* program, + const std::map<std::string, std::string>& parsed_options, + const std::string& option_string) + : t_generator(program) { + (void)option_string; + std::map<std::string, std::string>::const_iterator iter; + + should_merge_includes_ = false; + for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + if( iter->first.compare("merge") == 0) { + should_merge_includes_ = true; + } else { + throw "unknown option json:" + iter->first; + } + } + + out_dir_base_ = "gen-json"; + } + + ~t_json_generator() override = default; + + /** + * Init and close methods + */ + + void init_generator() override; + void close_generator() override; + + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_program() override; + void generate_function(t_function* tfunc); + void generate_field(t_field* field); + + void generate_service(t_service* tservice) override; + void generate_struct(t_struct* tstruct) override; + +private: + bool should_merge_includes_; + + ofstream_with_content_based_conditional_update f_json_; + std::stack<bool> comma_needed_; + + template <typename T> + string number_to_string(T t) { + std::ostringstream out; + out.imbue(std::locale::classic()); + out.precision(std::numeric_limits<T>::digits10); + out << t; + return out.str(); + } + + template <typename T> + void write_number(T n) { + f_json_ << number_to_string(n); + } + + string get_type_name(t_type* ttype); + string get_qualified_name(t_type* ttype); + + void start_object(bool should_indent = true); + void start_array(); + void end_object(); + void end_array(); + void write_comma_if_needed(); + void indicate_comma_needed(); + string escape_json_string(const string& input); + string json_str(const string& str); + void merge_includes(t_program*); + + void generate_constant(t_const* con); + + void write_type_spec_entry(const char* name, t_type* ttype); + void write_type_spec_object(const char* name, t_type* ttype); + void write_type_spec(t_type* ttype); + void write_string(const string& value); + void write_value(t_type* tvalue); + void write_const_value(t_const_value* value, bool force_string = false); + void write_key_and(string key); + void write_key_and_string(string key, string val); + void write_key_and_integer(string key, int val); + void write_key_and_bool(string key, bool val); +}; + +void t_json_generator::init_generator() { + MKDIR(get_out_dir().c_str()); + + string f_json_name = get_out_dir() + program_->get_name() + ".json"; + f_json_.open(f_json_name.c_str()); + + // Merge all included programs into this one so we can output one big file. + if (should_merge_includes_) { + merge_includes(program_); + } +} + +string t_json_generator::escape_json_string(const string& input) { + std::ostringstream ss; + for (char iter : input) { + switch (iter) { + case '\\': + ss << "\\\\"; + break; + case '"': + ss << "\\\""; + break; + case '/': + ss << "\\/"; + break; + case '\b': + ss << "\\b"; + break; + case '\f': + ss << "\\f"; + break; + case '\n': + ss << "\\n"; + break; + case '\r': + ss << "\\r"; + break; + case '\t': + ss << "\\t"; + break; + default: + ss << iter; + break; + } + } + return ss.str(); +} + +void t_json_generator::start_object(bool should_indent) { + f_json_ << (should_indent ? indent() : "") << "{" << endl; + indent_up(); + comma_needed_.push(false); +} + +void t_json_generator::start_array() { + f_json_ << "[" << endl; + indent_up(); + comma_needed_.push(false); +} + +void t_json_generator::write_comma_if_needed() { + if (comma_needed_.top()) { + f_json_ << "," << endl; + } +} + +void t_json_generator::indicate_comma_needed() { + comma_needed_.pop(); + comma_needed_.push(true); +} + +void t_json_generator::write_key_and(string key) { + write_comma_if_needed(); + indent(f_json_) << json_str(key) << ": "; + indicate_comma_needed(); +} + +void t_json_generator::write_key_and_integer(string key, int val) { + write_comma_if_needed(); + indent(f_json_) << json_str(key) << ": " << number_to_string(val); + indicate_comma_needed(); +} + +void t_json_generator::write_key_and_string(string key, string val) { + write_comma_if_needed(); + indent(f_json_) << json_str(key) << ": " << json_str(val); + indicate_comma_needed(); +} + +void t_json_generator::write_key_and_bool(string key, bool val) { + write_comma_if_needed(); + indent(f_json_) << json_str(key) << ": " << (val ? "true" : "false"); + indicate_comma_needed(); +} + +void t_json_generator::end_object() { + indent_down(); + f_json_ << endl << indent() << "}"; + comma_needed_.pop(); +} + +void t_json_generator::end_array() { + indent_down(); + if (comma_needed_.top()) { + f_json_ << endl; + } + indent(f_json_) << "]"; + comma_needed_.pop(); +} + +void t_json_generator::write_type_spec_object(const char* name, t_type* ttype) { + ttype = ttype->get_true_type(); + if (ttype->is_struct() || ttype->is_xception() || ttype->is_container()) { + write_key_and(name); + start_object(NO_INDENT); + write_key_and("typeId"); + write_type_spec(ttype); + end_object(); + } +} + +void t_json_generator::write_type_spec_entry(const char* name, t_type* ttype) { + write_key_and(name); + write_type_spec(ttype); +} + +void t_json_generator::write_type_spec(t_type* ttype) { + ttype = ttype->get_true_type(); + + write_string(get_type_name(ttype)); + + if (ttype->annotations_.size() > 0) { + write_key_and("annotations"); + start_object(); + for (auto & annotation : ttype->annotations_) { + write_key_and_string(annotation.first, annotation.second); + } + end_object(); + } + + if (ttype->is_struct() || ttype->is_xception()) { + write_key_and_string("class", get_qualified_name(ttype)); + } else if (ttype->is_map()) { + t_type* ktype = ((t_map*)ttype)->get_key_type(); + t_type* vtype = ((t_map*)ttype)->get_val_type(); + write_key_and_string("keyTypeId", get_type_name(ktype)); + write_key_and_string("valueTypeId", get_type_name(vtype)); + write_type_spec_object("keyType", ktype); + write_type_spec_object("valueType", vtype); + } else if (ttype->is_list()) { + t_type* etype = ((t_list*)ttype)->get_elem_type(); + write_key_and_string("elemTypeId", get_type_name(etype)); + write_type_spec_object("elemType", etype); + } else if (ttype->is_set()) { + t_type* etype = ((t_set*)ttype)->get_elem_type(); + write_key_and_string("elemTypeId", get_type_name(etype)); + write_type_spec_object("elemType", etype); + } +} + +void t_json_generator::close_generator() { + f_json_ << endl; + f_json_.close(); +} + +void t_json_generator::merge_includes(t_program* program) { + vector<t_program*> includes = program->get_includes(); + vector<t_program*>::iterator inc_iter; + for (inc_iter = includes.begin(); inc_iter != includes.end(); ++inc_iter) { + t_program* include = *inc_iter; + // recurse in case we get crazy + merge_includes(include); + // merge enums + vector<t_enum*> enums = include->get_enums(); + vector<t_enum*>::iterator en_iter; + for (en_iter = enums.begin(); en_iter != enums.end(); ++en_iter) { + program->add_enum(*en_iter); + } + // merge typedefs + vector<t_typedef*> typedefs = include->get_typedefs(); + vector<t_typedef*>::iterator td_iter; + for (td_iter = typedefs.begin(); td_iter != typedefs.end(); ++td_iter) { + program->add_typedef(*td_iter); + } + // merge structs + vector<t_struct*> objects = include->get_objects(); + vector<t_struct*>::iterator o_iter; + for (o_iter = objects.begin(); o_iter != objects.end(); ++o_iter) { + program->add_struct(*o_iter); + } + // merge constants + vector<t_const*> consts = include->get_consts(); + vector<t_const*>::iterator c_iter; + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + program->add_const(*c_iter); + } + + // merge services + vector<t_service*> services = include->get_services(); + vector<t_service*>::iterator sv_iter; + for (sv_iter = services.begin(); sv_iter != services.end(); ++sv_iter) { + program->add_service(*sv_iter); + } + } +} + +void t_json_generator::generate_program() { + + init_generator(); + + start_object(); + write_key_and_string("name", program_->get_name()); + if (program_->has_doc()) { + write_key_and_string("doc", program_->get_doc()); + } + + // When merging includes, the "namespaces" and "includes" sections + // become ambiguous, so just skip them. + if (!should_merge_includes_) { + // Generate namespaces + write_key_and("namespaces"); + start_object(NO_INDENT); + const map<string, string>& namespaces = program_->get_namespaces(); + map<string, string>::const_iterator ns_it; + for (ns_it = namespaces.begin(); ns_it != namespaces.end(); ++ns_it) { + write_key_and_string(ns_it->first, ns_it->second); + indicate_comma_needed(); + } + end_object(); + + // Generate includes + write_key_and("includes"); + start_array(); + const vector<t_program*> includes = program_->get_includes(); + vector<t_program*>::const_iterator inc_it; + for (inc_it = includes.begin(); inc_it != includes.end(); ++inc_it) { + write_comma_if_needed(); + write_string((*inc_it)->get_name()); + indicate_comma_needed(); + } + end_array(); + } + + // Generate enums + write_key_and("enums"); + start_array(); + vector<t_enum*> enums = program_->get_enums(); + vector<t_enum*>::iterator en_iter; + for (en_iter = enums.begin(); en_iter != enums.end(); ++en_iter) { + write_comma_if_needed(); + generate_enum(*en_iter); + indicate_comma_needed(); + } + end_array(); + + // Generate typedefs + write_key_and("typedefs"); + start_array(); + vector<t_typedef*> typedefs = program_->get_typedefs(); + vector<t_typedef*>::iterator td_iter; + for (td_iter = typedefs.begin(); td_iter != typedefs.end(); ++td_iter) { + write_comma_if_needed(); + generate_typedef(*td_iter); + indicate_comma_needed(); + } + end_array(); + + // Generate structs, exceptions, and unions in declared order + write_key_and("structs"); + start_array(); + vector<t_struct*> objects = program_->get_objects(); + vector<t_struct*>::iterator o_iter; + for (o_iter = objects.begin(); o_iter != objects.end(); ++o_iter) { + write_comma_if_needed(); + if ((*o_iter)->is_xception()) { + generate_xception(*o_iter); + } else { + generate_struct(*o_iter); + } + indicate_comma_needed(); + } + end_array(); + + // Generate constants + write_key_and("constants"); + start_array(); + vector<t_const*> consts = program_->get_consts(); + vector<t_const*>::iterator c_iter; + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + write_comma_if_needed(); + generate_constant(*c_iter); + indicate_comma_needed(); + } + end_array(); + + // Generate services + write_key_and("services"); + start_array(); + vector<t_service*> services = program_->get_services(); + vector<t_service*>::iterator sv_iter; + for (sv_iter = services.begin(); sv_iter != services.end(); ++sv_iter) { + write_comma_if_needed(); + generate_service(*sv_iter); + indicate_comma_needed(); + } + end_array(); + + end_object(); + + // Close the generator + close_generator(); +} + +void t_json_generator::generate_typedef(t_typedef* ttypedef) { + start_object(); + write_key_and_string("name", get_qualified_name(ttypedef)); + write_key_and_string("typeId", get_type_name(ttypedef->get_true_type())); + write_type_spec_object("type", ttypedef->get_true_type()); + if (ttypedef->has_doc()) { + write_key_and_string("doc", ttypedef->get_doc()); + } + if (ttypedef->annotations_.size() > 0) { + write_key_and("annotations"); + start_object(); + for (auto & annotation : ttypedef->annotations_) { + write_key_and_string(annotation.first, annotation.second); + } + end_object(); + } + end_object(); +} + +void t_json_generator::write_string(const string& value) { + f_json_ << quot << escape_json_string(value) << quot; +} + +void t_json_generator::write_const_value(t_const_value* value, bool should_force_string) { + + switch (value->get_type()) { + + case t_const_value::CV_IDENTIFIER: + case t_const_value::CV_INTEGER: + if (should_force_string) { + write_string(number_to_string(value->get_integer())); + } else { + write_number(value->get_integer()); + } + break; + + case t_const_value::CV_DOUBLE: + if (should_force_string) { + write_string(number_to_string(value->get_double())); + } else { + write_number(value->get_double()); + } + break; + + case t_const_value::CV_STRING: + write_string(value->get_string()); + break; + + case t_const_value::CV_LIST: { + start_array(); + std::vector<t_const_value*> list = value->get_list(); + std::vector<t_const_value*>::iterator lit; + for (lit = list.begin(); lit != list.end(); ++lit) { + write_comma_if_needed(); + f_json_ << indent(); + write_const_value(*lit); + indicate_comma_needed(); + } + end_array(); + break; + } + + case t_const_value::CV_MAP: { + start_object(NO_INDENT); + std::map<t_const_value*, t_const_value*, t_const_value::value_compare> map = value->get_map(); + std::map<t_const_value*, t_const_value*, t_const_value::value_compare>::iterator mit; + for (mit = map.begin(); mit != map.end(); ++mit) { + write_comma_if_needed(); + f_json_ << indent(); + // JSON objects only allow string keys + write_const_value(mit->first, FORCE_STRING); + f_json_ << ": "; + write_const_value(mit->second); + indicate_comma_needed(); + } + end_object(); + break; + } + + default: + f_json_ << "null"; + break; + } +} + +string t_json_generator::json_str(const string& str) { + return quot + escape_json_string(str) + quot; +} + +void t_json_generator::generate_constant(t_const* con) { + start_object(); + + write_key_and_string("name", con->get_name()); + write_key_and_string("typeId", get_type_name(con->get_type())); + write_type_spec_object("type", con->get_type()); + + if (con->has_doc()) { + write_key_and_string("doc", con->get_doc()); + } + + write_key_and("value"); + write_const_value(con->get_value()); + + end_object(); +} + +void t_json_generator::generate_enum(t_enum* tenum) { + start_object(); + + write_key_and_string("name", tenum->get_name()); + + if (tenum->has_doc()) { + write_key_and_string("doc", tenum->get_doc()); + } + + if (tenum->annotations_.size() > 0) { + write_key_and("annotations"); + start_object(); + for (auto & annotation : tenum->annotations_) { + write_key_and_string(annotation.first, annotation.second); + } + end_object(); + } + + write_key_and("members"); + start_array(); + vector<t_enum_value*> values = tenum->get_constants(); + vector<t_enum_value*>::iterator val_iter; + for (val_iter = values.begin(); val_iter != values.end(); ++val_iter) { + write_comma_if_needed(); + t_enum_value* val = (*val_iter); + start_object(); + write_key_and_string("name", val->get_name()); + write_key_and_integer("value", val->get_value()); + if (val->has_doc()) { + write_key_and_string("doc", val->get_doc()); + } + end_object(); + indicate_comma_needed(); + } + end_array(); + + end_object(); +} + +void t_json_generator::generate_struct(t_struct* tstruct) { + start_object(); + + write_key_and_string("name", tstruct->get_name()); + + if (tstruct->has_doc()) { + write_key_and_string("doc", tstruct->get_doc()); + } + + if (tstruct->annotations_.size() > 0) { + write_key_and("annotations"); + start_object(); + for (auto & annotation : tstruct->annotations_) { + write_key_and_string(annotation.first, annotation.second); + } + end_object(); + } + + write_key_and_bool("isException", tstruct->is_xception()); + + write_key_and_bool("isUnion", tstruct->is_union()); + + write_key_and("fields"); + start_array(); + vector<t_field*> members = tstruct->get_members(); + vector<t_field*>::iterator mem_iter; + for (mem_iter = members.begin(); mem_iter != members.end(); mem_iter++) { + write_comma_if_needed(); + generate_field(*mem_iter); + indicate_comma_needed(); + } + end_array(); + + end_object(); +} + +void t_json_generator::generate_service(t_service* tservice) { + start_object(); + + write_key_and_string("name", get_qualified_name(tservice)); + + if (tservice->get_extends()) { + write_key_and_string("extends", get_qualified_name(tservice->get_extends())); + } + + if (tservice->has_doc()) { + write_key_and_string("doc", tservice->get_doc()); + } + + if (tservice->annotations_.size() > 0) { + write_key_and("annotations"); + start_object(); + for (auto & annotation : tservice->annotations_) { + write_key_and_string(annotation.first, annotation.second); + } + end_object(); + } + + write_key_and("functions"); + start_array(); + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator fn_iter = functions.begin(); + for (; fn_iter != functions.end(); fn_iter++) { + write_comma_if_needed(); + generate_function(*fn_iter); + indicate_comma_needed(); + } + end_array(); + + end_object(); +} + +void t_json_generator::generate_function(t_function* tfunc) { + start_object(); + + write_key_and_string("name", tfunc->get_name()); + + write_key_and_string("returnTypeId", get_type_name(tfunc->get_returntype())); + write_type_spec_object("returnType", tfunc->get_returntype()); + + write_key_and_bool("oneway", tfunc->is_oneway()); + + if (tfunc->has_doc()) { + write_key_and_string("doc", tfunc->get_doc()); + } + + if (tfunc->annotations_.size() > 0) { + write_key_and("annotations"); + start_object(); + for (auto & annotation : tfunc->annotations_) { + write_key_and_string(annotation.first, annotation.second); + } + end_object(); + } + + write_key_and("arguments"); + start_array(); + vector<t_field*> members = tfunc->get_arglist()->get_members(); + vector<t_field*>::iterator mem_iter = members.begin(); + for (; mem_iter != members.end(); mem_iter++) { + write_comma_if_needed(); + generate_field(*mem_iter); + indicate_comma_needed(); + } + end_array(); + + write_key_and("exceptions"); + start_array(); + vector<t_field*> excepts = tfunc->get_xceptions()->get_members(); + vector<t_field*>::iterator ex_iter = excepts.begin(); + for (; ex_iter != excepts.end(); ex_iter++) { + write_comma_if_needed(); + generate_field(*ex_iter); + indicate_comma_needed(); + } + end_array(); + + end_object(); +} + +void t_json_generator::generate_field(t_field* field) { + start_object(); + + write_key_and_integer("key", field->get_key()); + write_key_and_string("name", field->get_name()); + write_key_and_string("typeId", get_type_name(field->get_type())); + write_type_spec_object("type", field->get_type()); + + if (field->has_doc()) { + write_key_and_string("doc", field->get_doc()); + } + + if (field->annotations_.size() > 0) { + write_key_and("annotations"); + start_object(); + for (auto & annotation : field->annotations_) { + write_key_and_string(annotation.first, annotation.second); + } + end_object(); + } + + write_key_and("required"); + switch (field->get_req()) { + case t_field::T_REQUIRED: + write_string("required"); + break; + case t_field::T_OPT_IN_REQ_OUT: + write_string("req_out"); + break; + default: + write_string("optional"); + break; + } + + if (field->get_value()) { + write_key_and("default"); + write_const_value(field->get_value()); + } + + end_object(); +} + +string t_json_generator::get_type_name(t_type* ttype) { + ttype = ttype->get_true_type(); + if (ttype->is_list()) { + return "list"; + } + if (ttype->is_set()) { + return "set"; + } + if (ttype->is_map()) { + return "map"; + } + if (ttype->is_enum()) { + return "i32"; + } + if (ttype->is_struct()) { + return ((t_struct*)ttype)->is_union() ? "union" : "struct"; + } + if (ttype->is_xception()) { + return "exception"; + } + if (ttype->is_base_type()) { + t_base_type* tbasetype = (t_base_type*)ttype; + return tbasetype->is_binary() ? "binary" : t_base_type::t_base_name(tbasetype->get_base()); + } + + return "(unknown)"; +} + +string t_json_generator::get_qualified_name(t_type* ttype) { + if (should_merge_includes_ || ttype->get_program() == program_) { + return ttype->get_name(); + } + return ttype->get_program()->get_name() + "." + ttype->get_name(); +} + +THRIFT_REGISTER_GENERATOR(json, + "JSON", + " merge: Generate output with included files merged\n") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_lua_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_lua_generator.cc new file mode 100644 index 000000000..e6432c898 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_lua_generator.cc @@ -0,0 +1,1138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <sstream> +#include "thrift/platform.h" +#include "thrift/generate/t_oop_generator.h" + +using std::ostream; +using std::string; +using std::vector; +using std::map; + +static const string endl = "\n"; // avoid ostream << std::endl flushes + +/** + * LUA code generator. + * + */ +class t_lua_generator : public t_oop_generator { +public: + t_lua_generator(t_program* program, + const std::map<std::string, std::string>& parsed_options, + const std::string& option_string) + : t_oop_generator(program) { + (void)option_string; + std::map<std::string, std::string>::const_iterator iter; + + gen_requires_ = true; + for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + if( iter->first.compare("omit_requires") == 0) { + gen_requires_ = false; + } else { + throw "unknown option lua:" + iter->first; + } + } + + out_dir_base_ = "gen-lua"; + } + + /** + * Init and close methods + */ + void init_generator() override; + void close_generator() override; + + /** + * Program-level generation functions + */ + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_const(t_const* tconst) override; + void generate_struct(t_struct* tstruct) override; + void generate_xception(t_struct* txception) override; + void generate_service(t_service* tservice) override; + + std::string render_const_value(t_type* type, t_const_value* value); + +private: + /** + * True iff we should generate lua require statements. + */ + bool gen_requires_; + + /** + * Struct-level generation functions + */ + void generate_lua_struct_definition(std::ostream& out, + t_struct* tstruct, + bool is_xception = false); + void generate_lua_struct_reader(std::ostream& out, t_struct* tstruct); + void generate_lua_struct_writer(std::ostream& out, t_struct* tstruct); + + /** + * Service-level generation functions + */ + void generate_service_client(std::ostream& out, t_service* tservice); + void generate_service_interface(std::ostream& out, t_service* tservice); + void generate_service_processor(std::ostream& out, t_service* tservice); + void generate_process_function(std::ostream& out, t_service* tservice, t_function* tfunction); + void generate_service_helpers(ostream& out, t_service* tservice); + void generate_function_helpers(ostream& out, t_function* tfunction); + + /** + * Deserialization (Read) + */ + void generate_deserialize_field(std::ostream& out, + t_field* tfield, + bool local, + std::string prefix = ""); + + void generate_deserialize_struct(std::ostream& out, + t_struct* tstruct, + bool local, + std::string prefix = ""); + + void generate_deserialize_container(std::ostream& out, + t_type* ttype, + bool local, + std::string prefix = ""); + + void generate_deserialize_set_element(std::ostream& out, t_set* tset, std::string prefix = ""); + + void generate_deserialize_map_element(std::ostream& out, t_map* tmap, std::string prefix = ""); + + void generate_deserialize_list_element(std::ostream& out, + t_list* tlist, + std::string prefix = ""); + + /** + * Serialization (Write) + */ + void generate_serialize_field(std::ostream& out, t_field* tfield, std::string prefix = ""); + + void generate_serialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); + + void generate_serialize_container(std::ostream& out, t_type* ttype, std::string prefix = ""); + + void generate_serialize_map_element(std::ostream& out, + t_map* tmap, + std::string kiter, + std::string viter); + + void generate_serialize_set_element(std::ostream& out, t_set* tmap, std::string iter); + + void generate_serialize_list_element(std::ostream& out, t_list* tlist, std::string iter); + + /** + * Helper rendering functions + */ + std::string lua_includes(); + std::string function_signature(t_function* tfunction, std::string prefix = ""); + std::string argument_list(t_struct* tstruct, std::string prefix = ""); + std::string type_to_enum(t_type* ttype); + static std::string get_namespace(const t_program* program); + + std::string autogen_comment() override { + return std::string("--\n") + "-- Autogenerated by Thrift\n" + "--\n" + + "-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n" + "-- @" + "generated\n" + + "--\n"; + } + + /** + * File streams + */ + ofstream_with_content_based_conditional_update f_types_; + ofstream_with_content_based_conditional_update f_consts_; + ofstream_with_content_based_conditional_update f_service_; +}; + +/** + * Init and close methods + */ +void t_lua_generator::init_generator() { + // Make output directory + string outdir = get_out_dir(); + MKDIR(outdir.c_str()); + + // Make output files + string cur_namespace = get_namespace(program_); + string f_consts_name = outdir + cur_namespace + "constants.lua"; + f_consts_.open(f_consts_name.c_str()); + string f_types_name = outdir + cur_namespace + "ttypes.lua"; + f_types_.open(f_types_name.c_str()); + + // Add headers + f_consts_ << autogen_comment() << lua_includes(); + f_types_ << autogen_comment() << lua_includes(); + if (gen_requires_) { + f_types_ << endl << "require '" << cur_namespace << "constants'"; + } +} + +void t_lua_generator::close_generator() { + // Close types file + f_types_.close(); + f_consts_.close(); +} + +/** + * Generate a typedef (essentially a constant) + */ +void t_lua_generator::generate_typedef(t_typedef* ttypedef) { + f_types_ << endl << endl << indent() << ttypedef->get_symbolic() << " = " + << ttypedef->get_type()->get_name(); +} + +/** + * Generates code for an enumerated type (table) + */ +void t_lua_generator::generate_enum(t_enum* tenum) { + f_types_ << endl << endl << tenum->get_name() << " = {" << endl; + + vector<t_enum_value*> constants = tenum->get_constants(); + vector<t_enum_value*>::iterator c_iter; + for (c_iter = constants.begin(); c_iter != constants.end();) { + int32_t value = (*c_iter)->get_value(); + + f_types_ << " " << (*c_iter)->get_name() << " = " << value; + ++c_iter; + if (c_iter != constants.end()) { + f_types_ << ","; + } + f_types_ << endl; + } + f_types_ << "}"; +} + +/** + * Generate a constant (non-local) value + */ +void t_lua_generator::generate_const(t_const* tconst) { + t_type* type = tconst->get_type(); + string name = tconst->get_name(); + t_const_value* value = tconst->get_value(); + + f_consts_ << endl << endl << name << " = "; + f_consts_ << render_const_value(type, value); +} + +/** + * Prints the value of a constant with the given type. + */ +string t_lua_generator::render_const_value(t_type* type, t_const_value* value) { + std::ostringstream out; + + type = get_true_type(type); + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + out << "'" << value->get_string() << "'"; + break; + case t_base_type::TYPE_BOOL: + out << (value->get_integer() > 0 ? "true" : "false"); + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + out << value->get_integer(); + break; + case t_base_type::TYPE_I64: + out << "lualongnumber.new('" << value->get_string() << "')"; + break; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) { + out << value->get_integer(); + } else { + out << value->get_double(); + } + break; + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << value->get_integer(); + } else if (type->is_struct() || type->is_xception()) { + out << type->get_name() << " = {" << endl; + indent_up(); + + const vector<t_field*>& fields = ((t_struct*)type)->get_members(); + vector<t_field*>::const_iterator f_iter; + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end();) { + t_type* field_type = NULL; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field_type = (*f_iter)->get_type(); + } + } + if (field_type == NULL) { + throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string(); + } + + indent(out); + out << render_const_value(g_type_string, v_iter->first); + out << " = "; + out << render_const_value(field_type, v_iter->second); + ++v_iter; + if (v_iter != val.end()) { + out << ","; + } + } + + out << "}"; + indent_down(); + } else if (type->is_map()) { + out << type->get_name() << "{" << endl; + indent_up(); + + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end();) { + indent(out) << "[" << render_const_value(ktype, v_iter->first) + << "] = " << render_const_value(vtype, v_iter->second); + ++v_iter; + if (v_iter != val.end()) { + out << ","; + } + out << endl; + } + indent_down(); + indent(out) << "}"; + } else if (type->is_list() || type->is_set()) { + t_type* etype; + if (type->is_list()) { + etype = ((t_list*)type)->get_elem_type(); + } else { + etype = ((t_set*)type)->get_elem_type(); + } + out << type->get_name() << " = {" << endl; + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end();) { + indent(out); + out << "[" << render_const_value(etype, *v_iter) << "]"; + if (type->is_set()) { + out << " = true"; + } else { + out << " = false"; + } + ++v_iter; + if (v_iter != val.end()) { + out << "," << endl; + } + } + out << "}"; + } + return out.str(); +} + +/** + * Generate a thrift struct + */ +void t_lua_generator::generate_struct(t_struct* tstruct) { + generate_lua_struct_definition(f_types_, tstruct, false); +} + +/** + * Generate a thrift exception + */ +void t_lua_generator::generate_xception(t_struct* txception) { + generate_lua_struct_definition(f_types_, txception, true); +} + +/** + * Generate a thrift struct or exception (lua table) + */ +void t_lua_generator::generate_lua_struct_definition(ostream& out, + t_struct* tstruct, + bool is_exception) { + vector<t_field*>::const_iterator m_iter; + const vector<t_field*>& members = tstruct->get_members(); + + indent(out) << endl << endl << tstruct->get_name(); + if (is_exception) { + out << " = TException:new{" << endl << indent() << " __type = '" << tstruct->get_name() << "'"; + if (members.size() > 0) { + out << ","; + } + out << endl; + } else { + out << " = __TObject:new{" << endl; + } + indent_up(); + for (m_iter = members.begin(); m_iter != members.end();) { + indent(out); + out << (*m_iter)->get_name(); + ++m_iter; + if (m_iter != members.end()) { + out << "," << endl; + } + } + indent_down(); + indent(out); + out << endl << "}"; + + generate_lua_struct_reader(out, tstruct); + generate_lua_struct_writer(out, tstruct); +} + +/** + * Generate a struct/exception reader + */ +void t_lua_generator::generate_lua_struct_reader(ostream& out, t_struct* tstruct) { + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + // function + indent(out) << endl << endl << "function " << tstruct->get_name() << ":read(iprot)" << endl; + indent_up(); + + indent(out) << "iprot:readStructBegin()" << endl; + + // while: Read in fields + indent(out) << "while true do" << endl; + indent_up(); + + // if: Check what to read + indent(out) << "local fname, ftype, fid = iprot:readFieldBegin()" << endl; + indent(out) << "if ftype == TType.STOP then" << endl; + indent_up(); + indent(out) << "break" << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + indent_down(); + indent(out) << "elseif fid == " << (*f_iter)->get_key() << " then" << endl; + indent_up(); + indent(out) << "if ftype == " << type_to_enum((*f_iter)->get_type()) << " then" << endl; + indent_up(); + + // Read field contents + generate_deserialize_field(out, *f_iter, false, "self."); + + indent_down(); + indent(out) << "else" << endl; + indent(out) << " iprot:skip(ftype)" << endl; + indent(out) << "end" << endl; + } + + // end if + indent_down(); + indent(out) << "else" << endl; + indent(out) << " iprot:skip(ftype)" << endl; + indent(out) << "end" << endl; + indent(out) << "iprot:readFieldEnd()" << endl; + + // end while + indent_down(); + indent(out) << "end" << endl; + indent(out) << "iprot:readStructEnd()" << endl; + + // end function + indent_down(); + indent(out); + out << "end"; +} + +/** + * Generate a struct/exception writer + */ +void t_lua_generator::generate_lua_struct_writer(ostream& out, t_struct* tstruct) { + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + // function + indent(out) << endl << endl << "function " << tstruct->get_name() << ":write(oprot)" << endl; + indent_up(); + + indent(out) << "oprot:writeStructBegin('" << tstruct->get_name() << "')" << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + // To check element of self whether nil or not. + // avoid the value(false) of BOOL is lost. + indent(out) << "if self." << (*f_iter)->get_name() << " ~= nil then" << endl; + indent_up(); + indent(out) << "oprot:writeFieldBegin('" << (*f_iter)->get_name() << "', " + << type_to_enum((*f_iter)->get_type()) << ", " << (*f_iter)->get_key() << ")" + << endl; + + // Write field contents + generate_serialize_field(out, *f_iter, "self."); + + indent(out) << "oprot:writeFieldEnd()" << endl; + indent_down(); + indent(out) << "end" << endl; + } + indent(out) << "oprot:writeFieldStop()" << endl; + indent(out) << "oprot:writeStructEnd()" << endl; + + // end function + indent_down(); + indent(out); + out << "end"; +} + +/** + * Generate a thrift service + */ +void t_lua_generator::generate_service(t_service* tservice) { + // Get output directory + string outdir = get_out_dir(); + + // Open the file for writing + string cur_ns = get_namespace(program_); + string f_service_name = outdir + cur_ns + tservice->get_name() + ".lua"; + f_service_.open(f_service_name.c_str()); + + // Headers + f_service_ << autogen_comment() << lua_includes(); + if (gen_requires_) { + f_service_ << endl << "require '" << cur_ns << "ttypes'" << endl; + + if (tservice->get_extends() != NULL) { + f_service_ << "require '" << get_namespace(tservice->get_extends()->get_program()) + << tservice->get_extends()->get_name() << "'" << endl; + } + } + + f_service_ << endl; + + generate_service_client(f_service_, tservice); + generate_service_interface(f_service_, tservice); + generate_service_processor(f_service_, tservice); + generate_service_helpers(f_service_, tservice); + + // Close the file + f_service_.close(); +} + +void t_lua_generator::generate_service_interface(ostream& out, t_service* tservice) { + string classname = tservice->get_name() + "Iface"; + t_service* extends_s = tservice->get_extends(); + + // Interface object definition + out << classname << " = "; + if (extends_s) { + out << extends_s->get_name() << "Iface:new{" << endl; + } else { + out << "__TObject:new{" << endl; + } + out << " __type = '" << classname << "'" << endl << "}" << endl << endl; +} + +void t_lua_generator::generate_service_client(ostream& out, t_service* tservice) { + string classname = tservice->get_name() + "Client"; + t_service* extends_s = tservice->get_extends(); + + // Client object definition + out << classname << " = __TObject.new("; + if (extends_s != NULL) { + out << extends_s->get_name() << "Client"; + } else { + out << "__TClient"; + } + out << ", {" << endl << " __type = '" << classname << "'" << endl << "})" << endl; + + // Send/Recv functions + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + string sig = function_signature(*f_iter); + string funcname = (*f_iter)->get_name(); + + // Wrapper function + indent(out) << endl << "function " << classname << ":" << sig << endl; + indent_up(); + + indent(out) << "self:send_" << sig << endl << indent(); + if (!(*f_iter)->is_oneway()) { + if (!(*f_iter)->get_returntype()->is_void()) { + out << "return "; + } + out << "self:recv_" << sig << endl; + } + + indent_down(); + indent(out) << "end" << endl; + + // Send function + indent(out) << endl << "function " << classname << ":send_" << sig << endl; + indent_up(); + + indent(out) << "self.oprot:writeMessageBegin('" << funcname << "', " + << ((*f_iter)->is_oneway() ? "TMessageType.ONEWAY" : "TMessageType.CALL") + << ", self._seqid)" << endl; + indent(out) << "local args = " << funcname << "_args:new{}" << endl; + + // Set the args + const vector<t_field*>& args = (*f_iter)->get_arglist()->get_members(); + vector<t_field*>::const_iterator fld_iter; + for (fld_iter = args.begin(); fld_iter != args.end(); ++fld_iter) { + std::string argname = (*fld_iter)->get_name(); + indent(out) << "args." << argname << " = " << argname << endl; + } + + indent(out) << "args:write(self.oprot)" << endl; + indent(out) << "self.oprot:writeMessageEnd()" << endl; + indent(out) << "self.oprot.trans:flush()" << endl; + + indent_down(); + indent(out) << "end" << endl; + + // Recv function + if (!(*f_iter)->is_oneway()) { + indent(out) << endl << "function " << classname << ":recv_" << sig << endl; + indent_up(); + + out << indent() << "local fname, mtype, rseqid = self.iprot:" + << "readMessageBegin()" << endl << indent() << "if mtype == TMessageType.EXCEPTION then" + << endl << indent() << " local x = TApplicationException:new{}" << endl << indent() + << " x:read(self.iprot)" << endl << indent() << " self.iprot:readMessageEnd()" << endl + << indent() << " error(x)" << endl << indent() << "end" << endl << indent() + << "local result = " << funcname << "_result:new{}" << endl << indent() + << "result:read(self.iprot)" << endl << indent() << "self.iprot:readMessageEnd()" << endl; + + // Return the result if it's not a void function + if (!(*f_iter)->get_returntype()->is_void()) { + out << indent() << "if result.success ~= nil then" << endl << indent() << " return result.success" + << endl; + + // Throw custom exceptions + const std::vector<t_field*>& xf = (*f_iter)->get_xceptions()->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xf.begin(); x_iter != xf.end(); ++x_iter) { + out << indent() << "elseif result." << (*x_iter)->get_name() << " then" << endl + << indent() << " error(result." << (*x_iter)->get_name() << ")" << endl; + } + + out << indent() << "end" << endl << indent() + << "error(TApplicationException:new{errorCode = " + << "TApplicationException.MISSING_RESULT})" << endl; + } + + indent_down(); + indent(out) << "end" << endl; + } + } +} + +void t_lua_generator::generate_service_processor(ostream& out, t_service* tservice) { + string classname = tservice->get_name() + "Processor"; + t_service* extends_s = tservice->get_extends(); + + // Define processor table + out << endl << classname << " = __TObject.new("; + if (extends_s != NULL) { + out << extends_s->get_name() << "Processor" << endl; + } else { + out << "__TProcessor" << endl; + } + out << ", {" << endl << " __type = '" << classname << "'" << endl << "})" << endl; + + // Process function + indent(out) << endl << "function " << classname << ":process(iprot, oprot, server_ctx)" << endl; + indent_up(); + + indent(out) << "local name, mtype, seqid = iprot:readMessageBegin()" << endl; + indent(out) << "local func_name = 'process_' .. name" << endl; + indent(out) << "if not self[func_name] or ttype(self[func_name]) ~= 'function' then"; + indent_up(); + out << endl << indent() << "iprot:skip(TType.STRUCT)" << endl << indent() + << "iprot:readMessageEnd()" << endl << indent() << "x = TApplicationException:new{" << endl + << indent() << " errorCode = TApplicationException.UNKNOWN_METHOD" << endl << indent() << "}" + << endl << indent() << "oprot:writeMessageBegin(name, TMessageType.EXCEPTION, " + << "seqid)" << endl << indent() << "x:write(oprot)" << endl << indent() + << "oprot:writeMessageEnd()" << endl << indent() << "oprot.trans:flush()" << endl; + indent_down(); + indent(out) << "else" << endl << indent() + << " self[func_name](self, seqid, iprot, oprot, server_ctx)" << endl << indent() + << "end" << endl; + + indent_down(); + indent(out) << "end" << endl; + + // Generate the process subfunctions + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_process_function(out, tservice, *f_iter); + } +} + +void t_lua_generator::generate_process_function(ostream& out, + t_service* tservice, + t_function* tfunction) { + string classname = tservice->get_name() + "Processor"; + string argsname = tfunction->get_name() + "_args"; + string resultname = tfunction->get_name() + "_result"; + string fn_name = tfunction->get_name(); + + indent(out) << endl << "function " << classname << ":process_" << fn_name + << "(seqid, iprot, oprot, server_ctx)" << endl; + indent_up(); + + // Read the request + out << indent() << "local args = " << argsname << ":new{}" << endl << indent() + << "local reply_type = TMessageType.REPLY" << endl << indent() << "args:read(iprot)" << endl + << indent() << "iprot:readMessageEnd()" << endl << indent() << "local result = " << resultname + << ":new{}" << endl << indent() << "local status, res = pcall(self.handler." << fn_name + << ", self.handler"; + + // Print arguments + t_struct* args = tfunction->get_arglist(); + if (args->get_members().size() > 0) { + out << ", " << argument_list(args, "args."); + } + + // Check for errors + out << ")" << endl << indent() << "if not status then" << endl << indent() + << " reply_type = TMessageType.EXCEPTION" << endl << indent() + << " result = TApplicationException:new{message = res}" << endl; + + // Handle custom exceptions + const std::vector<t_field*>& xf = tfunction->get_xceptions()->get_members(); + if (xf.size() > 0) { + vector<t_field*>::const_iterator x_iter; + for (x_iter = xf.begin(); x_iter != xf.end(); ++x_iter) { + out << indent() << "elseif ttype(res) == '" << (*x_iter)->get_type()->get_name() << "' then" + << endl << indent() << " result." << (*x_iter)->get_name() << " = res" << endl; + } + } + + // Set the result and write the reply + out << indent() << "else" << endl << indent() << " result.success = res" << endl << indent() + << "end" << endl << indent() << "oprot:writeMessageBegin('" << fn_name << "', reply_type, " + << "seqid)" << endl << indent() << "result:write(oprot)" << endl << indent() + << "oprot:writeMessageEnd()" << endl << indent() << "oprot.trans:flush()" << endl; + + indent_down(); + indent(out) << "end" << endl; +} + +// Service helpers +void t_lua_generator::generate_service_helpers(ostream& out, t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + out << endl << "-- HELPER FUNCTIONS AND STRUCTURES"; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* ts = (*f_iter)->get_arglist(); + generate_lua_struct_definition(out, ts, false); + generate_function_helpers(out, *f_iter); + } +} + +void t_lua_generator::generate_function_helpers(ostream& out, t_function* tfunction) { + if (!tfunction->is_oneway()) { + t_struct result(program_, tfunction->get_name() + "_result"); + t_field success(tfunction->get_returntype(), "success", 0); + if (!tfunction->get_returntype()->is_void()) { + result.append(&success); + } + + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& fields = xs->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + result.append(*f_iter); + } + generate_lua_struct_definition(out, &result, false); + } +} + +/** + * Deserialize (Read) + */ +void t_lua_generator::generate_deserialize_field(ostream& out, + t_field* tfield, + bool local, + string prefix) { + t_type* type = get_true_type(tfield->get_type()); + + if (type->is_void()) { + throw "CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name(); + } + + string name = prefix + tfield->get_name(); + + if (type->is_struct() || type->is_xception()) { + generate_deserialize_struct(out, (t_struct*)type, local, name); + } else if (type->is_container()) { + generate_deserialize_container(out, type, local, name); + } else if (type->is_base_type() || type->is_enum()) { + indent(out) << (local ? "local " : "") << name << " = iprot:"; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + out << "readString()"; + break; + case t_base_type::TYPE_BOOL: + out << "readBool()"; + break; + case t_base_type::TYPE_I8: + out << "readByte()"; + break; + case t_base_type::TYPE_I16: + out << "readI16()"; + break; + case t_base_type::TYPE_I32: + out << "readI32()"; + break; + case t_base_type::TYPE_I64: + out << "readI64()"; + break; + case t_base_type::TYPE_DOUBLE: + out << "readDouble()"; + break; + default: + throw "compiler error: no PHP name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "readI32()"; + } + out << endl; + + } else { + printf("DO NOT KNOW HOW TO DESERIALIZE FIELD '%s' TYPE '%s'\n", + tfield->get_name().c_str(), + type->get_name().c_str()); + } +} + +void t_lua_generator::generate_deserialize_struct(ostream& out, + t_struct* tstruct, + bool local, + string prefix) { + indent(out) << (local ? "local " : "") << prefix << " = " << tstruct->get_name() << ":new{}" + << endl << indent() << prefix << ":read(iprot)" << endl; +} + +void t_lua_generator::generate_deserialize_container(ostream& out, + t_type* ttype, + bool local, + string prefix) { + string size = tmp("_size"); + string ktype = tmp("_ktype"); + string vtype = tmp("_vtype"); + string etype = tmp("_etype"); + + t_field fsize(g_type_i32, size); + t_field fktype(g_type_i8, ktype); + t_field fvtype(g_type_i8, vtype); + t_field fetype(g_type_i8, etype); + + // Declare variables, read header + indent(out) << (local ? "local " : "") << prefix << " = {}" << endl; + if (ttype->is_map()) { + indent(out) << "local " << ktype << ", " << vtype << ", " << size << " = iprot:readMapBegin() " + << endl; + } else if (ttype->is_set()) { + indent(out) << "local " << etype << ", " << size << " = iprot:readSetBegin()" << endl; + } else if (ttype->is_list()) { + indent(out) << "local " << etype << ", " << size << " = iprot:readListBegin()" << endl; + } + + // Deserialize + indent(out) << "for _i=1," << size << " do" << endl; + indent_up(); + + if (ttype->is_map()) { + generate_deserialize_map_element(out, (t_map*)ttype, prefix); + } else if (ttype->is_set()) { + generate_deserialize_set_element(out, (t_set*)ttype, prefix); + } else if (ttype->is_list()) { + generate_deserialize_list_element(out, (t_list*)ttype, prefix); + } + + indent_down(); + indent(out) << "end" << endl; + + // Read container end + if (ttype->is_map()) { + indent(out) << "iprot:readMapEnd()" << endl; + } else if (ttype->is_set()) { + indent(out) << "iprot:readSetEnd()" << endl; + } else if (ttype->is_list()) { + indent(out) << "iprot:readListEnd()" << endl; + } +} + +void t_lua_generator::generate_deserialize_map_element(ostream& out, t_map* tmap, string prefix) { + // A map is represented by a table indexable by any lua type + string key = tmp("_key"); + string val = tmp("_val"); + t_field fkey(tmap->get_key_type(), key); + t_field fval(tmap->get_val_type(), val); + + generate_deserialize_field(out, &fkey, true); + generate_deserialize_field(out, &fval, true); + + indent(out) << prefix << "[" << key << "] = " << val << endl; +} + +void t_lua_generator::generate_deserialize_set_element(ostream& out, t_set* tset, string prefix) { + // A set is represented by a table indexed by the value + string elem = tmp("_elem"); + t_field felem(tset->get_elem_type(), elem); + + generate_deserialize_field(out, &felem, true); + + indent(out) << prefix << "[" << elem << "] = " << elem << endl; +} + +void t_lua_generator::generate_deserialize_list_element(ostream& out, + t_list* tlist, + string prefix) { + // A list is represented by a table indexed by integer values + // LUA natively provides all of the functions required to maintain a list + string elem = tmp("_elem"); + t_field felem(tlist->get_elem_type(), elem); + + generate_deserialize_field(out, &felem, true); + + indent(out) << "table.insert(" << prefix << ", " << elem << ")" << endl; +} + +/** + * Serialize (Write) + */ +void t_lua_generator::generate_serialize_field(ostream& out, t_field* tfield, string prefix) { + t_type* type = get_true_type(tfield->get_type()); + string name = prefix + tfield->get_name(); + + // Do nothing for void types + if (type->is_void()) { + throw "CANNOT GENERATE SERIALIZE CODE FOR void TYPE: " + name; + } + + if (type->is_struct() || type->is_xception()) { + generate_serialize_struct(out, (t_struct*)type, name); + } else if (type->is_container()) { + generate_serialize_container(out, type, name); + } else if (type->is_base_type() || type->is_enum()) { + indent(out) << "oprot:"; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + out << "writeString(" << name << ")"; + break; + case t_base_type::TYPE_BOOL: + out << "writeBool(" << name << ")"; + break; + case t_base_type::TYPE_I8: + out << "writeByte(" << name << ")"; + break; + case t_base_type::TYPE_I16: + out << "writeI16(" << name << ")"; + break; + case t_base_type::TYPE_I32: + out << "writeI32(" << name << ")"; + break; + case t_base_type::TYPE_I64: + out << "writeI64(" << name << ")"; + break; + case t_base_type::TYPE_DOUBLE: + out << "writeDouble(" << name << ")"; + break; + default: + throw "compiler error: no PHP name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "writeI32(" << name << ")"; + } + out << endl; + } else { + printf("DO NOT KNOW HOW TO SERIALIZE FIELD '%s' TYPE '%s'\n", + name.c_str(), + type->get_name().c_str()); + } +} + +void t_lua_generator::generate_serialize_struct(ostream& out, t_struct* tstruct, string prefix) { + (void)tstruct; + indent(out) << prefix << ":write(oprot)" << endl; +} + +void t_lua_generator::generate_serialize_container(ostream& out, t_type* ttype, string prefix) { + // Begin writing + if (ttype->is_map()) { + indent(out) << "oprot:writeMapBegin(" << type_to_enum(((t_map*)ttype)->get_key_type()) << ", " + << type_to_enum(((t_map*)ttype)->get_val_type()) << ", " + << "ttable_size(" << prefix << "))" << endl; + } else if (ttype->is_set()) { + indent(out) << "oprot:writeSetBegin(" << type_to_enum(((t_set*)ttype)->get_elem_type()) << ", " + << "ttable_size(" << prefix << "))" << endl; + } else if (ttype->is_list()) { + indent(out) << "oprot:writeListBegin(" << type_to_enum(((t_list*)ttype)->get_elem_type()) + << ", " + << "#" << prefix << ")" << endl; + } + + // Serialize + if (ttype->is_map()) { + string kiter = tmp("kiter"); + string viter = tmp("viter"); + indent(out) << "for " << kiter << "," << viter << " in pairs(" << prefix << ") do" << endl; + indent_up(); + generate_serialize_map_element(out, (t_map*)ttype, kiter, viter); + indent_down(); + indent(out) << "end" << endl; + } else if (ttype->is_set()) { + string iter = tmp("iter"); + indent(out) << "for " << iter << ",_ in pairs(" << prefix << ") do" << endl; + indent_up(); + generate_serialize_set_element(out, (t_set*)ttype, iter); + indent_down(); + indent(out) << "end" << endl; + } else if (ttype->is_list()) { + string iter = tmp("iter"); + indent(out) << "for _," << iter << " in ipairs(" << prefix << ") do" << endl; + indent_up(); + generate_serialize_list_element(out, (t_list*)ttype, iter); + indent_down(); + indent(out) << "end" << endl; + } + + // Finish writing + if (ttype->is_map()) { + indent(out) << "oprot:writeMapEnd()" << endl; + } else if (ttype->is_set()) { + indent(out) << "oprot:writeSetEnd()" << endl; + } else if (ttype->is_list()) { + indent(out) << "oprot:writeListEnd()" << endl; + } +} + +void t_lua_generator::generate_serialize_map_element(ostream& out, + t_map* tmap, + string kiter, + string viter) { + t_field kfield(tmap->get_key_type(), kiter); + generate_serialize_field(out, &kfield, ""); + + t_field vfield(tmap->get_val_type(), viter); + generate_serialize_field(out, &vfield, ""); +} + +void t_lua_generator::generate_serialize_set_element(ostream& out, t_set* tset, string iter) { + t_field efield(tset->get_elem_type(), iter); + generate_serialize_field(out, &efield, ""); +} + +void t_lua_generator::generate_serialize_list_element(ostream& out, t_list* tlist, string iter) { + t_field efield(tlist->get_elem_type(), iter); + generate_serialize_field(out, &efield, ""); +} + +/** + * Helper rendering functions + */ +string t_lua_generator::lua_includes() { + if (gen_requires_) { + return "\n\nrequire 'Thrift'"; + } else { + return ""; + } +} + +string t_lua_generator::get_namespace(const t_program* program) { + std::string real_module = program->get_namespace("lua"); + if (real_module.empty()) { + return program->get_name() + "_"; + } + return real_module + "_"; +} + +string t_lua_generator::function_signature(t_function* tfunction, string prefix) { + (void)prefix; + std::string ret = tfunction->get_name() + "(" + argument_list(tfunction->get_arglist()) + ")"; + return ret; +} + +string t_lua_generator::argument_list(t_struct* tstruct, string prefix) { + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator fld_iter; + std::string ret = ""; + for (fld_iter = fields.begin(); fld_iter != fields.end();) { + ret += prefix + (*fld_iter)->get_name(); + ++fld_iter; + if (fld_iter != fields.end()) { + ret += ", "; + } + } + return ret; +} + +string t_lua_generator::type_to_enum(t_type* type) { + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + return "TType.STRING"; + case t_base_type::TYPE_BOOL: + return "TType.BOOL"; + case t_base_type::TYPE_I8: + return "TType.BYTE"; + case t_base_type::TYPE_I16: + return "TType.I16"; + case t_base_type::TYPE_I32: + return "TType.I32"; + case t_base_type::TYPE_I64: + return "TType.I64"; + case t_base_type::TYPE_DOUBLE: + return "TType.DOUBLE"; + } + } else if (type->is_enum()) { + return "TType.I32"; + } else if (type->is_struct() || type->is_xception()) { + return "TType.STRUCT"; + } else if (type->is_map()) { + return "TType.MAP"; + } else if (type->is_set()) { + return "TType.SET"; + } else if (type->is_list()) { + return "TType.LIST"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +THRIFT_REGISTER_GENERATOR( + lua, + "Lua", + " omit_requires: Suppress generation of require 'somefile'.\n") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_netcore_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_netcore_generator.cc new file mode 100644 index 000000000..ed6a365ac --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_netcore_generator.cc @@ -0,0 +1,3133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +#include <cassert> + +#include <string> +#include <fstream> +#include <iostream> +#include <vector> +#include <cctype> + +#include <stdlib.h> +#include <sys/stat.h> +#include <sstream> + +#include "thrift/platform.h" +#include "thrift/generate/t_oop_generator.h" +#include "thrift/generate/t_netcore_generator.h" + +using std::map; +using std::ostream; +using std::ostringstream; +using std::string; +using std::stringstream; +using std::vector; + +//TODO: check for indentation +//TODO: Do we need seqId_ in generation? + +t_netcore_generator::t_netcore_generator(t_program* program, const map<string, string>& parsed_options, const string& option_string) + : t_oop_generator(program) +{ + (void)option_string; + + nullable_ = false; + hashcode_ = false; + union_ = false; + serialize_ = false; + wcf_ = false; + wcf_namespace_.clear(); + + map<string, string>::const_iterator iter; + + for (iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) + { + if (iter->first.compare("nullable") == 0) + { + nullable_ = true; + } + else if (iter->first.compare("hashcode") == 0) + { + hashcode_ = true; + } + else if (iter->first.compare("union") == 0) + { + union_ = true; + } + else if (iter->first.compare("serial") == 0) + { + serialize_ = true; + wcf_namespace_ = iter->second; // since there can be only one namespace + } + else if (iter->first.compare("wcf") == 0) + { + wcf_ = true; + wcf_namespace_ = iter->second; + } + else + { + throw "unknown option netcore:" + iter->first; + } + } + + pwarning(1, "The 'netcore' target is deprecated. Consider using 'netstd' instead.\n"); + + out_dir_base_ = "gen-netcore"; +} + +static string correct_function_name_for_async(string const& function_name) +{ + string const async_end = "Async"; + size_t i = function_name.find(async_end); + if (i != string::npos) + { + return function_name + async_end; + } + + return function_name; +} + +/** +* \brief Search and replace "_args" substring in struct name if exist (for C# class naming) +* \param struct_name +* \return Modified struct name ("Struct_args" -> "StructArgs") or original name +*/ +static string check_and_correct_struct_name(const string& struct_name) +{ + string args_end = "_args"; + size_t i = struct_name.find(args_end); + if (i != string::npos) + { + string new_struct_name = struct_name; + new_struct_name.replace(i, args_end.length(), "Args"); + return new_struct_name; + } + + string result_end = "_result"; + size_t j = struct_name.find(result_end); + if (j != string::npos) + { + string new_struct_name = struct_name; + new_struct_name.replace(j, result_end.length(), "Result"); + return new_struct_name; + } + + return struct_name; +} + +static bool field_has_default(t_field* tfield) { return tfield->get_value() != NULL; } + +static bool field_is_required(t_field* tfield) { return tfield->get_req() == t_field::T_REQUIRED; } + +static bool type_can_be_null(t_type* ttype) +{ + while (ttype->is_typedef()) + { + ttype = static_cast<t_typedef*>(ttype)->get_type(); + } + + return ttype->is_container() || ttype->is_struct() || ttype->is_xception() || ttype->is_string(); +} + +bool t_netcore_generator::is_wcf_enabled() const { return wcf_; } + +bool t_netcore_generator::is_nullable_enabled() const { return nullable_; } + +bool t_netcore_generator::is_hashcode_enabled() const { return hashcode_; } + +bool t_netcore_generator::is_serialize_enabled() const { return serialize_; } + +bool t_netcore_generator::is_union_enabled() const { return union_; } + +map<string, int> t_netcore_generator::get_keywords_list() const +{ + return netcore_keywords; +} + +void t_netcore_generator::init_generator() +{ + MKDIR(get_out_dir().c_str()); + + // for usage of csharp namespaces in thrift files (from files for csharp) + namespace_name_ = program_->get_namespace("netcore"); + if (namespace_name_.empty()) + { + namespace_name_ = program_->get_namespace("netcore"); + } + + string dir = namespace_name_; + string subdir = get_out_dir().c_str(); + string::size_type loc; + + while ((loc = dir.find(".")) != string::npos) + { + subdir = subdir + "/" + dir.substr(0, loc); + MKDIR(subdir.c_str()); + dir = dir.substr(loc + 1); + } + if (dir.size() > 0) + { + subdir = subdir + "/" + dir; + MKDIR(subdir.c_str()); + } + + namespace_dir_ = subdir; + init_keywords(); + + while (!member_mapping_scopes.empty()) + { + cleanup_member_name_mapping(member_mapping_scopes.back().scope_member); + } + + pverbose(".NET Core options:\n"); + pverbose("- nullable ... %s\n", (nullable_ ? "ON" : "off")); + pverbose("- union ...... %s\n", (union_ ? "ON" : "off")); + pverbose("- hashcode ... %s\n", (hashcode_ ? "ON" : "off")); + pverbose("- serialize .. %s\n", (serialize_ ? "ON" : "off")); + pverbose("- wcf ........ %s\n", (wcf_ ? "ON" : "off")); +} + +string t_netcore_generator::normalize_name(string name) +{ + string tmp(name); + transform(tmp.begin(), tmp.end(), tmp.begin(), static_cast<int(*)(int)>(tolower)); + + // un-conflict keywords by prefixing with "@" + if (netcore_keywords.find(tmp) != netcore_keywords.end()) + { + return "@" + name; + } + + // no changes necessary + return name; +} + +void t_netcore_generator::init_keywords() +{ + netcore_keywords.clear(); + + // C# keywords + netcore_keywords["abstract"] = 1; + netcore_keywords["as"] = 1; + netcore_keywords["base"] = 1; + netcore_keywords["bool"] = 1; + netcore_keywords["break"] = 1; + netcore_keywords["byte"] = 1; + netcore_keywords["case"] = 1; + netcore_keywords["catch"] = 1; + netcore_keywords["char"] = 1; + netcore_keywords["checked"] = 1; + netcore_keywords["class"] = 1; + netcore_keywords["const"] = 1; + netcore_keywords["continue"] = 1; + netcore_keywords["decimal"] = 1; + netcore_keywords["default"] = 1; + netcore_keywords["delegate"] = 1; + netcore_keywords["do"] = 1; + netcore_keywords["double"] = 1; + netcore_keywords["else"] = 1; + netcore_keywords["enum"] = 1; + netcore_keywords["event"] = 1; + netcore_keywords["explicit"] = 1; + netcore_keywords["extern"] = 1; + netcore_keywords["false"] = 1; + netcore_keywords["finally"] = 1; + netcore_keywords["fixed"] = 1; + netcore_keywords["float"] = 1; + netcore_keywords["for"] = 1; + netcore_keywords["foreach"] = 1; + netcore_keywords["goto"] = 1; + netcore_keywords["if"] = 1; + netcore_keywords["implicit"] = 1; + netcore_keywords["in"] = 1; + netcore_keywords["int"] = 1; + netcore_keywords["interface"] = 1; + netcore_keywords["internal"] = 1; + netcore_keywords["is"] = 1; + netcore_keywords["lock"] = 1; + netcore_keywords["long"] = 1; + netcore_keywords["namespace"] = 1; + netcore_keywords["new"] = 1; + netcore_keywords["null"] = 1; + netcore_keywords["object"] = 1; + netcore_keywords["operator"] = 1; + netcore_keywords["out"] = 1; + netcore_keywords["override"] = 1; + netcore_keywords["params"] = 1; + netcore_keywords["private"] = 1; + netcore_keywords["protected"] = 1; + netcore_keywords["public"] = 1; + netcore_keywords["readonly"] = 1; + netcore_keywords["ref"] = 1; + netcore_keywords["return"] = 1; + netcore_keywords["sbyte"] = 1; + netcore_keywords["sealed"] = 1; + netcore_keywords["short"] = 1; + netcore_keywords["sizeof"] = 1; + netcore_keywords["stackalloc"] = 1; + netcore_keywords["static"] = 1; + netcore_keywords["string"] = 1; + netcore_keywords["struct"] = 1; + netcore_keywords["switch"] = 1; + netcore_keywords["this"] = 1; + netcore_keywords["throw"] = 1; + netcore_keywords["true"] = 1; + netcore_keywords["try"] = 1; + netcore_keywords["typeof"] = 1; + netcore_keywords["uint"] = 1; + netcore_keywords["ulong"] = 1; + netcore_keywords["unchecked"] = 1; + netcore_keywords["unsafe"] = 1; + netcore_keywords["ushort"] = 1; + netcore_keywords["using"] = 1; + netcore_keywords["virtual"] = 1; + netcore_keywords["void"] = 1; + netcore_keywords["volatile"] = 1; + netcore_keywords["while"] = 1; + + // C# contextual keywords + netcore_keywords["add"] = 1; + netcore_keywords["alias"] = 1; + netcore_keywords["ascending"] = 1; + netcore_keywords["async"] = 1; + netcore_keywords["await"] = 1; + netcore_keywords["descending"] = 1; + netcore_keywords["dynamic"] = 1; + netcore_keywords["from"] = 1; + netcore_keywords["get"] = 1; + netcore_keywords["global"] = 1; + netcore_keywords["group"] = 1; + netcore_keywords["into"] = 1; + netcore_keywords["join"] = 1; + netcore_keywords["let"] = 1; + netcore_keywords["orderby"] = 1; + netcore_keywords["partial"] = 1; + netcore_keywords["remove"] = 1; + netcore_keywords["select"] = 1; + netcore_keywords["set"] = 1; + netcore_keywords["value"] = 1; + netcore_keywords["var"] = 1; + netcore_keywords["where"] = 1; + netcore_keywords["yield"] = 1; + + netcore_keywords["when"] = 1; +} + +void t_netcore_generator::start_netcore_namespace(ostream& out) +{ + if (!namespace_name_.empty()) + { + out << "namespace " << namespace_name_ << endl; + scope_up(out); + } +} + +void t_netcore_generator::end_netcore_namespace(ostream& out) +{ + if (!namespace_name_.empty()) + { + scope_down(out); + } +} + +string t_netcore_generator::netcore_type_usings() const +{ + string namespaces = + "using System;\n" + "using System.Collections;\n" + "using System.Collections.Generic;\n" + "using System.Text;\n" + "using System.IO;\n" + "using System.Threading;\n" + "using System.Threading.Tasks;\n" + "using Thrift;\n" + "using Thrift.Collections;\n"; + + if (wcf_) + { + namespaces += "using System.ServiceModel;\n"; + namespaces += "using System.Runtime.Serialization;\n"; + } + + return namespaces + endl; +} + +string t_netcore_generator::netcore_thrift_usings() const +{ + string namespaces = + "using Thrift.Protocols;\n" + "using Thrift.Protocols.Entities;\n" + "using Thrift.Protocols.Utilities;\n" + "using Thrift.Transports;\n" + "using Thrift.Transports.Client;\n" + "using Thrift.Transports.Server;\n"; + + return namespaces + endl; +} + +void t_netcore_generator::close_generator() +{ +} + +void t_netcore_generator::generate_typedef(t_typedef* ttypedef) +{ + (void)ttypedef; +} + +void t_netcore_generator::generate_enum(t_enum* tenum) +{ + int ic = indent_count(); + string f_enum_name = namespace_dir_ + "/" + tenum->get_name() + ".cs"; + + ofstream_with_content_based_conditional_update f_enum; + f_enum.open(f_enum_name.c_str()); + + generate_enum(f_enum, tenum); + + f_enum.close(); + indent_validate(ic, "generate_enum"); +} + +void t_netcore_generator::generate_enum(ostream& out, t_enum* tenum) +{ + out << autogen_comment() << endl; + + start_netcore_namespace(out); + generate_netcore_doc(out, tenum); + + out << indent() << "public enum " << tenum->get_name() << endl; + scope_up(out); + + vector<t_enum_value*> constants = tenum->get_constants(); + vector<t_enum_value*>::iterator c_iter; + + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) + { + generate_netcore_doc(out, *c_iter); + int value = (*c_iter)->get_value(); + out << indent() << (*c_iter)->get_name() << " = " << value << "," << endl; + } + + scope_down(out); + end_netcore_namespace(out); +} + +void t_netcore_generator::generate_consts(vector<t_const*> consts) +{ + if (consts.empty()) + { + return; + } + + string f_consts_name = namespace_dir_ + '/' + program_name_ + ".Constants.cs"; + ofstream_with_content_based_conditional_update f_consts; + f_consts.open(f_consts_name.c_str()); + + generate_consts(f_consts, consts); + + f_consts.close(); +} + +void t_netcore_generator::generate_consts(ostream& out, vector<t_const*> consts) +{ + if (consts.empty()) + { + return; + } + + out << autogen_comment() << netcore_type_usings() << endl; + + start_netcore_namespace(out); + + out << indent() << "public static class " << make_valid_csharp_identifier(program_name_) << "Constants" << endl; + + scope_up(out); + + vector<t_const*>::iterator c_iter; + bool need_static_constructor = false; + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) + { + generate_netcore_doc(out, *c_iter); + if (print_const_value(out, (*c_iter)->get_name(), (*c_iter)->get_type(), (*c_iter)->get_value(), false)) + { + need_static_constructor = true; + } + } + + if (need_static_constructor) + { + print_const_constructor(out, consts); + } + + scope_down(out); + end_netcore_namespace(out); +} + +void t_netcore_generator::print_const_def_value(ostream& out, string name, t_type* type, t_const_value* value) +{ + if (type->is_struct() || type->is_xception()) + { + const vector<t_field*>& fields = static_cast<t_struct*>(type)->get_members(); + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + vector<t_field*>::const_iterator f_iter; + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + prepare_member_name_mapping(static_cast<t_struct*>(type)); + + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) + { + t_field* field = NULL; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + if ((*f_iter)->get_name() == v_iter->first->get_string()) + { + field = *f_iter; + } + } + + if (field == NULL) + { + throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string(); + } + + t_type* field_type = field->get_type(); + + string val = render_const_value(out, name, field_type, v_iter->second); + out << indent() << name << "." << prop_name(field) << " = " << val << ";" << endl; + } + + cleanup_member_name_mapping(static_cast<t_struct*>(type)); + } + else if (type->is_map()) + { + t_type* ktype = static_cast<t_map*>(type)->get_key_type(); + t_type* vtype = static_cast<t_map*>(type)->get_val_type(); + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) + { + string key = render_const_value(out, name, ktype, v_iter->first); + string val = render_const_value(out, name, vtype, v_iter->second); + out << indent() << name << "[" << key << "]" << " = " << val << ";" << endl; + } + } + else if (type->is_list() || type->is_set()) + { + t_type* etype; + if (type->is_list()) + { + etype = static_cast<t_list*>(type)->get_elem_type(); + } + else + { + etype = static_cast<t_set*>(type)->get_elem_type(); + } + + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) + { + string val = render_const_value(out, name, etype, *v_iter); + out << indent() << name << ".Add(" << val << ");" << endl; + } + } +} + +void t_netcore_generator::print_const_constructor(ostream& out, vector<t_const*> consts) +{ + out << indent() << "static " << make_valid_csharp_identifier(program_name_).c_str() << "Constants()" << endl; + scope_up(out); + + vector<t_const*>::iterator c_iter; + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) + { + string name = (*c_iter)->get_name(); + t_type* type = (*c_iter)->get_type(); + t_const_value* value = (*c_iter)->get_value(); + + print_const_def_value(out, name, type, value); + } + scope_down(out); +} + +bool t_netcore_generator::print_const_value(ostream& out, string name, t_type* type, t_const_value* value, bool in_static, bool defval, bool needtype) +{ + out << indent(); + bool need_static_construction = !in_static; + while (type->is_typedef()) + { + type = static_cast<t_typedef*>(type)->get_type(); + } + + if (!defval || needtype) + { + out << (in_static ? "" : type->is_base_type() ? "public const " : "public static ") << type_name(type) << " "; + } + + if (type->is_base_type()) + { + string v2 = render_const_value(out, name, type, value); + out << normalize_name(name) << " = " << v2 << ";" << endl; + need_static_construction = false; + } + else if (type->is_enum()) + { + out << name << " = " << type_name(type, false, true) << "." << value->get_identifier_name() << ";" << endl; + need_static_construction = false; + } + else if (type->is_struct() || type->is_xception()) + { + out << name << " = new " << type_name(type) << "();" << endl; + } + else if (type->is_map()) + { + out << name << " = new " << type_name(type, true, true) << "();" << endl; + } + else if (type->is_list() || type->is_set()) + { + out << name << " = new " << type_name(type) << "();" << endl; + } + + if (defval && !type->is_base_type() && !type->is_enum()) + { + print_const_def_value(out, name, type, value); + } + + return need_static_construction; +} + +string t_netcore_generator::render_const_value(ostream& out, string name, t_type* type, t_const_value* value) +{ + (void)name; + ostringstream render; + + if (type->is_base_type()) + { + t_base_type::t_base tbase = static_cast<t_base_type*>(type)->get_base(); + switch (tbase) + { + case t_base_type::TYPE_STRING: + render << '"' << get_escaped_string(value) << '"'; + break; + case t_base_type::TYPE_BOOL: + render << ((value->get_integer() > 0) ? "true" : "false"); + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + render << value->get_integer(); + break; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) + { + render << value->get_integer(); + } + else + { + render << value->get_double(); + } + break; + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + } + else if (type->is_enum()) + { + render << type->get_name() << "." << value->get_identifier_name(); + } + else + { + string t = tmp("tmp"); + print_const_value(out, t, type, value, true, true, true); + render << t; + } + + return render.str(); +} + +void t_netcore_generator::generate_struct(t_struct* tstruct) +{ + if (union_ && tstruct->is_union()) + { + generate_netcore_union(tstruct); + } + else + { + generate_netcore_struct(tstruct, false); + } +} + +void t_netcore_generator::generate_xception(t_struct* txception) +{ + generate_netcore_struct(txception, true); +} + +void t_netcore_generator::generate_netcore_struct(t_struct* tstruct, bool is_exception) +{ + int ic = indent_count(); + + string f_struct_name = namespace_dir_ + "/" + (tstruct->get_name()) + ".cs"; + ofstream_with_content_based_conditional_update f_struct; + + f_struct.open(f_struct_name.c_str()); + + f_struct << autogen_comment() << netcore_type_usings() << netcore_thrift_usings() << endl; + + generate_netcore_struct_definition(f_struct, tstruct, is_exception); + + f_struct.close(); + + indent_validate(ic, "generate_netcore_struct"); +} + +void t_netcore_generator::generate_netcore_struct_definition(ostream& out, t_struct* tstruct, bool is_exception, bool in_class, bool is_result) +{ + if (!in_class) + { + start_netcore_namespace(out); + } + + out << endl; + + generate_netcore_doc(out, tstruct); + prepare_member_name_mapping(tstruct); + + if ((serialize_ || wcf_) && !is_exception) + { + out << indent() << "[DataContract(Namespace=\"" << wcf_namespace_ << "\")]" << endl; + } + + bool is_final = tstruct->annotations_.find("final") != tstruct->annotations_.end(); + + string sharp_struct_name = check_and_correct_struct_name(normalize_name(tstruct->get_name())); + + out << indent() << "public " << (is_final ? "sealed " : "") << "partial class " << sharp_struct_name << " : "; + + if (is_exception) + { + out << "TException, "; + } + + out << "TBase" << endl + << indent() << "{" << endl; + indent_up(); + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + // make private members with public Properties + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) + { + // if the field is requied, then we use auto-properties + if (!field_is_required((*m_iter)) && (!nullable_ || field_has_default((*m_iter)))) + { + out << indent() << "private " << declare_field(*m_iter, false, "_") << endl; + } + } + out << endl; + + bool has_non_required_fields = false; + bool has_non_required_default_value_fields = false; + bool has_required_fields = false; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) + { + generate_netcore_doc(out, *m_iter); + generate_property(out, *m_iter, true, true); + bool is_required = field_is_required((*m_iter)); + bool has_default = field_has_default((*m_iter)); + if (is_required) + { + has_required_fields = true; + } + else + { + if (has_default) + { + has_non_required_default_value_fields = true; + } + has_non_required_fields = true; + } + } + + bool generate_isset = (nullable_ && has_non_required_default_value_fields) || (!nullable_ && has_non_required_fields); + if (generate_isset) + { + out << endl; + if (serialize_ || wcf_) + { + out << indent() << "[DataMember(Order = 1)]" << endl; + } + out << indent() << "public Isset __isset;" << endl; + if (serialize_ || wcf_) + { + out << indent() << "[DataContract]" << endl; + } + + out << indent() << "public struct Isset" << endl + << indent() << "{" << endl; + indent_up(); + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) + { + bool is_required = field_is_required((*m_iter)); + bool has_default = field_has_default((*m_iter)); + // if it is required, don't need Isset for that variable + // if it is not required, if it has a default value, we need to generate Isset + // if we are not nullable, then we generate Isset + if (!is_required && (!nullable_ || has_default)) + { + if (serialize_ || wcf_) + { + out << indent() << "[DataMember]" << endl; + } + out << indent() << "public bool " << normalize_name((*m_iter)->get_name()) << ";" << endl; + } + } + + indent_down(); + out << indent() << "}" << endl << endl; + + if (generate_isset && (serialize_ || wcf_)) + { + out << indent() << "#region XmlSerializer support" << endl << endl; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) + { + bool is_required = field_is_required(*m_iter); + bool has_default = field_has_default(*m_iter); + // if it is required, don't need Isset for that variable + // if it is not required, if it has a default value, we need to generate Isset + // if we are not nullable, then we generate Isset + if (!is_required && (!nullable_ || has_default)) + { + out << indent() << "public bool ShouldSerialize" << prop_name(*m_iter) << "()" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "return __isset." << normalize_name((*m_iter)->get_name()) << ";" << endl; + indent_down(); + out << indent() << "}" << endl << endl; + } + } + + out << indent() << "#endregion XmlSerializer support" << endl << endl; + } + } + + // We always want a default, no argument constructor for Reading + out << indent() << "public " << sharp_struct_name << "()" << endl + << indent() << "{" << endl; + indent_up(); + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) + { + t_type* t = (*m_iter)->get_type(); + while (t->is_typedef()) + { + t = static_cast<t_typedef*>(t)->get_type(); + } + if ((*m_iter)->get_value() != NULL) + { + if (field_is_required((*m_iter))) + { + print_const_value(out, "this." + prop_name(*m_iter), t, (*m_iter)->get_value(), true, true); + } + else + { + print_const_value(out, "this._" + (*m_iter)->get_name(), t, (*m_iter)->get_value(), true, true); + // Optionals with defaults are marked set + out << indent() << "this.__isset." << normalize_name((*m_iter)->get_name()) << " = true;" << endl; + } + } + } + indent_down(); + out << indent() << "}" << endl << endl; + + if (has_required_fields) + { + out << indent() << "public " << sharp_struct_name << "("; + bool first = true; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) + { + if (field_is_required(*m_iter)) + { + if (first) + { + first = false; + } + else + { + out << ", "; + } + out << type_name((*m_iter)->get_type()) << " " << (*m_iter)->get_name(); + } + } + out << ") : this()" << endl + << indent() << "{" << endl; + indent_up(); + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) + { + if (field_is_required(*m_iter)) + { + out << indent() << "this." << prop_name(*m_iter) << " = " << (*m_iter)->get_name() << ";" << endl; + } + } + + indent_down(); + out << indent() << "}" << endl << endl; + } + + generate_netcore_struct_reader(out, tstruct); + if (is_result) + { + generate_netcore_struct_result_writer(out, tstruct); + } + else + { + generate_netcore_struct_writer(out, tstruct); + } + if (hashcode_) + { + generate_netcore_struct_equals(out, tstruct); + generate_netcore_struct_hashcode(out, tstruct); + } + generate_netcore_struct_tostring(out, tstruct); + + indent_down(); + out << indent() << "}" << endl << endl; + + // generate a corresponding WCF fault to wrap the exception + if ((serialize_ || wcf_) && is_exception) + { + generate_netcore_wcffault(out, tstruct); + } + + cleanup_member_name_mapping(tstruct); + if (!in_class) + { + end_netcore_namespace(out); + } +} + +void t_netcore_generator::generate_netcore_wcffault(ostream& out, t_struct* tstruct) +{ + out << endl; + out << indent() << "[DataContract]" << endl; + + bool is_final = tstruct->annotations_.find("final") != tstruct->annotations_.end(); + + out << indent() << "public " << (is_final ? "sealed " : "") << "partial class " << tstruct->get_name() << "Fault" << endl + << indent() << "{" << endl; + indent_up(); + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + // make private members with public Properties + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) + { + out << indent() << "private " << declare_field(*m_iter, false, "_") << endl; + } + out << endl; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) + { + generate_property(out, *m_iter, true, false); + } + + indent_down(); + out << indent() << "}" << endl << endl; +} + +void t_netcore_generator::generate_netcore_struct_reader(ostream& out, t_struct* tstruct) +{ + out << indent() << "public async Task ReadAsync(TProtocol iprot, CancellationToken cancellationToken)" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "iprot.IncrementRecursionDepth();" << endl + << indent() << "try" << endl + << indent() << "{" << endl; + indent_up(); + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + // Required variables aren't in __isset, so we need tmp vars to check them + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + if (field_is_required(*f_iter)) + { + out << indent() << "bool isset_" << (*f_iter)->get_name() << " = false;" << endl; + } + } + + out << indent() << "TField field;" << endl + << indent() << "await iprot.ReadStructBeginAsync(cancellationToken);" << endl + << indent() << "while (true)" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "field = await iprot.ReadFieldBeginAsync(cancellationToken);" << endl + << indent() << "if (field.Type == TType.Stop)" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "break;" << endl; + indent_down(); + out << indent() << "}" << endl << endl + << indent() << "switch (field.ID)" << endl + << indent() << "{" << endl; + indent_up(); + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + bool is_required = field_is_required(*f_iter); + out << indent() << "case " << (*f_iter)->get_key() << ":" << endl; + indent_up(); + out << indent() << "if (field.Type == " << type_to_enum((*f_iter)->get_type()) << ")" << endl + << indent() << "{" << endl; + indent_up(); + + generate_deserialize_field(out, *f_iter); + if (is_required) + { + out << indent() << "isset_" << (*f_iter)->get_name() << " = true;" << endl; + } + + indent_down(); + out << indent() << "}" << endl + << indent() << "else" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "await TProtocolUtil.SkipAsync(iprot, field.Type, cancellationToken);" << endl; + indent_down(); + out << indent() << "}" << endl + << indent() << "break;" << endl; + indent_down(); + } + + out << indent() << "default: " << endl; + indent_up(); + out << indent() << "await TProtocolUtil.SkipAsync(iprot, field.Type, cancellationToken);" << endl + << indent() << "break;" << endl; + indent_down(); + indent_down(); + out << indent() << "}" << endl + << endl + << indent() << "await iprot.ReadFieldEndAsync(cancellationToken);" << endl; + indent_down(); + out << indent() << "}" << endl + << endl + << indent() << "await iprot.ReadStructEndAsync(cancellationToken);" << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + if (field_is_required((*f_iter))) + { + out << indent() << "if (!isset_" << (*f_iter)->get_name() << ")" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "throw new TProtocolException(TProtocolException.INVALID_DATA);" << endl; + indent_down(); + out << indent() << "}" << endl; + } + } + + indent_down(); + out << indent() << "}" << endl; + out << indent() << "finally" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "iprot.DecrementRecursionDepth();" << endl; + indent_down(); + out << indent() << "}" << endl; + indent_down(); + out << indent() << "}" << endl << endl; +} + +void t_netcore_generator::generate_netcore_struct_writer(ostream& out, t_struct* tstruct) +{ + out << indent() << "public async Task WriteAsync(TProtocol oprot, CancellationToken cancellationToken)" << endl + << indent() << "{" << endl; + indent_up(); + + out << indent() << "oprot.IncrementRecursionDepth();" << endl + << indent() << "try" << endl + << indent() << "{" << endl; + indent_up(); + + string name = tstruct->get_name(); + const vector<t_field*>& fields = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator f_iter; + + out << indent() << "var struc = new TStruct(\"" << name << "\");" << endl + << indent() << "await oprot.WriteStructBeginAsync(struc, cancellationToken);" << endl; + + if (fields.size() > 0) + { + out << indent() << "var field = new TField();" << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + bool is_required = field_is_required(*f_iter); + bool has_default = field_has_default(*f_iter); + if (nullable_ && !has_default && !is_required) + { + out << indent() << "if (" << prop_name(*f_iter) << " != null)" << endl + << indent() << "{" << endl; + indent_up(); + } + else if (!is_required) + { + bool null_allowed = type_can_be_null((*f_iter)->get_type()); + if (null_allowed) + { + out << indent() << "if (" << prop_name(*f_iter) << " != null && __isset." << normalize_name((*f_iter)->get_name()) << ")" << endl + << indent() << "{" << endl; + indent_up(); + } + else + { + out << indent() << "if (__isset." << normalize_name((*f_iter)->get_name()) << ")" << endl + << indent() << "{" << endl; + indent_up(); + } + } + out << indent() << "field.Name = \"" << (*f_iter)->get_name() << "\";" << endl + << indent() << "field.Type = " << type_to_enum((*f_iter)->get_type()) << ";" << endl + << indent() << "field.ID = " << (*f_iter)->get_key() << ";" << endl + << indent() << "await oprot.WriteFieldBeginAsync(field, cancellationToken);" << endl; + + generate_serialize_field(out, *f_iter); + + out << indent() << "await oprot.WriteFieldEndAsync(cancellationToken);" << endl; + if (!is_required) + { + indent_down(); + out << indent() << "}" << endl; + } + } + } + + out << indent() << "await oprot.WriteFieldStopAsync(cancellationToken);" << endl + << indent() << "await oprot.WriteStructEndAsync(cancellationToken);" << endl; + indent_down(); + out << indent() << "}" << endl + << indent() << "finally" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "oprot.DecrementRecursionDepth();" << endl; + indent_down(); + out << indent() << "}" << endl; + indent_down(); + out << indent() << "}" << endl << endl; +} + +void t_netcore_generator::generate_netcore_struct_result_writer(ostream& out, t_struct* tstruct) +{ + out << indent() << "public async Task WriteAsync(TProtocol oprot, CancellationToken cancellationToken)" << endl + << indent() << "{" << endl; + indent_up(); + + out << indent() << "oprot.IncrementRecursionDepth();" << endl + << indent() << "try" << endl + << indent() << "{" << endl; + indent_up(); + + string name = tstruct->get_name(); + const vector<t_field*>& fields = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator f_iter; + + out << indent() << "var struc = new TStruct(\"" << name << "\");" << endl + << indent() << "await oprot.WriteStructBeginAsync(struc, cancellationToken);" << endl; + + if (fields.size() > 0) + { + out << indent() << "var field = new TField();" << endl; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + if (first) + { + first = false; + out << endl << indent() << "if"; + } + else + { + out << indent() << "else if"; + } + + if (nullable_) + { + out << "(this." << prop_name((*f_iter)) << " != null)" << endl + << indent() << "{" << endl; + } + else + { + out << "(this.__isset." << normalize_name((*f_iter)->get_name()) << ")" << endl + << indent() << "{" << endl; + } + indent_up(); + + bool null_allowed = !nullable_ && type_can_be_null((*f_iter)->get_type()); + if (null_allowed) + { + out << indent() << "if (" << prop_name(*f_iter) << " != null)" << endl + << indent() << "{" << endl; + indent_up(); + } + + out << indent() << "field.Name = \"" << prop_name(*f_iter) << "\";" << endl + << indent() << "field.Type = " << type_to_enum((*f_iter)->get_type()) << ";" << endl + << indent() << "field.ID = " << (*f_iter)->get_key() << ";" << endl + << indent() << "await oprot.WriteFieldBeginAsync(field, cancellationToken);" << endl; + + generate_serialize_field(out, *f_iter); + + out << indent() << "await oprot.WriteFieldEndAsync(cancellationToken);" << endl; + + if (null_allowed) + { + indent_down(); + out << indent() << "}" << endl; + } + + indent_down(); + out << indent() << "}" << endl; + } + } + + out << indent() << "await oprot.WriteFieldStopAsync(cancellationToken);" << endl + << indent() << "await oprot.WriteStructEndAsync(cancellationToken);" << endl; + indent_down(); + out << indent() << "}" << endl + << indent() << "finally" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "oprot.DecrementRecursionDepth();" << endl; + indent_down(); + out << indent() << "}" << endl; + indent_down(); + out << indent() << "}" << endl << endl; +} + +void t_netcore_generator::generate_netcore_struct_tostring(ostream& out, t_struct* tstruct) +{ + out << indent() << "public override string ToString()" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "var sb = new StringBuilder(\"" << tstruct->get_name() << "(\");" << endl; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + bool useFirstFlag = false; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + if (!field_is_required((*f_iter))) + { + out << indent() << "bool __first = true;" << endl; + useFirstFlag = true; + } + break; + } + + bool had_required = false; // set to true after first required field has been processed + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + bool is_required = field_is_required((*f_iter)); + bool has_default = field_has_default((*f_iter)); + if (nullable_ && !has_default && !is_required) + { + out << indent() << "if (" << prop_name((*f_iter)) << " != null)" << endl + << indent() << "{" << endl; + indent_up(); + } + else if (!is_required) + { + bool null_allowed = type_can_be_null((*f_iter)->get_type()); + if (null_allowed) + { + out << indent() << "if (" << prop_name((*f_iter)) << " != null && __isset." << normalize_name((*f_iter)->get_name()) << ")" << endl + << indent() << "{" << endl; + indent_up(); + } + else + { + out << indent() << "if (__isset." << normalize_name((*f_iter)->get_name()) << ")" << endl + << indent() << "{" << endl; + indent_up(); + } + } + + if (useFirstFlag && (!had_required)) + { + out << indent() << "if(!__first) { sb.Append(\", \"); }" << endl; + if (!is_required) + { + out << indent() << "__first = false;" << endl; + } + out << indent() << "sb.Append(\"" << prop_name(*f_iter) << ": \");" << endl; + } + else + { + out << indent() << "sb.Append(\", " << prop_name(*f_iter) << ": \");" << endl; + } + + t_type* ttype = (*f_iter)->get_type(); + if (ttype->is_xception() || ttype->is_struct()) + { + out << indent() << "sb.Append(" << prop_name(*f_iter) << "== null ? \"<null>\" : " << prop_name(*f_iter) << ".ToString());" << endl; + } + else + { + out << indent() << "sb.Append(" << prop_name(*f_iter) << ");" << endl; + } + + if (!is_required) + { + indent_down(); + out << indent() << "}" << endl; + } + else + { + had_required = true; // now __first must be false, so we don't need to check it anymore + } + } + + out << indent() << "sb.Append(\")\");" << endl + << indent() << "return sb.ToString();" << endl; + indent_down(); + out << indent() << "}" << endl; +} + +void t_netcore_generator::generate_netcore_union(t_struct* tunion) +{ + int ic = indent_count(); + + string f_union_name = namespace_dir_ + "/" + (tunion->get_name()) + ".cs"; + ofstream_with_content_based_conditional_update f_union; + + f_union.open(f_union_name.c_str()); + + f_union << autogen_comment() << netcore_type_usings() << netcore_thrift_usings() << endl; + + generate_netcore_union_definition(f_union, tunion); + + f_union.close(); + + indent_validate(ic, "generate_netcore_union."); +} + +void t_netcore_generator::generate_netcore_union_definition(ostream& out, t_struct* tunion) +{ + // Let's define the class first + start_netcore_namespace(out); + + out << indent() << "public abstract partial class " << tunion->get_name() << " : TAbstractBase" << endl + << indent() << "{" << endl; + indent_up(); + + out << indent() << "public abstract Task WriteAsync(TProtocol tProtocol, CancellationToken cancellationToken);" << endl + << indent() << "public readonly int Isset;" << endl + << indent() << "public abstract object Data { get; }" << endl + << indent() << "protected " << tunion->get_name() << "(int isset)" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "Isset = isset;" << endl; + indent_down(); + out << indent() << "}" << endl << endl; + + out << indent() << "public class ___undefined : " << tunion->get_name() << endl + << indent() << "{" << endl; + indent_up(); + + out << indent() << "public override object Data { get { return null; } }" << endl + << indent() << "public ___undefined() : base(0) {}" << endl << endl; + + out << indent() << "public override Task WriteAsync(TProtocol oprot, CancellationToken cancellationToken)" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "throw new TProtocolException( TProtocolException.INVALID_DATA, \"Cannot persist an union type which is not set.\");" << endl; + indent_down(); + out << indent() << "}" << endl << endl; + indent_down(); + out << indent() << "}" << endl << endl; + + const vector<t_field*>& fields = tunion->get_members(); + vector<t_field*>::const_iterator f_iter; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + generate_netcore_union_class(out, tunion, (*f_iter)); + } + + generate_netcore_union_reader(out, tunion); + + indent_down(); + out << indent() << "}" << endl << endl; + + end_netcore_namespace(out); +} + +void t_netcore_generator::generate_netcore_union_class(ostream& out, t_struct* tunion, t_field* tfield) +{ + out << indent() << "public " << type_name(tfield->get_type()) << " As_" << tfield->get_name() << endl; + out << indent() << "{" << endl; + indent_up(); + out << indent() << "get" << endl; + out << indent() << "{" << endl; + indent_up(); + out << indent() << "return (" << tfield->get_key() << " == Isset) ? (" << type_name(tfield->get_type()) << ")Data : default(" << type_name(tfield->get_type()) << ");" << endl; + indent_down(); + out << indent() << "}" << endl; + indent_down(); + out << indent() << "}" << endl + << endl; + + + out << indent() << "public class " << tfield->get_name() << " : " << tunion->get_name() << endl + << indent() << "{" << endl; + indent_up(); + + out << indent() << "private " << type_name(tfield->get_type()) << " _data;" << endl + << indent() << "public override object Data { get { return _data; } }" << endl + << indent() << "public " << tfield->get_name() << "(" << type_name(tfield->get_type()) << " data) : base("<< tfield->get_key() <<")" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "this._data = data;" << endl; + indent_down(); + out << indent() << "}" << endl; + + out << indent() << "public override async Task WriteAsync(TProtocol oprot, CancellationToken cancellationToken) {" << endl; + indent_up(); + + out << indent() << "oprot.IncrementRecursionDepth();" << endl + << indent() << "try" << endl + << indent() << "{" << endl; + indent_up(); + + out << indent() << "var struc = new TStruct(\"" << tunion->get_name() << "\");" << endl + << indent() << "await oprot.WriteStructBeginAsync(struc, cancellationToken);" << endl; + + out << indent() << "var field = new TField();" << endl + << indent() << "field.Name = \"" << tfield->get_name() << "\";" << endl + << indent() << "field.Type = " << type_to_enum(tfield->get_type()) << ";" << endl + << indent() << "field.ID = " << tfield->get_key() << ";" << endl + << indent() << "await oprot.WriteFieldBeginAsync(field, cancellationToken);" << endl; + + generate_serialize_field(out, tfield, "_data", true, true); + + out << indent() << "await oprot.WriteFieldEndAsync(cancellationToken);" << endl + << indent() << "await oprot.WriteFieldStopAsync(cancellationToken);" << endl + << indent() << "await oprot.WriteStructEndAsync(cancellationToken);" << endl; + indent_down(); + out << indent() << "}" << endl + << indent() << "finally" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "oprot.DecrementRecursionDepth();" << endl; + indent_down(); + out << indent() << "}" << endl; + out << indent() << "}" << endl; + indent_down(); + out << indent() << "}" << endl << endl; +} + +void t_netcore_generator::generate_netcore_struct_equals(ostream& out, t_struct* tstruct) +{ + out << indent() << "public override bool Equals(object that)" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "var other = that as " << check_and_correct_struct_name(normalize_name(tstruct->get_name())) << ";" << endl + << indent() << "if (other == null) return false;" << endl + << indent() << "if (ReferenceEquals(this, other)) return true;" << endl; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + bool first = true; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + if (first) + { + first = false; + out << indent() << "return "; + indent_up(); + } + else + { + out << endl; + out << indent() << "&& "; + } + if (!field_is_required((*f_iter)) && !(nullable_ && !field_has_default((*f_iter)))) + { + out << "((__isset." << normalize_name((*f_iter)->get_name()) << " == other.__isset." + << normalize_name((*f_iter)->get_name()) << ") && ((!__isset." + << normalize_name((*f_iter)->get_name()) << ") || ("; + } + t_type* ttype = (*f_iter)->get_type(); + if (ttype->is_container() || ttype->is_binary()) + { + out << "TCollections.Equals("; + } + else + { + out << "System.Object.Equals("; + } + out << prop_name((*f_iter)) << ", other." << prop_name((*f_iter)) << ")"; + if (!field_is_required((*f_iter)) && !(nullable_ && !field_has_default((*f_iter)))) + { + out << ")))"; + } + } + if (first) + { + out << indent() << "return true;" << endl; + } + else + { + out << ";" << endl; + indent_down(); + } + + indent_down(); + out << indent() << "}" << endl << endl; +} + +void t_netcore_generator::generate_netcore_struct_hashcode(ostream& out, t_struct* tstruct) +{ + out << indent() << "public override int GetHashCode() {" << endl; + indent_up(); + + out << indent() << "int hashcode = 0;" << endl; + out << indent() << "unchecked {" << endl; + indent_up(); + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + t_type* ttype = (*f_iter)->get_type(); + out << indent() << "hashcode = (hashcode * 397) ^ "; + if (field_is_required((*f_iter))) + { + out << "("; + } + else if (nullable_) + { + out << "(" << prop_name((*f_iter)) << " == null ? 0 : "; + } + else + { + out << "(!__isset." << normalize_name((*f_iter)->get_name()) << " ? 0 : "; + } + if (ttype->is_container()) + { + out << "(TCollections.GetHashCode(" << prop_name((*f_iter)) << "))"; + } + else + { + out << "(" << prop_name((*f_iter)) << ".GetHashCode())"; + } + out << ");" << endl; + } + + indent_down(); + out << indent() << "}" << endl; + out << indent() << "return hashcode;" << endl; + + indent_down(); + out << indent() << "}" << endl << endl; +} + +void t_netcore_generator::generate_service(t_service* tservice) +{ + int ic = indent_count(); + + string f_service_name = namespace_dir_ + "/" + service_name_ + ".cs"; + ofstream_with_content_based_conditional_update f_service; + f_service.open(f_service_name.c_str()); + + f_service << autogen_comment() << netcore_type_usings() << netcore_thrift_usings() << endl; + + start_netcore_namespace(f_service); + + f_service << indent() << "public partial class " << normalize_name(service_name_) << endl + << indent() << "{" << endl; + indent_up(); + + generate_service_interface(f_service, tservice); + generate_service_client(f_service, tservice); + generate_service_server(f_service, tservice); + generate_service_helpers(f_service, tservice); + + indent_down(); + f_service << indent() << "}" << endl; + + end_netcore_namespace(f_service); + f_service.close(); + + indent_validate(ic, "generate_service."); +} + +void t_netcore_generator::generate_service_interface(ostream& out, t_service* tservice) +{ + string extends = ""; + string extends_iface = ""; + if (tservice->get_extends() != NULL) + { + extends = type_name(tservice->get_extends()); + extends_iface = " : " + extends + ".IAsync"; + } + + //out << endl << endl; + + generate_netcore_doc(out, tservice); + + if (wcf_) + { + out << indent() << "[ServiceContract(Namespace=\"" << wcf_namespace_ << "\")]" << endl; + } + + out << indent() << "public interface IAsync" << extends_iface << endl + << indent() << "{" << endl; + + indent_up(); + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) + { + generate_netcore_doc(out, *f_iter); + + // if we're using WCF, add the corresponding attributes + if (wcf_) + { + out << indent() << "[OperationContract]" << endl; + + const vector<t_field*>& xceptions = (*f_iter)->get_xceptions()->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) + { + out << indent() << "[FaultContract(typeof(" + type_name((*x_iter)->get_type(), false, false) + "Fault))]" << endl; + } + } + + out << indent() << function_signature_async(*f_iter) << ";" << endl << endl; + } + indent_down(); + out << indent() << "}" << endl << endl; +} + +void t_netcore_generator::generate_service_helpers(ostream& out, t_service* tservice) +{ + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) + { + t_struct* ts = (*f_iter)->get_arglist(); + generate_netcore_struct_definition(out, ts, false, true); + generate_function_helpers(out, *f_iter); + } +} + +void t_netcore_generator::generate_service_client(ostream& out, t_service* tservice) +{ + string extends = ""; + string extends_client = ""; + if (tservice->get_extends() != NULL) + { + extends = type_name(tservice->get_extends()); + extends_client = extends + ".Client, "; + } + else + { + extends_client = "TBaseClient, IDisposable, "; + } + + out << endl; + + generate_netcore_doc(out, tservice); + + out << indent() << "public class Client : " << extends_client << "IAsync" << endl + << indent() << "{" << endl; + indent_up(); + + out << indent() << "public Client(TProtocol protocol) : this(protocol, protocol)" << endl + << indent() << "{" << endl + << indent() << "}" << endl + << endl + << indent() << "public Client(TProtocol inputProtocol, TProtocol outputProtocol) : base(inputProtocol, outputProtocol)" + << indent() << "{" << endl + << indent() << "}" << endl; + + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator functions_iterator; + + for (functions_iterator = functions.begin(); functions_iterator != functions.end(); ++functions_iterator) + { + string function_name = correct_function_name_for_async((*functions_iterator)->get_name()); + + // async + out << indent() << "public async " << function_signature_async(*functions_iterator, "") << endl + << indent() << "{" << endl; + indent_up(); + + string argsname = (*functions_iterator)->get_name() + "Args"; + + out << indent() << "await OutputProtocol.WriteMessageBeginAsync(new TMessage(\"" << function_name + << "\", " << ((*functions_iterator)->is_oneway() ? "TMessageType.Oneway" : "TMessageType.Call") << ", SeqId), cancellationToken);" << endl + << indent() << endl + << indent() << "var args = new " << argsname << "();" << endl; + + t_struct* arg_struct = (*functions_iterator)->get_arglist(); + prepare_member_name_mapping(arg_struct); + const vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator fld_iter; + + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) + { + out << indent() << "args." << prop_name(*fld_iter) << " = " << normalize_name((*fld_iter)->get_name()) << ";" << endl; + } + + out << indent() << endl + << indent() << "await args.WriteAsync(OutputProtocol, cancellationToken);" << endl + << indent() << "await OutputProtocol.WriteMessageEndAsync(cancellationToken);" << endl + << indent() << "await OutputProtocol.Transport.FlushAsync(cancellationToken);" << endl; + + if (!(*functions_iterator)->is_oneway()) + { + string resultname = (*functions_iterator)->get_name() + "Result"; + t_struct noargs(program_); + t_struct* xs = (*functions_iterator)->get_xceptions(); + prepare_member_name_mapping(xs, xs->get_members(), resultname); + + out << indent() << endl + << indent() << "var msg = await InputProtocol.ReadMessageBeginAsync(cancellationToken);" << endl + << indent() << "if (msg.Type == TMessageType.Exception)" << endl + << indent() << "{" << endl; + indent_up(); + + out << indent() << "var x = await TApplicationException.ReadAsync(InputProtocol, cancellationToken);" << endl + << indent() << "await InputProtocol.ReadMessageEndAsync(cancellationToken);" << endl + << indent() << "throw x;" << endl; + indent_down(); + + out << indent() << "}" << endl + << endl + << indent() << "var result = new " << resultname << "();" << endl + << indent() << "await result.ReadAsync(InputProtocol, cancellationToken);" << endl + << indent() << "await InputProtocol.ReadMessageEndAsync(cancellationToken);" << endl; + + if (!(*functions_iterator)->get_returntype()->is_void()) + { + if (nullable_) + { + if (type_can_be_null((*functions_iterator)->get_returntype())) + { + out << indent() << "if (result.Success != null)" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "return result.Success;" << endl; + indent_down(); + out << indent() << "}" << endl; + } + else + { + out << indent() << "if (result.Success.HasValue)" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "return result.Success.Value;" << endl; + indent_down(); + out << indent() << "}" << endl; + } + } + else + { + out << indent() << "if (result.__isset.success)" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "return result.Success;" << endl; + indent_down(); + out << indent() << "}" << endl; + } + } + + const vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) + { + if (nullable_) + { + out << indent() << "if (result." << prop_name(*x_iter) << " != null)" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "throw result." << prop_name(*x_iter) << ";" << endl; + indent_down(); + out << indent() << "}" << endl; + } + else + { + out << indent() << "if (result.__isset." << normalize_name((*x_iter)->get_name()) << ")" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "throw result." << prop_name(*x_iter) << ";" << endl; + indent_down(); + out << indent() << "}" << endl; + } + } + + if ((*functions_iterator)->get_returntype()->is_void()) + { + out << indent() << "return;" << endl; + } + else + { + out << indent() << "throw new TApplicationException(TApplicationException.ExceptionType.MissingResult, \"" + << function_name << " failed: unknown result\");" << endl; + } + + cleanup_member_name_mapping((*functions_iterator)->get_xceptions()); + indent_down(); + out << indent() << "}" << endl << endl; + } + else + { + indent_down(); + out << indent() << "}" << endl; + } + } + + indent_down(); + out << indent() << "}" << endl << endl; +} + +void t_netcore_generator::generate_service_server(ostream& out, t_service* tservice) +{ + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + string extends = ""; + string extends_processor = ""; + if (tservice->get_extends() != NULL) + { + extends = type_name(tservice->get_extends()); + extends_processor = extends + ".AsyncProcessor, "; + } + + out << indent() << "public class AsyncProcessor : " << extends_processor << "ITAsyncProcessor" << endl + << indent() << "{" << endl; + + indent_up(); + + out << indent() << "private IAsync _iAsync;" << endl + << endl + << indent() << "public AsyncProcessor(IAsync iAsync)"; + + if (!extends.empty()) + { + out << " : base(iAsync)"; + } + + out << endl + << indent() << "{" << endl; + indent_up(); + + out << indent() << "if (iAsync == null) throw new ArgumentNullException(nameof(iAsync));" << endl + << endl + << indent() << "_iAsync = iAsync;" << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) + { + string function_name = (*f_iter)->get_name(); + out << indent() << "processMap_[\"" << correct_function_name_for_async(function_name) << "\"] = " << function_name << "_ProcessAsync;" << endl; + } + + indent_down(); + out << indent() << "}" << endl + << endl; + + if (extends.empty()) + { + out << indent() << "protected delegate Task ProcessFunction(int seqid, TProtocol iprot, TProtocol oprot, CancellationToken cancellationToken);" << endl; + } + + if (extends.empty()) + { + out << indent() << "protected Dictionary<string, ProcessFunction> processMap_ = new Dictionary<string, ProcessFunction>();" << endl; + } + + out << endl; + + if (extends.empty()) + { + out << indent() << "public async Task<bool> ProcessAsync(TProtocol iprot, TProtocol oprot)" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "return await ProcessAsync(iprot, oprot, CancellationToken.None);" << endl; + indent_down(); + out << indent() << "}" << endl << endl; + + out << indent() << "public async Task<bool> ProcessAsync(TProtocol iprot, TProtocol oprot, CancellationToken cancellationToken)" << endl; + } + else + { + out << indent() << "public new async Task<bool> ProcessAsync(TProtocol iprot, TProtocol oprot)" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "return await ProcessAsync(iprot, oprot, CancellationToken.None);" << endl; + indent_down(); + out << indent() << "}" << endl << endl; + + out << indent() << "public new async Task<bool> ProcessAsync(TProtocol iprot, TProtocol oprot, CancellationToken cancellationToken)" << endl; + } + + out << indent() << "{" << endl; + indent_up(); + out << indent() << "try" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "var msg = await iprot.ReadMessageBeginAsync(cancellationToken);" << endl + << endl + << indent() << "ProcessFunction fn;" << endl + << indent() << "processMap_.TryGetValue(msg.Name, out fn);" << endl + << endl + << indent() << "if (fn == null)" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "await TProtocolUtil.SkipAsync(iprot, TType.Struct, cancellationToken);" << endl + << indent() << "await iprot.ReadMessageEndAsync(cancellationToken);" << endl + << indent() << "var x = new TApplicationException (TApplicationException.ExceptionType.UnknownMethod, \"Invalid method name: '\" + msg.Name + \"'\");" << endl + << indent() << "await oprot.WriteMessageBeginAsync(new TMessage(msg.Name, TMessageType.Exception, msg.SeqID), cancellationToken);" << endl + << indent() << "await x.WriteAsync(oprot, cancellationToken);" << endl + << indent() << "await oprot.WriteMessageEndAsync(cancellationToken);" << endl + << indent() << "await oprot.Transport.FlushAsync(cancellationToken);" << endl + << indent() << "return true;" << endl; + indent_down(); + out << indent() << "}" << endl + << endl + << indent() << "await fn(msg.SeqID, iprot, oprot, cancellationToken);" << endl + << endl; + indent_down(); + out << indent() << "}" << endl; + out << indent() << "catch (IOException)" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "return false;" << endl; + indent_down(); + out << indent() << "}" << endl + << endl + << indent() << "return true;" << endl; + indent_down(); + out << indent() << "}" << endl << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) + { + generate_process_function_async(out, tservice, *f_iter); + } + + indent_down(); + out << indent() << "}" << endl << endl; +} + +void t_netcore_generator::generate_function_helpers(ostream& out, t_function* tfunction) +{ + if (tfunction->is_oneway()) + { + return; + } + + t_struct result(program_, tfunction->get_name() + "_result"); + t_field success(tfunction->get_returntype(), "success", 0); + if (!tfunction->get_returntype()->is_void()) + { + result.append(&success); + } + + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& fields = xs->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + result.append(*f_iter); + } + + generate_netcore_struct_definition(out, &result, false, true, true); +} + +void t_netcore_generator::generate_process_function_async(ostream& out, t_service* tservice, t_function* tfunction) +{ + (void)tservice; + out << indent() << "public async Task " << tfunction->get_name() + << "_ProcessAsync(int seqid, TProtocol iprot, TProtocol oprot, CancellationToken cancellationToken)" << endl + << indent() << "{" << endl; + indent_up(); + + string argsname = tfunction->get_name() + "Args"; + string resultname = tfunction->get_name() + "Result"; + + out << indent() << "var args = new " << argsname << "();" << endl + << indent() << "await args.ReadAsync(iprot, cancellationToken);" << endl + << indent() << "await iprot.ReadMessageEndAsync(cancellationToken);" << endl; + + if (!tfunction->is_oneway()) + { + out << indent() << "var result = new " << resultname << "();" << endl; + } + + out << indent() << "try" << endl + << indent() << "{" << endl; + indent_up(); + + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& xceptions = xs->get_members(); + + if (xceptions.size() > 0) + { + out << indent() << "try" << endl + << indent() << "{" << endl; + indent_up(); + } + + t_struct* arg_struct = tfunction->get_arglist(); + const vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + + out << indent(); + if (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void()) + { + out << "result.Success = "; + } + + out << "await _iAsync." << normalize_name(tfunction->get_name()) << "Async("; + + bool first = true; + prepare_member_name_mapping(arg_struct); + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + if (first) + { + first = false; + } + else + { + out << ", "; + } + + out << "args." << prop_name(*f_iter); + if (nullable_ && !type_can_be_null((*f_iter)->get_type())) + { + out << ".Value"; + } + } + + cleanup_member_name_mapping(arg_struct); + + if (!first) + { + out << ", "; + } + + out << "cancellationToken);" << endl; + + vector<t_field*>::const_iterator x_iter; + + prepare_member_name_mapping(xs, xs->get_members(), resultname); + if (xceptions.size() > 0) + { + indent_down(); + out << indent() << "}" << endl; + + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) + { + out << indent() << "catch (" << type_name((*x_iter)->get_type(), false, false) << " " << (*x_iter)->get_name() << ")" << endl + << indent() << "{" << endl; + + if (!tfunction->is_oneway()) + { + indent_up(); + out << indent() << "result." << prop_name(*x_iter) << " = " << (*x_iter)->get_name() << ";" << endl; + indent_down(); + } + out << indent() << "}" << endl; + } + } + + if (!tfunction->is_oneway()) + { + out << indent() << "await oprot.WriteMessageBeginAsync(new TMessage(\"" + << correct_function_name_for_async(tfunction->get_name()) << "\", TMessageType.Reply, seqid), cancellationToken); " << endl + << indent() << "await result.WriteAsync(oprot, cancellationToken);" << endl; + } + indent_down(); + + cleanup_member_name_mapping(xs); + + out << indent() << "}" << endl + << indent() << "catch (TTransportException)" << endl + << indent() << "{" << endl + << indent() << " throw;" << endl + << indent() << "}" << endl + << indent() << "catch (Exception ex)" << endl + << indent() << "{" << endl; + indent_up(); + + out << indent() << "Console.Error.WriteLine(\"Error occurred in processor:\");" << endl + << indent() << "Console.Error.WriteLine(ex.ToString());" << endl; + + if (tfunction->is_oneway()) + { + indent_down(); + out << indent() << "}" << endl; + } + else + { + out << indent() << "var x = new TApplicationException(TApplicationException.ExceptionType.InternalError,\" Internal error.\");" << endl + << indent() << "await oprot.WriteMessageBeginAsync(new TMessage(\"" << correct_function_name_for_async(tfunction->get_name()) + << "\", TMessageType.Exception, seqid), cancellationToken);" << endl + << indent() << "await x.WriteAsync(oprot, cancellationToken);" << endl; + indent_down(); + + out << indent() << "}" << endl + << indent() << "await oprot.WriteMessageEndAsync(cancellationToken);" << endl + << indent() << "await oprot.Transport.FlushAsync(cancellationToken);" << endl; + } + + indent_down(); + out << indent() << "}" << endl << endl; +} + +void t_netcore_generator::generate_netcore_union_reader(ostream& out, t_struct* tunion) +{ + // Thanks to THRIFT-1768, we don't need to check for required fields in the union + const vector<t_field*>& fields = tunion->get_members(); + vector<t_field*>::const_iterator f_iter; + + out << indent() << "public static async Task<" << tunion->get_name() << "> ReadAsync(TProtocol iprot, CancellationToken cancellationToken)" << endl; + scope_up(out); + + out << indent() << "iprot.IncrementRecursionDepth();" << endl; + out << indent() << "try" << endl; + scope_up(out); + + out << indent() << tunion->get_name() << " retval;" << endl; + out << indent() << "await iprot.ReadStructBeginAsync(cancellationToken);" << endl; + out << indent() << "TField field = await iprot.ReadFieldBeginAsync(cancellationToken);" << endl; + // we cannot have the first field be a stop -- we must have a single field defined + out << indent() << "if (field.Type == TType.Stop)" << endl; + scope_up(out); + out << indent() << "await iprot.ReadFieldEndAsync(cancellationToken);" << endl; + out << indent() << "retval = new ___undefined();" << endl; + scope_down(out); + out << indent() << "else" << endl; + scope_up(out); + out << indent() << "switch (field.ID)" << endl; + scope_up(out); + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + out << indent() << "case " << (*f_iter)->get_key() << ":" << endl; + indent_up(); + out << indent() << "if (field.Type == " << type_to_enum((*f_iter)->get_type()) << ") {" << endl; + indent_up(); + + out << indent() << type_name((*f_iter)->get_type()) << " temp;" << endl; + generate_deserialize_field(out, (*f_iter), "temp", true); + out << indent() << "retval = new " << (*f_iter)->get_name() << "(temp);" << endl; + + indent_down(); + out << indent() << "} else { " << endl << indent() << " await TProtocolUtil.SkipAsync(iprot, field.Type, cancellationToken);" + << endl << indent() << " retval = new ___undefined();" << endl << indent() << "}" << endl + << indent() << "break;" << endl; + indent_down(); + } + + out << indent() << "default: " << endl; + indent_up(); + out << indent() << "await TProtocolUtil.SkipAsync(iprot, field.Type, cancellationToken);" << endl << indent() + << "retval = new ___undefined();" << endl; + out << indent() << "break;" << endl; + indent_down(); + + scope_down(out); + + out << indent() << "await iprot.ReadFieldEndAsync(cancellationToken);" << endl; + + out << indent() << "if ((await iprot.ReadFieldBeginAsync(cancellationToken)).Type != TType.Stop)" << endl; + scope_up(out); + out << indent() << "throw new TProtocolException(TProtocolException.INVALID_DATA);" << endl; + scope_down(out); + + // end of else for TStop + scope_down(out); + out << indent() << "await iprot.ReadStructEndAsync(cancellationToken);" << endl; + out << indent() << "return retval;" << endl; + indent_down(); + + scope_down(out); + out << indent() << "finally" << endl; + scope_up(out); + out << indent() << "iprot.DecrementRecursionDepth();" << endl; + scope_down(out); + + out << indent() << "}" << endl << endl; +} + +void t_netcore_generator::generate_deserialize_field(ostream& out, t_field* tfield, string prefix, bool is_propertyless) +{ + t_type* type = tfield->get_type(); + while (type->is_typedef()) + { + type = static_cast<t_typedef*>(type)->get_type(); + } + + if (type->is_void()) + { + throw "CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name(); + } + + string name = prefix + (is_propertyless ? "" : prop_name(tfield)); + + if (type->is_struct() || type->is_xception()) + { + generate_deserialize_struct(out, static_cast<t_struct*>(type), name); + } + else if (type->is_container()) + { + generate_deserialize_container(out, type, name); + } + else if (type->is_base_type() || type->is_enum()) + { + out << indent() << name << " = "; + + if (type->is_enum()) + { + out << "(" << type_name(type, false, true) << ")"; + } + + out << "await iprot."; + + if (type->is_base_type()) + { + t_base_type::t_base tbase = static_cast<t_base_type*>(type)->get_base(); + switch (tbase) + { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + if (type->is_binary()) + { + out << "ReadBinaryAsync(cancellationToken);"; + } + else + { + out << "ReadStringAsync(cancellationToken);"; + } + break; + case t_base_type::TYPE_BOOL: + out << "ReadBoolAsync(cancellationToken);"; + break; + case t_base_type::TYPE_I8: + out << "ReadByteAsync(cancellationToken);"; + break; + case t_base_type::TYPE_I16: + out << "ReadI16Async(cancellationToken);"; + break; + case t_base_type::TYPE_I32: + out << "ReadI32Async(cancellationToken);"; + break; + case t_base_type::TYPE_I64: + out << "ReadI64Async(cancellationToken);"; + break; + case t_base_type::TYPE_DOUBLE: + out << "ReadDoubleAsync(cancellationToken);"; + break; + default: + throw "compiler error: no C# name for base type " + t_base_type::t_base_name(tbase); + } + } + else if (type->is_enum()) + { + out << "ReadI32Async(cancellationToken);"; + } + out << endl; + } + else + { + printf("DO NOT KNOW HOW TO DESERIALIZE FIELD '%s' TYPE '%s'\n", tfield->get_name().c_str(), type_name(type).c_str()); + } +} + +void t_netcore_generator::generate_deserialize_struct(ostream& out, t_struct* tstruct, string prefix) +{ + if (union_ && tstruct->is_union()) + { + out << indent() << prefix << " = await " << type_name(tstruct) << ".ReadAsync(iprot, cancellationToken);" << endl; + } + else + { + out << indent() << prefix << " = new " << type_name(tstruct) << "();" << endl + << indent() << "await " << prefix << ".ReadAsync(iprot, cancellationToken);" << endl; + } +} + +void t_netcore_generator::generate_deserialize_container(ostream& out, t_type* ttype, string prefix) +{ + out << indent() << "{" << endl; + indent_up(); + + string obj; + + if (ttype->is_map()) + { + obj = tmp("_map"); + } + else if (ttype->is_set()) + { + obj = tmp("_set"); + } + else if (ttype->is_list()) + { + obj = tmp("_list"); + } + + out << indent() << prefix << " = new " << type_name(ttype, false, true) << "();" << endl; + if (ttype->is_map()) + { + out << indent() << "TMap " << obj << " = await iprot.ReadMapBeginAsync(cancellationToken);" << endl; + } + else if (ttype->is_set()) + { + out << indent() << "TSet " << obj << " = await iprot.ReadSetBeginAsync(cancellationToken);" << endl; + } + else if (ttype->is_list()) + { + out << indent() << "TList " << obj << " = await iprot.ReadListBeginAsync(cancellationToken);" << endl; + } + + string i = tmp("_i"); + out << indent() << "for(int " << i << " = 0; " << i << " < " << obj << ".Count; ++" << i << ")" << endl + << indent() << "{" << endl; + indent_up(); + + if (ttype->is_map()) + { + generate_deserialize_map_element(out, static_cast<t_map*>(ttype), prefix); + } + else if (ttype->is_set()) + { + generate_deserialize_set_element(out, static_cast<t_set*>(ttype), prefix); + } + else if (ttype->is_list()) + { + generate_deserialize_list_element(out, static_cast<t_list*>(ttype), prefix); + } + + indent_down(); + out << indent() << "}" << endl; + + if (ttype->is_map()) + { + out << indent() << "await iprot.ReadMapEndAsync(cancellationToken);" << endl; + } + else if (ttype->is_set()) + { + out << indent() << "await iprot.ReadSetEndAsync(cancellationToken);" << endl; + } + else if (ttype->is_list()) + { + out << indent() << "await iprot.ReadListEndAsync(cancellationToken);" << endl; + } + + indent_down(); + out << indent() << "}" << endl; +} + +void t_netcore_generator::generate_deserialize_map_element(ostream& out, t_map* tmap, string prefix) +{ + string key = tmp("_key"); + string val = tmp("_val"); + + t_field fkey(tmap->get_key_type(), key); + t_field fval(tmap->get_val_type(), val); + + out << indent() << declare_field(&fkey) << endl; + out << indent() << declare_field(&fval) << endl; + + generate_deserialize_field(out, &fkey); + generate_deserialize_field(out, &fval); + + out << indent() << prefix << "[" << key << "] = " << val << ";" << endl; +} + +void t_netcore_generator::generate_deserialize_set_element(ostream& out, t_set* tset, string prefix) +{ + string elem = tmp("_elem"); + t_field felem(tset->get_elem_type(), elem); + + out << indent() << declare_field(&felem) << endl; + + generate_deserialize_field(out, &felem); + + out << indent() << prefix << ".Add(" << elem << ");" << endl; +} + +void t_netcore_generator::generate_deserialize_list_element(ostream& out, t_list* tlist, string prefix) +{ + string elem = tmp("_elem"); + t_field felem(tlist->get_elem_type(), elem); + + out << indent() << declare_field(&felem) << endl; + + generate_deserialize_field(out, &felem); + + out << indent() << prefix << ".Add(" << elem << ");" << endl; +} + +void t_netcore_generator::generate_serialize_field(ostream& out, t_field* tfield, string prefix, bool is_element, bool is_propertyless) +{ + t_type* type = tfield->get_type(); + while (type->is_typedef()) + { + type = static_cast<t_typedef*>(type)->get_type(); + } + + string name = prefix + (is_propertyless ? "" : prop_name(tfield)); + + if (type->is_void()) + { + throw "CANNOT GENERATE SERIALIZE CODE FOR void TYPE: " + name; + } + + if (type->is_struct() || type->is_xception()) + { + generate_serialize_struct(out, static_cast<t_struct*>(type), name); + } + else if (type->is_container()) + { + generate_serialize_container(out, type, name); + } + else if (type->is_base_type() || type->is_enum()) + { + out << indent() << "await oprot."; + + string nullable_name = nullable_ && !is_element && !field_is_required(tfield) ? name + ".Value" : name; + + if (type->is_base_type()) + { + t_base_type::t_base tbase = static_cast<t_base_type*>(type)->get_base(); + switch (tbase) + { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + case t_base_type::TYPE_STRING: + if (type->is_binary()) + { + out << "WriteBinaryAsync("; + } + else + { + out << "WriteStringAsync("; + } + out << name << ", cancellationToken);"; + break; + case t_base_type::TYPE_BOOL: + out << "WriteBoolAsync(" << nullable_name << ", cancellationToken);"; + break; + case t_base_type::TYPE_I8: + out << "WriteByteAsync(" << nullable_name << ", cancellationToken);"; + break; + case t_base_type::TYPE_I16: + out << "WriteI16Async(" << nullable_name << ", cancellationToken);"; + break; + case t_base_type::TYPE_I32: + out << "WriteI32Async(" << nullable_name << ", cancellationToken);"; + break; + case t_base_type::TYPE_I64: + out << "WriteI64Async(" << nullable_name << ", cancellationToken);"; + break; + case t_base_type::TYPE_DOUBLE: + out << "WriteDoubleAsync(" << nullable_name << ", cancellationToken);"; + break; + default: + throw "compiler error: no C# name for base type " + t_base_type::t_base_name(tbase); + } + } + else if (type->is_enum()) + { + out << "WriteI32Async((int)" << nullable_name << ", cancellationToken);"; + } + out << endl; + } + else + { + printf("DO NOT KNOW HOW TO SERIALIZE '%s%s' TYPE '%s'\n", prefix.c_str(), tfield->get_name().c_str(), type_name(type).c_str()); + } +} + +void t_netcore_generator::generate_serialize_struct(ostream& out, t_struct* tstruct, string prefix) +{ + (void)tstruct; + out << indent() << "await " << prefix << ".WriteAsync(oprot, cancellationToken);" << endl; +} + +void t_netcore_generator::generate_serialize_container(ostream& out, t_type* ttype, string prefix) +{ + out << indent() << "{" << endl; + indent_up(); + + if (ttype->is_map()) + { + out << indent() << "await oprot.WriteMapBeginAsync(new TMap(" << type_to_enum(static_cast<t_map*>(ttype)->get_key_type()) + << ", " << type_to_enum(static_cast<t_map*>(ttype)->get_val_type()) << ", " << prefix + << ".Count), cancellationToken);" << endl; + } + else if (ttype->is_set()) + { + out << indent() << "await oprot.WriteSetBeginAsync(new TSet(" << type_to_enum(static_cast<t_set*>(ttype)->get_elem_type()) + << ", " << prefix << ".Count), cancellationToken);" << endl; + } + else if (ttype->is_list()) + { + out << indent() << "await oprot.WriteListBeginAsync(new TList(" + << type_to_enum(static_cast<t_list*>(ttype)->get_elem_type()) << ", " << prefix << ".Count), cancellationToken);" + << endl; + } + + string iter = tmp("_iter"); + if (ttype->is_map()) + { + out << indent() << "foreach (" << type_name(static_cast<t_map*>(ttype)->get_key_type()) << " " << iter + << " in " << prefix << ".Keys)"; + } + else if (ttype->is_set()) + { + out << indent() << "foreach (" << type_name(static_cast<t_set*>(ttype)->get_elem_type()) << " " << iter + << " in " << prefix << ")"; + } + else if (ttype->is_list()) + { + out << indent() << "foreach (" << type_name(static_cast<t_list*>(ttype)->get_elem_type()) << " " << iter + << " in " << prefix << ")"; + } + + out << endl; + out << indent() << "{" << endl; + indent_up(); + + if (ttype->is_map()) + { + generate_serialize_map_element(out, static_cast<t_map*>(ttype), iter, prefix); + } + else if (ttype->is_set()) + { + generate_serialize_set_element(out, static_cast<t_set*>(ttype), iter); + } + else if (ttype->is_list()) + { + generate_serialize_list_element(out, static_cast<t_list*>(ttype), iter); + } + + indent_down(); + out << indent() << "}" << endl; + + if (ttype->is_map()) + { + out << indent() << "await oprot.WriteMapEndAsync(cancellationToken);" << endl; + } + else if (ttype->is_set()) + { + out << indent() << "await oprot.WriteSetEndAsync(cancellationToken);" << endl; + } + else if (ttype->is_list()) + { + out << indent() << "await oprot.WriteListEndAsync(cancellationToken);" << endl; + } + + indent_down(); + out << indent() << "}" << endl; +} + +void t_netcore_generator::generate_serialize_map_element(ostream& out, t_map* tmap, string iter, string map) +{ + t_field kfield(tmap->get_key_type(), iter); + generate_serialize_field(out, &kfield, "", true); + t_field vfield(tmap->get_val_type(), map + "[" + iter + "]"); + generate_serialize_field(out, &vfield, "", true); +} + +void t_netcore_generator::generate_serialize_set_element(ostream& out, t_set* tset, string iter) +{ + t_field efield(tset->get_elem_type(), iter); + generate_serialize_field(out, &efield, "", true); +} + +void t_netcore_generator::generate_serialize_list_element(ostream& out, t_list* tlist, string iter) +{ + t_field efield(tlist->get_elem_type(), iter); + generate_serialize_field(out, &efield, "", true); +} + +void t_netcore_generator::generate_property(ostream& out, t_field* tfield, bool isPublic, bool generateIsset) +{ + generate_netcore_property(out, tfield, isPublic, generateIsset, "_"); +} + +void t_netcore_generator::generate_netcore_property(ostream& out, t_field* tfield, bool isPublic, bool generateIsset, string fieldPrefix) +{ + if ((serialize_ || wcf_) && isPublic) + { + out << indent() << "[DataMember(Order = 0)]" << endl; + } + bool has_default = field_has_default(tfield); + bool is_required = field_is_required(tfield); + if ((nullable_ && !has_default) || is_required) + { + out << indent() << (isPublic ? "public " : "private ") << type_name(tfield->get_type(), false, false, true, is_required) << " " << prop_name(tfield) << " { get; set; }" << endl; + } + else + { + out << indent() << (isPublic ? "public " : "private ") << type_name(tfield->get_type(), false, false, true) << " " << prop_name(tfield) << endl + << indent() << "{" << endl; + indent_up(); + + out << indent() << "get" << endl + << indent() << "{" << endl; + indent_up(); + + bool use_nullable = false; + if (nullable_) + { + t_type* ttype = tfield->get_type(); + while (ttype->is_typedef()) + { + ttype = static_cast<t_typedef*>(ttype)->get_type(); + } + if (ttype->is_base_type()) + { + use_nullable = static_cast<t_base_type*>(ttype)->get_base() != t_base_type::TYPE_STRING; + } + } + + out << indent() << "return " << fieldPrefix + tfield->get_name() << ";" << endl; + indent_down(); + out << indent() << "}" << endl + << indent() << "set" << endl + << indent() << "{" << endl; + indent_up(); + + if (use_nullable) + { + if (generateIsset) + { + out << indent() << "__isset." << normalize_name(tfield->get_name()) << " = value.HasValue;" << endl; + } + out << indent() << "if (value.HasValue) this." << fieldPrefix + tfield->get_name() << " = value.Value;" << endl; + } + else + { + if (generateIsset) + { + out << indent() << "__isset." << normalize_name(tfield->get_name()) << " = true;" << endl; + } + out << indent() << "this." << fieldPrefix + tfield->get_name() << " = value;" << endl; + } + + indent_down(); + out << indent() << "}" << endl; + indent_down(); + out << indent() << "}" << endl; + } + out << endl; +} + +string t_netcore_generator::make_valid_csharp_identifier(string const& fromName) +{ + string str = fromName; + if (str.empty()) + { + return str; + } + + // tests rely on this + assert(('A' < 'Z') && ('a' < 'z') && ('0' < '9')); + + // if the first letter is a number, we add an additional underscore in front of it + char c = str.at(0); + if (('0' <= c) && (c <= '9')) + { + str = "_" + str; + } + + // following chars: letter, number or underscore + for (size_t i = 0; i < str.size(); ++i) + { + c = str.at(i); + if (('A' > c || c > 'Z') && ('a' > c || c > 'z') && ('0' > c || c > '9') && '_' != c) + { + str.replace(i, 1, "_"); + } + } + + return str; +} + +void t_netcore_generator::cleanup_member_name_mapping(void* scope) +{ + if (member_mapping_scopes.empty()) + { + throw "internal error: cleanup_member_name_mapping() no scope active"; + } + + member_mapping_scope& active = member_mapping_scopes.back(); + if (active.scope_member != scope) + { + throw "internal error: cleanup_member_name_mapping() called for wrong struct"; + } + + member_mapping_scopes.pop_back(); +} + +string t_netcore_generator::get_mapped_member_name(string name) +{ + if (!member_mapping_scopes.empty()) + { + member_mapping_scope& active = member_mapping_scopes.back(); + map<string, string>::iterator iter = active.mapping_table.find(name); + if (active.mapping_table.end() != iter) + { + return iter->second; + } + } + + pverbose("no mapping for member %s\n", name.c_str()); + return name; +} + +void t_netcore_generator::prepare_member_name_mapping(t_struct* tstruct) +{ + prepare_member_name_mapping(tstruct, tstruct->get_members(), tstruct->get_name()); +} + +void t_netcore_generator::prepare_member_name_mapping(void* scope, const vector<t_field*>& members, const string& structname) +{ + // begin new scope + member_mapping_scopes.emplace_back(); + member_mapping_scope& active = member_mapping_scopes.back(); + active.scope_member = scope; + + // current C# generator policy: + // - prop names are always rendered with an Uppercase first letter + // - struct names are used as given + std::set<string> used_member_names; + vector<t_field*>::const_iterator iter; + + // prevent name conflicts with struct (CS0542 error) + used_member_names.insert(structname); + + // prevent name conflicts with known methods (THRIFT-2942) + used_member_names.insert("Read"); + used_member_names.insert("Write"); + + for (iter = members.begin(); iter != members.end(); ++iter) + { + string oldname = (*iter)->get_name(); + string newname = prop_name(*iter, true); + while (true) + { + // new name conflicts with another member + if (used_member_names.find(newname) != used_member_names.end()) + { + pverbose("struct %s: member %s conflicts with another member\n", structname.c_str(), newname.c_str()); + newname += '_'; + continue; + } + + // add always, this helps us to detect edge cases like + // different spellings ("foo" and "Foo") within the same struct + pverbose("struct %s: member mapping %s => %s\n", structname.c_str(), oldname.c_str(), newname.c_str()); + active.mapping_table[oldname] = newname; + used_member_names.insert(newname); + break; + } + } +} + +string t_netcore_generator::prop_name(t_field* tfield, bool suppress_mapping) +{ + string name(tfield->get_name()); + if (suppress_mapping) + { + name[0] = toupper(name[0]); + } + else + { + name = get_mapped_member_name(name); + } + return name; +} + +string t_netcore_generator::type_name(t_type* ttype, bool in_container, bool in_init, bool in_param, bool is_required) +{ + (void)in_init; + + while (ttype->is_typedef()) + { + ttype = static_cast<t_typedef*>(ttype)->get_type(); + } + + if (ttype->is_base_type()) + { + return base_type_name(static_cast<t_base_type*>(ttype), in_container, in_param, is_required); + } + + if (ttype->is_map()) + { + t_map* tmap = static_cast<t_map*>(ttype); + return "Dictionary<" + type_name(tmap->get_key_type(), true) + ", " + type_name(tmap->get_val_type(), true) + ">"; + } + + if (ttype->is_set()) + { + t_set* tset = static_cast<t_set*>(ttype); + return "THashSet<" + type_name(tset->get_elem_type(), true) + ">"; + } + + if (ttype->is_list()) + { + t_list* tlist = static_cast<t_list*>(ttype); + return "List<" + type_name(tlist->get_elem_type(), true) + ">"; + } + + t_program* program = ttype->get_program(); + string postfix = (!is_required && nullable_ && in_param && ttype->is_enum()) ? "?" : ""; + if (program != NULL && program != program_) + { + string ns = program->get_namespace("netcore"); + if (!ns.empty()) + { + return ns + "." + normalize_name(ttype->get_name()) + postfix; + } + } + + return normalize_name(ttype->get_name()) + postfix; +} + +string t_netcore_generator::base_type_name(t_base_type* tbase, bool in_container, bool in_param, bool is_required) +{ + (void)in_container; + string postfix = (!is_required && nullable_ && in_param) ? "?" : ""; + switch (tbase->get_base()) + { + case t_base_type::TYPE_VOID: + return "void"; + case t_base_type::TYPE_STRING: + { + if (tbase->is_binary()) + { + return "byte[]"; + } + return "string"; + } + case t_base_type::TYPE_BOOL: + return "bool" + postfix; + case t_base_type::TYPE_I8: + return "sbyte" + postfix; + case t_base_type::TYPE_I16: + return "short" + postfix; + case t_base_type::TYPE_I32: + return "int" + postfix; + case t_base_type::TYPE_I64: + return "long" + postfix; + case t_base_type::TYPE_DOUBLE: + return "double" + postfix; + default: + throw "compiler error: no C# name for base type " + t_base_type::t_base_name(tbase->get_base()); + } +} + +string t_netcore_generator::declare_field(t_field* tfield, bool init, string prefix) +{ + string result = type_name(tfield->get_type()) + " " + prefix + tfield->get_name(); + if (init) + { + t_type* ttype = tfield->get_type(); + while (ttype->is_typedef()) + { + ttype = static_cast<t_typedef*>(ttype)->get_type(); + } + if (ttype->is_base_type() && field_has_default(tfield)) + { + std::ofstream dummy; + result += " = " + render_const_value(dummy, tfield->get_name(), ttype, tfield->get_value()); + } + else if (ttype->is_base_type()) + { + t_base_type::t_base tbase = static_cast<t_base_type*>(ttype)->get_base(); + switch (tbase) + { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + result += " = null"; + break; + case t_base_type::TYPE_BOOL: + result += " = false"; + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + result += " = 0"; + break; + case t_base_type::TYPE_DOUBLE: + result += " = (double)0"; + break; + } + } + else if (ttype->is_enum()) + { + result += " = (" + type_name(ttype, false, true) + ")0"; + } + else if (ttype->is_container()) + { + result += " = new " + type_name(ttype, false, true) + "()"; + } + else + { + result += " = new " + type_name(ttype, false, true) + "()"; + } + } + return result + ";"; +} + +string t_netcore_generator::function_signature(t_function* tfunction, string prefix) +{ + t_type* ttype = tfunction->get_returntype(); + return type_name(ttype) + " " + normalize_name(prefix + tfunction->get_name()) + "(" + argument_list(tfunction->get_arglist()) + ")"; +} + +string t_netcore_generator::function_signature_async(t_function* tfunction, string prefix) +{ + t_type* ttype = tfunction->get_returntype(); + string task = "Task"; + if (!ttype->is_void()) + { + task += "<" + type_name(ttype) + ">"; + } + + string result = task + " " + normalize_name(prefix + tfunction->get_name()) + "Async("; + string args = argument_list(tfunction->get_arglist()); + result += args; + if (!args.empty()) + { + result += ", "; + } + result += "CancellationToken cancellationToken)"; + + return result; +} + +string t_netcore_generator::argument_list(t_struct* tstruct) +{ + string result = ""; + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + if (first) + { + first = false; + } + else + { + result += ", "; + } + result += type_name((*f_iter)->get_type()) + " " + normalize_name((*f_iter)->get_name()); + } + return result; +} + +string t_netcore_generator::type_to_enum(t_type* type) +{ + while (type->is_typedef()) + { + type = static_cast<t_typedef*>(type)->get_type(); + } + + if (type->is_base_type()) + { + t_base_type::t_base tbase = static_cast<t_base_type*>(type)->get_base(); + switch (tbase) + { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + return "TType.String"; + case t_base_type::TYPE_BOOL: + return "TType.Bool"; + case t_base_type::TYPE_I8: + return "TType.Byte"; + case t_base_type::TYPE_I16: + return "TType.I16"; + case t_base_type::TYPE_I32: + return "TType.I32"; + case t_base_type::TYPE_I64: + return "TType.I64"; + case t_base_type::TYPE_DOUBLE: + return "TType.Double"; + } + } + else if (type->is_enum()) + { + return "TType.I32"; + } + else if (type->is_struct() || type->is_xception()) + { + return "TType.Struct"; + } + else if (type->is_map()) + { + return "TType.Map"; + } + else if (type->is_set()) + { + return "TType.Set"; + } + else if (type->is_list()) + { + return "TType.List"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +void t_netcore_generator::generate_netcore_docstring_comment(ostream& out, string contents) +{ + docstring_comment(out, "/// <summary>" + endl, "/// ", contents, "/// </summary>" + endl); +} + +void t_netcore_generator::generate_netcore_doc(ostream& out, t_field* field) +{ + if (field->get_type()->is_enum()) + { + string combined_message = field->get_doc() + endl + "<seealso cref=\"" + get_enum_class_name(field->get_type()) + "\"/>"; + generate_netcore_docstring_comment(out, combined_message); + } + else + { + generate_netcore_doc(out, static_cast<t_doc*>(field)); + } +} + +void t_netcore_generator::generate_netcore_doc(ostream& out, t_doc* tdoc) +{ + if (tdoc->has_doc()) + { + generate_netcore_docstring_comment(out, tdoc->get_doc()); + } +} + +void t_netcore_generator::generate_netcore_doc(ostream& out, t_function* tfunction) +{ + if (tfunction->has_doc()) + { + stringstream ps; + const vector<t_field*>& fields = tfunction->get_arglist()->get_members(); + vector<t_field*>::const_iterator p_iter; + for (p_iter = fields.begin(); p_iter != fields.end(); ++p_iter) + { + t_field* p = *p_iter; + ps << endl << "<param name=\"" << p->get_name() << "\">"; + if (p->has_doc()) + { + string str = p->get_doc(); + str.erase(remove(str.begin(), str.end(), '\n'), str.end()); + ps << str; + } + ps << "</param>"; + } + + docstring_comment(out, + "", + "/// ", + "<summary>" + endl + tfunction->get_doc() + "</summary>" + ps.str(), + ""); + } +} + +void t_netcore_generator::docstring_comment(ostream& out, const string& comment_start, const string& line_prefix, const string& contents, const string& comment_end) +{ + if (comment_start != "") + { + out << indent() << comment_start; + } + + stringstream docs(contents, std::ios_base::in); + + while (!(docs.eof() || docs.fail())) + { + char line[1024]; + docs.getline(line, 1024); + + // Just prnt a newline when the line & prefix are empty. + if (strlen(line) == 0 && line_prefix == "" && !docs.eof()) + { + out << endl; + } + else if (strlen(line) > 0 || !docs.eof()) + { // skip the empty last line + out << indent() << line_prefix << line << endl; + } + } + if (comment_end != "") + { + out << indent() << comment_end; + } +} + +string t_netcore_generator::get_enum_class_name(t_type* type) +{ + string package = ""; + t_program* program = type->get_program(); + if (program != NULL && program != program_) + { + package = program->get_namespace("netcore") + "."; + } + return package + type->get_name(); +} + +THRIFT_REGISTER_GENERATOR( + netcore, + "C#", + " wcf: Adds bindings for WCF to generated classes.\n" + " serial: Add serialization support to generated classes.\n" + " nullable: Use nullable types for properties.\n" + " hashcode: Generate a hashcode and equals implementation for classes.\n" + " union: Use new union typing, which includes a static read function for union types.\n" +) diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_netcore_generator.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_netcore_generator.h new file mode 100644 index 000000000..e98980a9e --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_netcore_generator.h @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +#include <cassert> + +#include <string> +#include <fstream> +#include <iostream> +#include <vector> +#include <cctype> + +#include <stdlib.h> +#include <sys/stat.h> +#include <sstream> + +#include "thrift/platform.h" +#include "thrift/generate/t_oop_generator.h" + +using std::map; +using std::ostream; +using std::ostringstream; +using std::string; +using std::stringstream; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes + +class t_netcore_generator : public t_oop_generator +{ + + struct member_mapping_scope + { + public: + member_mapping_scope() : scope_member(0) { } + void* scope_member; + map<string, string> mapping_table; + }; + +public: + t_netcore_generator(t_program* program, const map<string, string>& parsed_options, const string& option_string); + + bool is_wcf_enabled() const; + bool is_nullable_enabled() const; + bool is_hashcode_enabled() const; + bool is_serialize_enabled() const; + bool is_union_enabled() const; + map<string, int> get_keywords_list() const; + + // overrides + void init_generator(); + void close_generator(); + void generate_consts(vector<t_const*> consts); + void generate_consts(ostream& out, vector<t_const*> consts); + void generate_typedef(t_typedef* ttypedef); + void generate_enum(t_enum* tenum); + void generate_enum(ostream& out, t_enum* tenum); + void generate_struct(t_struct* tstruct); + void generate_xception(t_struct* txception); + void generate_service(t_service* tservice); + + void generate_property(ostream& out, t_field* tfield, bool isPublic, bool generateIsset); + void generate_netcore_property(ostream& out, t_field* tfield, bool isPublic, bool includeIsset = true, string fieldPrefix = ""); + bool print_const_value(ostream& out, string name, t_type* type, t_const_value* value, bool in_static, bool defval = false, bool needtype = false); + string render_const_value(ostream& out, string name, t_type* type, t_const_value* value); + void print_const_constructor(ostream& out, vector<t_const*> consts); + void print_const_def_value(ostream& out, string name, t_type* type, t_const_value* value); + void generate_netcore_struct(t_struct* tstruct, bool is_exception); + void generate_netcore_union(t_struct* tunion); + void generate_netcore_struct_definition(ostream& out, t_struct* tstruct, bool is_xception = false, bool in_class = false, bool is_result = false); + void generate_netcore_union_definition(ostream& out, t_struct* tunion); + void generate_netcore_union_class(ostream& out, t_struct* tunion, t_field* tfield); + void generate_netcore_wcffault(ostream& out, t_struct* tstruct); + void generate_netcore_struct_reader(ostream& out, t_struct* tstruct); + void generate_netcore_struct_result_writer(ostream& out, t_struct* tstruct); + void generate_netcore_struct_writer(ostream& out, t_struct* tstruct); + void generate_netcore_struct_tostring(ostream& out, t_struct* tstruct); + void generate_netcore_struct_equals(ostream& out, t_struct* tstruct); + void generate_netcore_struct_hashcode(ostream& out, t_struct* tstruct); + void generate_netcore_union_reader(ostream& out, t_struct* tunion); + void generate_function_helpers(ostream& out, t_function* tfunction); + void generate_service_interface(ostream& out, t_service* tservice); + void generate_service_helpers(ostream& out, t_service* tservice); + void generate_service_client(ostream& out, t_service* tservice); + void generate_service_server(ostream& out, t_service* tservice); + void generate_process_function_async(ostream& out, t_service* tservice, t_function* function); + void generate_deserialize_field(ostream& out, t_field* tfield, string prefix = "", bool is_propertyless = false); + void generate_deserialize_struct(ostream& out, t_struct* tstruct, string prefix = ""); + void generate_deserialize_container(ostream& out, t_type* ttype, string prefix = ""); + void generate_deserialize_set_element(ostream& out, t_set* tset, string prefix = ""); + void generate_deserialize_map_element(ostream& out, t_map* tmap, string prefix = ""); + void generate_deserialize_list_element(ostream& out, t_list* list, string prefix = ""); + void generate_serialize_field(ostream& out, t_field* tfield, string prefix = "", bool is_element = false, bool is_propertyless = false); + void generate_serialize_struct(ostream& out, t_struct* tstruct, string prefix = ""); + void generate_serialize_container(ostream& out, t_type* ttype, string prefix = ""); + void generate_serialize_map_element(ostream& out, t_map* tmap, string iter, string map); + void generate_serialize_set_element(ostream& out, t_set* tmap, string iter); + void generate_serialize_list_element(ostream& out, t_list* tlist, string iter); + void generate_netcore_doc(ostream& out, t_field* field); + void generate_netcore_doc(ostream& out, t_doc* tdoc); + void generate_netcore_doc(ostream& out, t_function* tdoc); + void generate_netcore_docstring_comment(ostream& out, string contents); + void docstring_comment(ostream& out, const string& comment_start, const string& line_prefix, const string& contents, const string& comment_end); + void start_netcore_namespace(ostream& out); + void end_netcore_namespace(ostream& out); + + string netcore_type_usings() const; + string netcore_thrift_usings() const; + + string type_name(t_type* ttype, bool in_countainer = false, bool in_init = false, bool in_param = false, bool is_required = false); + string base_type_name(t_base_type* tbase, bool in_container = false, bool in_param = false, bool is_required = false); + string declare_field(t_field* tfield, bool init = false, string prefix = ""); + string function_signature_async(t_function* tfunction, string prefix = ""); + string function_signature(t_function* tfunction, string prefix = ""); + string argument_list(t_struct* tstruct); + string type_to_enum(t_type* ttype); + string prop_name(t_field* tfield, bool suppress_mapping = false); + string get_enum_class_name(t_type* type); + +private: + string namespace_name_; + string namespace_dir_; + + bool nullable_; + bool union_; + bool hashcode_; + bool serialize_; + bool wcf_; + + string wcf_namespace_; + map<string, int> netcore_keywords; + vector<member_mapping_scope> member_mapping_scopes; + + void init_keywords(); + string normalize_name(string name); + string make_valid_csharp_identifier(string const& fromName); + void prepare_member_name_mapping(t_struct* tstruct); + void prepare_member_name_mapping(void* scope, const vector<t_field*>& members, const string& structname); + void cleanup_member_name_mapping(void* scope); + string get_mapped_member_name(string oldname); +}; diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_netstd_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_netstd_generator.cc new file mode 100644 index 000000000..ffe51ab0b --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_netstd_generator.cc @@ -0,0 +1,3023 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +#include <cassert> + +#include <string> +#include <fstream> +#include <iostream> +#include <vector> +#include <cctype> + +#include <stdlib.h> +#include <sys/stat.h> +#include <sstream> + +#include "thrift/platform.h" +#include "thrift/generate/t_oop_generator.h" +#include "thrift/generate/t_netstd_generator.h" + +using std::map; +using std::ostream; +using std::ostringstream; +using std::string; +using std::stringstream; +using std::vector; + +//TODO: check for indentation +//TODO: Do we need seqId_ in generation? + +t_netstd_generator::t_netstd_generator(t_program* program, const map<string, string>& parsed_options, const string& option_string) + : t_oop_generator(program) +{ + (void)option_string; + + union_ = false; + serialize_ = false; + wcf_ = false; + wcf_namespace_.clear(); + + map<string, string>::const_iterator iter; + + for (iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) + { + if (iter->first.compare("union") == 0) + { + union_ = true; + } + else if (iter->first.compare("serial") == 0) + { + serialize_ = true; + wcf_namespace_ = iter->second; // since there can be only one namespace + } + else if (iter->first.compare("wcf") == 0) + { + wcf_ = true; + wcf_namespace_ = iter->second; + } + else + { + throw "unknown option netstd:" + iter->first; + } + } + + out_dir_base_ = "gen-netstd"; +} + +static string correct_function_name_for_async(string const& function_name) +{ + string const async_end = "Async"; + size_t i = function_name.find(async_end); + if (i != string::npos) + { + return function_name + async_end; + } + + return function_name; +} + +/** +* \brief Search and replace "_args" substring in struct name if exist (for C# class naming) +* \param struct_name +* \return Modified struct name ("Struct_args" -> "StructArgs") or original name +*/ +static string check_and_correct_struct_name(const string& struct_name) +{ + string args_end = "_args"; + size_t i = struct_name.find(args_end); + if (i != string::npos) + { + string new_struct_name = struct_name; + new_struct_name.replace(i, args_end.length(), "Args"); + return new_struct_name; + } + + string result_end = "_result"; + size_t j = struct_name.find(result_end); + if (j != string::npos) + { + string new_struct_name = struct_name; + new_struct_name.replace(j, result_end.length(), "Result"); + return new_struct_name; + } + + return struct_name; +} + +static bool field_has_default(t_field* tfield) { return tfield->get_value() != NULL; } + +static bool field_is_required(t_field* tfield) { return tfield->get_req() == t_field::T_REQUIRED; } + +static bool type_can_be_null(t_type* ttype) +{ + while (ttype->is_typedef()) + { + ttype = static_cast<t_typedef*>(ttype)->get_type(); + } + + return ttype->is_container() || ttype->is_struct() || ttype->is_xception() || ttype->is_string(); +} + +bool t_netstd_generator::is_wcf_enabled() const { return wcf_; } + +bool t_netstd_generator::is_serialize_enabled() const { return serialize_; } + +bool t_netstd_generator::is_union_enabled() const { return union_; } + +map<string, int> t_netstd_generator::get_keywords_list() const +{ + return netstd_keywords; +} + +void t_netstd_generator::init_generator() +{ + MKDIR(get_out_dir().c_str()); + + // for usage of csharp namespaces in thrift files (from files for csharp) + namespace_name_ = program_->get_namespace("netstd"); + if (namespace_name_.empty()) + { + namespace_name_ = program_->get_namespace("netstd"); + } + + string dir = namespace_name_; + string subdir = get_out_dir().c_str(); + string::size_type loc; + + while ((loc = dir.find(".")) != string::npos) + { + subdir = subdir + "/" + dir.substr(0, loc); + MKDIR(subdir.c_str()); + dir = dir.substr(loc + 1); + } + if (dir.size() > 0) + { + subdir = subdir + "/" + dir; + MKDIR(subdir.c_str()); + } + + namespace_dir_ = subdir; + init_keywords(); + + while (!member_mapping_scopes.empty()) + { + cleanup_member_name_mapping(member_mapping_scopes.back().scope_member); + } + + pverbose(".NET Standard options:\n"); + pverbose("- union ...... %s\n", (is_union_enabled() ? "ON" : "off")); + pverbose("- serialize .. %s\n", (is_serialize_enabled() ? "ON" : "off")); + pverbose("- wcf ........ %s\n", (is_wcf_enabled() ? "ON" : "off")); +} + +string t_netstd_generator::normalize_name(string name) +{ + string tmp(name); + transform(tmp.begin(), tmp.end(), tmp.begin(), static_cast<int(*)(int)>(tolower)); + + // un-conflict keywords by prefixing with "@" + if (netstd_keywords.find(tmp) != netstd_keywords.end()) + { + return "@" + name; + } + + // no changes necessary + return name; +} + +void t_netstd_generator::init_keywords() +{ + netstd_keywords.clear(); + + // C# keywords + netstd_keywords["abstract"] = 1; + netstd_keywords["as"] = 1; + netstd_keywords["base"] = 1; + netstd_keywords["bool"] = 1; + netstd_keywords["break"] = 1; + netstd_keywords["byte"] = 1; + netstd_keywords["case"] = 1; + netstd_keywords["catch"] = 1; + netstd_keywords["char"] = 1; + netstd_keywords["checked"] = 1; + netstd_keywords["class"] = 1; + netstd_keywords["const"] = 1; + netstd_keywords["continue"] = 1; + netstd_keywords["decimal"] = 1; + netstd_keywords["default"] = 1; + netstd_keywords["delegate"] = 1; + netstd_keywords["do"] = 1; + netstd_keywords["double"] = 1; + netstd_keywords["else"] = 1; + netstd_keywords["enum"] = 1; + netstd_keywords["event"] = 1; + netstd_keywords["explicit"] = 1; + netstd_keywords["extern"] = 1; + netstd_keywords["false"] = 1; + netstd_keywords["finally"] = 1; + netstd_keywords["fixed"] = 1; + netstd_keywords["float"] = 1; + netstd_keywords["for"] = 1; + netstd_keywords["foreach"] = 1; + netstd_keywords["goto"] = 1; + netstd_keywords["if"] = 1; + netstd_keywords["implicit"] = 1; + netstd_keywords["in"] = 1; + netstd_keywords["int"] = 1; + netstd_keywords["interface"] = 1; + netstd_keywords["internal"] = 1; + netstd_keywords["is"] = 1; + netstd_keywords["lock"] = 1; + netstd_keywords["long"] = 1; + netstd_keywords["namespace"] = 1; + netstd_keywords["new"] = 1; + netstd_keywords["null"] = 1; + netstd_keywords["object"] = 1; + netstd_keywords["operator"] = 1; + netstd_keywords["out"] = 1; + netstd_keywords["override"] = 1; + netstd_keywords["params"] = 1; + netstd_keywords["private"] = 1; + netstd_keywords["protected"] = 1; + netstd_keywords["public"] = 1; + netstd_keywords["readonly"] = 1; + netstd_keywords["ref"] = 1; + netstd_keywords["return"] = 1; + netstd_keywords["sbyte"] = 1; + netstd_keywords["sealed"] = 1; + netstd_keywords["short"] = 1; + netstd_keywords["sizeof"] = 1; + netstd_keywords["stackalloc"] = 1; + netstd_keywords["static"] = 1; + netstd_keywords["string"] = 1; + netstd_keywords["struct"] = 1; + netstd_keywords["switch"] = 1; + netstd_keywords["this"] = 1; + netstd_keywords["throw"] = 1; + netstd_keywords["true"] = 1; + netstd_keywords["try"] = 1; + netstd_keywords["typeof"] = 1; + netstd_keywords["uint"] = 1; + netstd_keywords["ulong"] = 1; + netstd_keywords["unchecked"] = 1; + netstd_keywords["unsafe"] = 1; + netstd_keywords["ushort"] = 1; + netstd_keywords["using"] = 1; + netstd_keywords["virtual"] = 1; + netstd_keywords["void"] = 1; + netstd_keywords["volatile"] = 1; + netstd_keywords["while"] = 1; + + // C# contextual keywords + netstd_keywords["add"] = 1; + netstd_keywords["alias"] = 1; + netstd_keywords["ascending"] = 1; + netstd_keywords["async"] = 1; + netstd_keywords["await"] = 1; + netstd_keywords["descending"] = 1; + netstd_keywords["dynamic"] = 1; + netstd_keywords["from"] = 1; + netstd_keywords["get"] = 1; + netstd_keywords["global"] = 1; + netstd_keywords["group"] = 1; + netstd_keywords["into"] = 1; + netstd_keywords["join"] = 1; + netstd_keywords["let"] = 1; + netstd_keywords["orderby"] = 1; + netstd_keywords["partial"] = 1; + netstd_keywords["remove"] = 1; + netstd_keywords["select"] = 1; + netstd_keywords["set"] = 1; + netstd_keywords["value"] = 1; + netstd_keywords["var"] = 1; + netstd_keywords["where"] = 1; + netstd_keywords["yield"] = 1; + + netstd_keywords["when"] = 1; +} + +void t_netstd_generator::start_netstd_namespace(ostream& out) +{ + if (!namespace_name_.empty()) + { + out << "namespace " << namespace_name_ << endl; + scope_up(out); + } +} + +void t_netstd_generator::end_netstd_namespace(ostream& out) +{ + if (!namespace_name_.empty()) + { + scope_down(out); + } +} + +string t_netstd_generator::netstd_type_usings() const +{ + string namespaces = + "using System;\n" + "using System.Collections;\n" + "using System.Collections.Generic;\n" + "using System.Text;\n" + "using System.IO;\n" + "using System.Threading;\n" + "using System.Threading.Tasks;\n" + "using Thrift;\n" + "using Thrift.Collections;\n"; + + if (is_wcf_enabled()) + { + namespaces += "using System.ServiceModel;\n"; + namespaces += "using System.Runtime.Serialization;\n"; + } + + return namespaces + endl; +} + +string t_netstd_generator::netstd_thrift_usings() const +{ + string namespaces = + "using Thrift.Protocol;\n" + "using Thrift.Protocol.Entities;\n" + "using Thrift.Protocol.Utilities;\n" + "using Thrift.Transport;\n" + "using Thrift.Transport.Client;\n" + "using Thrift.Transport.Server;\n" + "using Thrift.Processor;\n"; + + return namespaces + endl; +} + +void t_netstd_generator::close_generator() +{ +} + +void t_netstd_generator::generate_typedef(t_typedef* ttypedef) +{ + (void)ttypedef; +} + +void t_netstd_generator::generate_enum(t_enum* tenum) +{ + int ic = indent_count(); + string f_enum_name = namespace_dir_ + "/" + tenum->get_name() + ".cs"; + + ofstream_with_content_based_conditional_update f_enum; + f_enum.open(f_enum_name.c_str()); + + generate_enum(f_enum, tenum); + + f_enum.close(); + indent_validate(ic, "generate_enum"); +} + +void t_netstd_generator::generate_enum(ostream& out, t_enum* tenum) +{ + out << autogen_comment() << endl; + + start_netstd_namespace(out); + generate_netstd_doc(out, tenum); + + out << indent() << "public enum " << tenum->get_name() << endl; + scope_up(out); + + vector<t_enum_value*> constants = tenum->get_constants(); + vector<t_enum_value*>::iterator c_iter; + + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) + { + generate_netstd_doc(out, *c_iter); + int value = (*c_iter)->get_value(); + out << indent() << (*c_iter)->get_name() << " = " << value << "," << endl; + } + + scope_down(out); + end_netstd_namespace(out); +} + +void t_netstd_generator::generate_consts(vector<t_const*> consts) +{ + if (consts.empty()) + { + return; + } + + string f_consts_name = namespace_dir_ + '/' + program_name_ + ".Constants.cs"; + ofstream_with_content_based_conditional_update f_consts; + f_consts.open(f_consts_name.c_str()); + + generate_consts(f_consts, consts); + + f_consts.close(); +} + +void t_netstd_generator::generate_consts(ostream& out, vector<t_const*> consts) +{ + if (consts.empty()) + { + return; + } + + out << autogen_comment() << netstd_type_usings() << endl; + + start_netstd_namespace(out); + + out << indent() << "public static class " << make_valid_csharp_identifier(program_name_) << "Constants" << endl; + + scope_up(out); + + vector<t_const*>::iterator c_iter; + bool need_static_constructor = false; + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) + { + generate_netstd_doc(out, *c_iter); + if (print_const_value(out, (*c_iter)->get_name(), (*c_iter)->get_type(), (*c_iter)->get_value(), false)) + { + need_static_constructor = true; + } + } + + if (need_static_constructor) + { + print_const_constructor(out, consts); + } + + scope_down(out); + end_netstd_namespace(out); +} + +void t_netstd_generator::print_const_def_value(ostream& out, string name, t_type* type, t_const_value* value) +{ + if (type->is_struct() || type->is_xception()) + { + const vector<t_field*>& fields = static_cast<t_struct*>(type)->get_members(); + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + vector<t_field*>::const_iterator f_iter; + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + prepare_member_name_mapping(static_cast<t_struct*>(type)); + + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) + { + t_field* field = NULL; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + if ((*f_iter)->get_name() == v_iter->first->get_string()) + { + field = *f_iter; + } + } + + if (field == NULL) + { + throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string(); + } + + t_type* field_type = field->get_type(); + + string val = render_const_value(out, name, field_type, v_iter->second); + out << indent() << name << "." << prop_name(field) << " = " << val << ";" << endl; + } + + cleanup_member_name_mapping(static_cast<t_struct*>(type)); + } + else if (type->is_map()) + { + t_type* ktype = static_cast<t_map*>(type)->get_key_type(); + t_type* vtype = static_cast<t_map*>(type)->get_val_type(); + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) + { + string key = render_const_value(out, name, ktype, v_iter->first); + string val = render_const_value(out, name, vtype, v_iter->second); + out << indent() << name << "[" << key << "]" << " = " << val << ";" << endl; + } + } + else if (type->is_list() || type->is_set()) + { + t_type* etype; + if (type->is_list()) + { + etype = static_cast<t_list*>(type)->get_elem_type(); + } + else + { + etype = static_cast<t_set*>(type)->get_elem_type(); + } + + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) + { + string val = render_const_value(out, name, etype, *v_iter); + out << indent() << name << ".Add(" << val << ");" << endl; + } + } +} + +void t_netstd_generator::print_const_constructor(ostream& out, vector<t_const*> consts) +{ + out << indent() << "static " << make_valid_csharp_identifier(program_name_).c_str() << "Constants()" << endl; + scope_up(out); + + vector<t_const*>::iterator c_iter; + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) + { + string name = (*c_iter)->get_name(); + t_type* type = (*c_iter)->get_type(); + t_const_value* value = (*c_iter)->get_value(); + + print_const_def_value(out, name, type, value); + } + scope_down(out); +} + +bool t_netstd_generator::print_const_value(ostream& out, string name, t_type* type, t_const_value* value, bool in_static, bool defval, bool needtype) +{ + out << indent(); + bool need_static_construction = !in_static; + while (type->is_typedef()) + { + type = static_cast<t_typedef*>(type)->get_type(); + } + + if (!defval || needtype) + { + out << (in_static ? "" : type->is_base_type() ? "public const " : "public static ") << type_name(type) << " "; + } + + if (type->is_base_type()) + { + string v2 = render_const_value(out, name, type, value); + out << normalize_name(name) << " = " << v2 << ";" << endl; + need_static_construction = false; + } + else if (type->is_enum()) + { + out << name << " = " << type_name(type) << "." << value->get_identifier_name() << ";" << endl; + need_static_construction = false; + } + else if (type->is_struct() || type->is_xception()) + { + out << name << " = new " << type_name(type) << "();" << endl; + } + else if (type->is_map()) + { + out << name << " = new " << type_name(type) << "();" << endl; + } + else if (type->is_list() || type->is_set()) + { + out << name << " = new " << type_name(type) << "();" << endl; + } + + if (defval && !type->is_base_type() && !type->is_enum()) + { + print_const_def_value(out, name, type, value); + } + + return need_static_construction; +} + +string t_netstd_generator::render_const_value(ostream& out, string name, t_type* type, t_const_value* value) +{ + (void)name; + ostringstream render; + + if (type->is_base_type()) + { + t_base_type::t_base tbase = static_cast<t_base_type*>(type)->get_base(); + switch (tbase) + { + case t_base_type::TYPE_STRING: + render << '"' << get_escaped_string(value) << '"'; + break; + case t_base_type::TYPE_BOOL: + render << ((value->get_integer() > 0) ? "true" : "false"); + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + render << value->get_integer(); + break; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) + { + render << value->get_integer(); + } + else + { + render << value->get_double(); + } + break; + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + } + else if (type->is_enum()) + { + render << type->get_name() << "." << value->get_identifier_name(); + } + else + { + string t = tmp("tmp"); + print_const_value(out, t, type, value, true, true, true); + render << t; + } + + return render.str(); +} + +void t_netstd_generator::generate_struct(t_struct* tstruct) +{ + if (is_union_enabled() && tstruct->is_union()) + { + generate_netstd_union(tstruct); + } + else + { + generate_netstd_struct(tstruct, false); + } +} + +void t_netstd_generator::generate_xception(t_struct* txception) +{ + generate_netstd_struct(txception, true); +} + +void t_netstd_generator::generate_netstd_struct(t_struct* tstruct, bool is_exception) +{ + int ic = indent_count(); + + string f_struct_name = namespace_dir_ + "/" + (tstruct->get_name()) + ".cs"; + ofstream_with_content_based_conditional_update f_struct; + + f_struct.open(f_struct_name.c_str()); + + f_struct << autogen_comment() << netstd_type_usings() << netstd_thrift_usings() << endl; + + generate_netstd_struct_definition(f_struct, tstruct, is_exception); + + f_struct.close(); + + indent_validate(ic, "generate_netstd_struct"); +} + +void t_netstd_generator::generate_netstd_struct_definition(ostream& out, t_struct* tstruct, bool is_exception, bool in_class, bool is_result) +{ + if (!in_class) + { + start_netstd_namespace(out); + } + + out << endl; + + generate_netstd_doc(out, tstruct); + prepare_member_name_mapping(tstruct); + + if ((is_serialize_enabled() || is_wcf_enabled()) && !is_exception) + { + out << indent() << "[DataContract(Namespace=\"" << wcf_namespace_ << "\")]" << endl; + } + + bool is_final = tstruct->annotations_.find("final") != tstruct->annotations_.end(); + + string sharp_struct_name = check_and_correct_struct_name(normalize_name(tstruct->get_name())); + + out << indent() << "public " << (is_final ? "sealed " : "") << "partial class " << sharp_struct_name << " : "; + + if (is_exception) + { + out << "TException, "; + } + + out << "TBase" << endl + << indent() << "{" << endl; + indent_up(); + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + // make private members with public Properties + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) + { + // if the field is required, then we use auto-properties + if (!field_is_required((*m_iter))) + { + out << indent() << "private " << declare_field(*m_iter, false, "_") << endl; + } + } + out << endl; + + bool has_non_required_fields = false; + bool has_required_fields = false; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) + { + generate_netstd_doc(out, *m_iter); + generate_property(out, *m_iter, true, true); + bool is_required = field_is_required((*m_iter)); + if (is_required) + { + has_required_fields = true; + } + else + { + has_non_required_fields = true; + } + } + + bool generate_isset = has_non_required_fields; + if (generate_isset) + { + out << endl; + if (is_serialize_enabled() || is_wcf_enabled()) + { + out << indent() << "[DataMember(Order = 1)]" << endl; + } + out << indent() << "public Isset __isset;" << endl; + if (is_serialize_enabled() || is_wcf_enabled()) + { + out << indent() << "[DataContract]" << endl; + } + + out << indent() << "public struct Isset" << endl + << indent() << "{" << endl; + indent_up(); + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) + { + bool is_required = field_is_required((*m_iter)); + // if it is required, don't need Isset for that variable + // if it is not required, if it has a default value, we need to generate Isset + if (!is_required) + { + if (is_serialize_enabled() || is_wcf_enabled()) + { + out << indent() << "[DataMember]" << endl; + } + out << indent() << "public bool " << normalize_name((*m_iter)->get_name()) << ";" << endl; + } + } + + indent_down(); + out << indent() << "}" << endl << endl; + + if (generate_isset && (is_serialize_enabled() || is_wcf_enabled())) + { + out << indent() << "#region XmlSerializer support" << endl << endl; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) + { + bool is_required = field_is_required(*m_iter); + // if it is required, don't need Isset for that variable + // if it is not required, if it has a default value, we need to generate Isset + if (!is_required) + { + out << indent() << "public bool ShouldSerialize" << prop_name(*m_iter) << "()" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "return __isset." << normalize_name((*m_iter)->get_name()) << ";" << endl; + indent_down(); + out << indent() << "}" << endl << endl; + } + } + + out << indent() << "#endregion XmlSerializer support" << endl << endl; + } + } + + // We always want a default, no argument constructor for Reading + out << indent() << "public " << sharp_struct_name << "()" << endl + << indent() << "{" << endl; + indent_up(); + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) + { + t_type* t = (*m_iter)->get_type(); + while (t->is_typedef()) + { + t = static_cast<t_typedef*>(t)->get_type(); + } + if ((*m_iter)->get_value() != NULL) + { + if (field_is_required((*m_iter))) + { + print_const_value(out, "this." + prop_name(*m_iter), t, (*m_iter)->get_value(), true, true); + } + else + { + print_const_value(out, "this._" + (*m_iter)->get_name(), t, (*m_iter)->get_value(), true, true); + // Optionals with defaults are marked set + out << indent() << "this.__isset." << normalize_name((*m_iter)->get_name()) << " = true;" << endl; + } + } + } + indent_down(); + out << indent() << "}" << endl << endl; + + if (has_required_fields) + { + out << indent() << "public " << sharp_struct_name << "("; + bool first = true; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) + { + if (field_is_required(*m_iter)) + { + if (first) + { + first = false; + } + else + { + out << ", "; + } + out << type_name((*m_iter)->get_type()) << " " << (*m_iter)->get_name(); + } + } + out << ") : this()" << endl + << indent() << "{" << endl; + indent_up(); + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) + { + if (field_is_required(*m_iter)) + { + out << indent() << "this." << prop_name(*m_iter) << " = " << (*m_iter)->get_name() << ";" << endl; + } + } + + indent_down(); + out << indent() << "}" << endl << endl; + } + + generate_netstd_struct_reader(out, tstruct); + if (is_result) + { + generate_netstd_struct_result_writer(out, tstruct); + } + else + { + generate_netstd_struct_writer(out, tstruct); + } + generate_netstd_struct_equals(out, tstruct); + generate_netstd_struct_hashcode(out, tstruct); + generate_netstd_struct_tostring(out, tstruct); + + indent_down(); + out << indent() << "}" << endl << endl; + + // generate a corresponding WCF fault to wrap the exception + if ((is_serialize_enabled() || is_wcf_enabled()) && is_exception) + { + generate_netstd_wcffault(out, tstruct); + } + + cleanup_member_name_mapping(tstruct); + if (!in_class) + { + end_netstd_namespace(out); + } +} + +void t_netstd_generator::generate_netstd_wcffault(ostream& out, t_struct* tstruct) +{ + out << endl; + out << indent() << "[DataContract]" << endl; + + bool is_final = tstruct->annotations_.find("final") != tstruct->annotations_.end(); + + out << indent() << "public " << (is_final ? "sealed " : "") << "partial class " << tstruct->get_name() << "Fault" << endl + << indent() << "{" << endl; + indent_up(); + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + // make private members with public Properties + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) + { + // if the field is required, then we use auto-properties + if (!field_is_required((*m_iter))) + { + out << indent() << "private " << declare_field(*m_iter, false, "_") << endl; + } + } + out << endl; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) + { + generate_property(out, *m_iter, true, false); + } + + indent_down(); + out << indent() << "}" << endl << endl; +} + +void t_netstd_generator::generate_netstd_struct_reader(ostream& out, t_struct* tstruct) +{ + out << indent() << "public async Task ReadAsync(TProtocol iprot, CancellationToken cancellationToken)" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "iprot.IncrementRecursionDepth();" << endl + << indent() << "try" << endl + << indent() << "{" << endl; + indent_up(); + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + // Required variables aren't in __isset, so we need tmp vars to check them + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + if (field_is_required(*f_iter)) + { + out << indent() << "bool isset_" << (*f_iter)->get_name() << " = false;" << endl; + } + } + + out << indent() << "TField field;" << endl + << indent() << "await iprot.ReadStructBeginAsync(cancellationToken);" << endl + << indent() << "while (true)" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "field = await iprot.ReadFieldBeginAsync(cancellationToken);" << endl + << indent() << "if (field.Type == TType.Stop)" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "break;" << endl; + indent_down(); + out << indent() << "}" << endl << endl + << indent() << "switch (field.ID)" << endl + << indent() << "{" << endl; + indent_up(); + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + bool is_required = field_is_required(*f_iter); + out << indent() << "case " << (*f_iter)->get_key() << ":" << endl; + indent_up(); + out << indent() << "if (field.Type == " << type_to_enum((*f_iter)->get_type()) << ")" << endl + << indent() << "{" << endl; + indent_up(); + + generate_deserialize_field(out, *f_iter); + if (is_required) + { + out << indent() << "isset_" << (*f_iter)->get_name() << " = true;" << endl; + } + + indent_down(); + out << indent() << "}" << endl + << indent() << "else" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "await TProtocolUtil.SkipAsync(iprot, field.Type, cancellationToken);" << endl; + indent_down(); + out << indent() << "}" << endl + << indent() << "break;" << endl; + indent_down(); + } + + out << indent() << "default: " << endl; + indent_up(); + out << indent() << "await TProtocolUtil.SkipAsync(iprot, field.Type, cancellationToken);" << endl + << indent() << "break;" << endl; + indent_down(); + indent_down(); + out << indent() << "}" << endl + << endl + << indent() << "await iprot.ReadFieldEndAsync(cancellationToken);" << endl; + indent_down(); + out << indent() << "}" << endl + << endl + << indent() << "await iprot.ReadStructEndAsync(cancellationToken);" << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + if (field_is_required((*f_iter))) + { + out << indent() << "if (!isset_" << (*f_iter)->get_name() << ")" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "throw new TProtocolException(TProtocolException.INVALID_DATA);" << endl; + indent_down(); + out << indent() << "}" << endl; + } + } + + indent_down(); + out << indent() << "}" << endl; + out << indent() << "finally" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "iprot.DecrementRecursionDepth();" << endl; + indent_down(); + out << indent() << "}" << endl; + indent_down(); + out << indent() << "}" << endl << endl; +} + +void t_netstd_generator::generate_netstd_struct_writer(ostream& out, t_struct* tstruct) +{ + out << indent() << "public async Task WriteAsync(TProtocol oprot, CancellationToken cancellationToken)" << endl + << indent() << "{" << endl; + indent_up(); + + out << indent() << "oprot.IncrementRecursionDepth();" << endl + << indent() << "try" << endl + << indent() << "{" << endl; + indent_up(); + + string name = tstruct->get_name(); + const vector<t_field*>& fields = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator f_iter; + + out << indent() << "var struc = new TStruct(\"" << name << "\");" << endl + << indent() << "await oprot.WriteStructBeginAsync(struc, cancellationToken);" << endl; + + if (fields.size() > 0) + { + out << indent() << "var field = new TField();" << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + bool is_required = field_is_required(*f_iter); + if (!is_required) + { + bool null_allowed = type_can_be_null((*f_iter)->get_type()); + if (null_allowed) + { + out << indent() << "if (" << prop_name(*f_iter) << " != null && __isset." << normalize_name((*f_iter)->get_name()) << ")" << endl + << indent() << "{" << endl; + indent_up(); + } + else + { + out << indent() << "if (__isset." << normalize_name((*f_iter)->get_name()) << ")" << endl + << indent() << "{" << endl; + indent_up(); + } + } + out << indent() << "field.Name = \"" << (*f_iter)->get_name() << "\";" << endl + << indent() << "field.Type = " << type_to_enum((*f_iter)->get_type()) << ";" << endl + << indent() << "field.ID = " << (*f_iter)->get_key() << ";" << endl + << indent() << "await oprot.WriteFieldBeginAsync(field, cancellationToken);" << endl; + + generate_serialize_field(out, *f_iter); + + out << indent() << "await oprot.WriteFieldEndAsync(cancellationToken);" << endl; + if (!is_required) + { + indent_down(); + out << indent() << "}" << endl; + } + } + } + + out << indent() << "await oprot.WriteFieldStopAsync(cancellationToken);" << endl + << indent() << "await oprot.WriteStructEndAsync(cancellationToken);" << endl; + indent_down(); + out << indent() << "}" << endl + << indent() << "finally" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "oprot.DecrementRecursionDepth();" << endl; + indent_down(); + out << indent() << "}" << endl; + indent_down(); + out << indent() << "}" << endl << endl; +} + +void t_netstd_generator::generate_netstd_struct_result_writer(ostream& out, t_struct* tstruct) +{ + out << indent() << "public async Task WriteAsync(TProtocol oprot, CancellationToken cancellationToken)" << endl + << indent() << "{" << endl; + indent_up(); + + out << indent() << "oprot.IncrementRecursionDepth();" << endl + << indent() << "try" << endl + << indent() << "{" << endl; + indent_up(); + + string name = tstruct->get_name(); + const vector<t_field*>& fields = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator f_iter; + + out << indent() << "var struc = new TStruct(\"" << name << "\");" << endl + << indent() << "await oprot.WriteStructBeginAsync(struc, cancellationToken);" << endl; + + if (fields.size() > 0) + { + out << indent() << "var field = new TField();" << endl; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + if (first) + { + first = false; + out << endl << indent() << "if"; + } + else + { + out << indent() << "else if"; + } + + out << "(this.__isset." << normalize_name((*f_iter)->get_name()) << ")" << endl + << indent() << "{" << endl; + indent_up(); + + bool null_allowed = type_can_be_null((*f_iter)->get_type()); + if (null_allowed) + { + out << indent() << "if (" << prop_name(*f_iter) << " != null)" << endl + << indent() << "{" << endl; + indent_up(); + } + + out << indent() << "field.Name = \"" << prop_name(*f_iter) << "\";" << endl + << indent() << "field.Type = " << type_to_enum((*f_iter)->get_type()) << ";" << endl + << indent() << "field.ID = " << (*f_iter)->get_key() << ";" << endl + << indent() << "await oprot.WriteFieldBeginAsync(field, cancellationToken);" << endl; + + generate_serialize_field(out, *f_iter); + + out << indent() << "await oprot.WriteFieldEndAsync(cancellationToken);" << endl; + + if (null_allowed) + { + indent_down(); + out << indent() << "}" << endl; + } + + indent_down(); + out << indent() << "}" << endl; + } + } + + out << indent() << "await oprot.WriteFieldStopAsync(cancellationToken);" << endl + << indent() << "await oprot.WriteStructEndAsync(cancellationToken);" << endl; + indent_down(); + out << indent() << "}" << endl + << indent() << "finally" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "oprot.DecrementRecursionDepth();" << endl; + indent_down(); + out << indent() << "}" << endl; + indent_down(); + out << indent() << "}" << endl << endl; +} + +void t_netstd_generator::generate_netstd_struct_tostring(ostream& out, t_struct* tstruct) +{ + out << indent() << "public override string ToString()" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "var sb = new StringBuilder(\"" << tstruct->get_name() << "(\");" << endl; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + bool useFirstFlag = false; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + if (!field_is_required((*f_iter))) + { + out << indent() << "bool __first = true;" << endl; + useFirstFlag = true; + } + break; + } + + bool had_required = false; // set to true after first required field has been processed + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + bool is_required = field_is_required((*f_iter)); + if (!is_required) + { + bool null_allowed = type_can_be_null((*f_iter)->get_type()); + if (null_allowed) + { + out << indent() << "if (" << prop_name((*f_iter)) << " != null && __isset." << normalize_name((*f_iter)->get_name()) << ")" << endl + << indent() << "{" << endl; + indent_up(); + } + else + { + out << indent() << "if (__isset." << normalize_name((*f_iter)->get_name()) << ")" << endl + << indent() << "{" << endl; + indent_up(); + } + } + + if (useFirstFlag && (!had_required)) + { + out << indent() << "if(!__first) { sb.Append(\", \"); }" << endl; + if (!is_required) + { + out << indent() << "__first = false;" << endl; + } + out << indent() << "sb.Append(\"" << prop_name(*f_iter) << ": \");" << endl; + } + else + { + out << indent() << "sb.Append(\", " << prop_name(*f_iter) << ": \");" << endl; + } + + t_type* ttype = (*f_iter)->get_type(); + if (ttype->is_xception() || ttype->is_struct()) + { + out << indent() << "sb.Append(" << prop_name(*f_iter) << "== null ? \"<null>\" : " << prop_name(*f_iter) << ".ToString());" << endl; + } + else + { + out << indent() << "sb.Append(" << prop_name(*f_iter) << ");" << endl; + } + + if (!is_required) + { + indent_down(); + out << indent() << "}" << endl; + } + else + { + had_required = true; // now __first must be false, so we don't need to check it anymore + } + } + + out << indent() << "sb.Append(\")\");" << endl + << indent() << "return sb.ToString();" << endl; + indent_down(); + out << indent() << "}" << endl; +} + +void t_netstd_generator::generate_netstd_union(t_struct* tunion) +{ + int ic = indent_count(); + + string f_union_name = namespace_dir_ + "/" + (tunion->get_name()) + ".cs"; + ofstream_with_content_based_conditional_update f_union; + + f_union.open(f_union_name.c_str()); + + f_union << autogen_comment() << netstd_type_usings() << netstd_thrift_usings() << endl; + + generate_netstd_union_definition(f_union, tunion); + + f_union.close(); + + indent_validate(ic, "generate_netstd_union."); +} + +void t_netstd_generator::generate_netstd_union_definition(ostream& out, t_struct* tunion) +{ + // Let's define the class first + start_netstd_namespace(out); + + out << indent() << "public abstract partial class " << tunion->get_name() << " : TUnionBase" << endl + << indent() << "{" << endl; + indent_up(); + + out << indent() << "public abstract Task WriteAsync(TProtocol tProtocol, CancellationToken cancellationToken);" << endl + << indent() << "public readonly int Isset;" << endl + << indent() << "public abstract object Data { get; }" << endl + << indent() << "protected " << tunion->get_name() << "(int isset)" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "Isset = isset;" << endl; + indent_down(); + out << indent() << "}" << endl << endl; + + out << indent() << "public class ___undefined : " << tunion->get_name() << endl + << indent() << "{" << endl; + indent_up(); + + out << indent() << "public override object Data { get { return null; } }" << endl + << indent() << "public ___undefined() : base(0) {}" << endl << endl; + + out << indent() << "public override Task WriteAsync(TProtocol oprot, CancellationToken cancellationToken)" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "throw new TProtocolException( TProtocolException.INVALID_DATA, \"Cannot persist an union type which is not set.\");" << endl; + indent_down(); + out << indent() << "}" << endl << endl; + indent_down(); + out << indent() << "}" << endl << endl; + + const vector<t_field*>& fields = tunion->get_members(); + vector<t_field*>::const_iterator f_iter; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + generate_netstd_union_class(out, tunion, (*f_iter)); + } + + generate_netstd_union_reader(out, tunion); + + indent_down(); + out << indent() << "}" << endl << endl; + + end_netstd_namespace(out); +} + +void t_netstd_generator::generate_netstd_union_class(ostream& out, t_struct* tunion, t_field* tfield) +{ + out << indent() << "public " << type_name(tfield->get_type()) << " As_" << tfield->get_name() << endl; + out << indent() << "{" << endl; + indent_up(); + out << indent() << "get" << endl; + out << indent() << "{" << endl; + indent_up(); + out << indent() << "return (" << tfield->get_key() << " == Isset) ? (" << type_name(tfield->get_type()) << ")Data : default(" << type_name(tfield->get_type()) << ");" << endl; + indent_down(); + out << indent() << "}" << endl; + indent_down(); + out << indent() << "}" << endl + << endl; + + + out << indent() << "public class " << tfield->get_name() << " : " << tunion->get_name() << endl + << indent() << "{" << endl; + indent_up(); + + out << indent() << "private " << type_name(tfield->get_type()) << " _data;" << endl + << indent() << "public override object Data { get { return _data; } }" << endl + << indent() << "public " << tfield->get_name() << "(" << type_name(tfield->get_type()) << " data) : base("<< tfield->get_key() <<")" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "this._data = data;" << endl; + indent_down(); + out << indent() << "}" << endl; + + out << indent() << "public override async Task WriteAsync(TProtocol oprot, CancellationToken cancellationToken) {" << endl; + indent_up(); + + out << indent() << "oprot.IncrementRecursionDepth();" << endl + << indent() << "try" << endl + << indent() << "{" << endl; + indent_up(); + + out << indent() << "var struc = new TStruct(\"" << tunion->get_name() << "\");" << endl + << indent() << "await oprot.WriteStructBeginAsync(struc, cancellationToken);" << endl; + + out << indent() << "var field = new TField();" << endl + << indent() << "field.Name = \"" << tfield->get_name() << "\";" << endl + << indent() << "field.Type = " << type_to_enum(tfield->get_type()) << ";" << endl + << indent() << "field.ID = " << tfield->get_key() << ";" << endl + << indent() << "await oprot.WriteFieldBeginAsync(field, cancellationToken);" << endl; + + generate_serialize_field(out, tfield, "_data", true); + + out << indent() << "await oprot.WriteFieldEndAsync(cancellationToken);" << endl + << indent() << "await oprot.WriteFieldStopAsync(cancellationToken);" << endl + << indent() << "await oprot.WriteStructEndAsync(cancellationToken);" << endl; + indent_down(); + out << indent() << "}" << endl + << indent() << "finally" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "oprot.DecrementRecursionDepth();" << endl; + indent_down(); + out << indent() << "}" << endl; + out << indent() << "}" << endl; + indent_down(); + out << indent() << "}" << endl << endl; +} + +void t_netstd_generator::generate_netstd_struct_equals(ostream& out, t_struct* tstruct) +{ + out << indent() << "public override bool Equals(object that)" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "var other = that as " << check_and_correct_struct_name(normalize_name(tstruct->get_name())) << ";" << endl + << indent() << "if (other == null) return false;" << endl + << indent() << "if (ReferenceEquals(this, other)) return true;" << endl; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + bool first = true; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + if (first) + { + first = false; + out << indent() << "return "; + indent_up(); + } + else + { + out << endl; + out << indent() << "&& "; + } + if (!field_is_required((*f_iter))) + { + out << "((__isset." << normalize_name((*f_iter)->get_name()) << " == other.__isset." + << normalize_name((*f_iter)->get_name()) << ") && ((!__isset." + << normalize_name((*f_iter)->get_name()) << ") || ("; + } + t_type* ttype = (*f_iter)->get_type(); + if (ttype->is_container() || ttype->is_binary()) + { + out << "TCollections.Equals("; + } + else + { + out << "System.Object.Equals("; + } + out << prop_name((*f_iter)) << ", other." << prop_name((*f_iter)) << ")"; + if (!field_is_required((*f_iter))) + { + out << ")))"; + } + } + if (first) + { + out << indent() << "return true;" << endl; + } + else + { + out << ";" << endl; + indent_down(); + } + + indent_down(); + out << indent() << "}" << endl << endl; +} + +void t_netstd_generator::generate_netstd_struct_hashcode(ostream& out, t_struct* tstruct) +{ + out << indent() << "public override int GetHashCode() {" << endl; + indent_up(); + + out << indent() << "int hashcode = 157;" << endl; + out << indent() << "unchecked {" << endl; + indent_up(); + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + t_type* ttype = (*f_iter)->get_type(); + if (!field_is_required((*f_iter))) + { + out << indent() << "if(__isset." << normalize_name((*f_iter)->get_name()) << ")" << endl; + indent_up(); + } + out << indent() << "hashcode = (hashcode * 397) + "; + if (ttype->is_container()) + { + out << "TCollections.GetHashCode(" << prop_name((*f_iter)) << ")"; + } + else + { + out << prop_name((*f_iter)) << ".GetHashCode()"; + } + out << ";" << endl; + + if (!field_is_required((*f_iter))) + { + indent_down(); + } + } + + indent_down(); + out << indent() << "}" << endl; + out << indent() << "return hashcode;" << endl; + + indent_down(); + out << indent() << "}" << endl << endl; +} + +void t_netstd_generator::generate_service(t_service* tservice) +{ + int ic = indent_count(); + + string f_service_name = namespace_dir_ + "/" + service_name_ + ".cs"; + ofstream_with_content_based_conditional_update f_service; + f_service.open(f_service_name.c_str()); + + f_service << autogen_comment() << netstd_type_usings() << netstd_thrift_usings() << endl; + + start_netstd_namespace(f_service); + + f_service << indent() << "public partial class " << normalize_name(service_name_) << endl + << indent() << "{" << endl; + indent_up(); + + generate_service_interface(f_service, tservice); + generate_service_client(f_service, tservice); + generate_service_server(f_service, tservice); + generate_service_helpers(f_service, tservice); + + indent_down(); + f_service << indent() << "}" << endl; + + end_netstd_namespace(f_service); + f_service.close(); + + indent_validate(ic, "generate_service."); +} + +void t_netstd_generator::generate_service_interface(ostream& out, t_service* tservice) +{ + string extends = ""; + string extends_iface = ""; + if (tservice->get_extends() != NULL) + { + extends = type_name(tservice->get_extends()); + extends_iface = " : " + extends + ".IAsync"; + } + + //out << endl << endl; + + generate_netstd_doc(out, tservice); + + if (is_wcf_enabled()) + { + out << indent() << "[ServiceContract(Namespace=\"" << wcf_namespace_ << "\")]" << endl; + } + + out << indent() << "public interface IAsync" << extends_iface << endl + << indent() << "{" << endl; + + indent_up(); + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) + { + generate_netstd_doc(out, *f_iter); + + // if we're using WCF, add the corresponding attributes + if (is_wcf_enabled()) + { + out << indent() << "[OperationContract]" << endl; + + const vector<t_field*>& xceptions = (*f_iter)->get_xceptions()->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) + { + out << indent() << "[FaultContract(typeof(" + type_name((*x_iter)->get_type()) + "Fault))]" << endl; + } + } + + out << indent() << function_signature_async(*f_iter) << ";" << endl << endl; + } + indent_down(); + out << indent() << "}" << endl << endl; +} + +void t_netstd_generator::generate_service_helpers(ostream& out, t_service* tservice) +{ + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) + { + t_struct* ts = (*f_iter)->get_arglist(); + generate_netstd_struct_definition(out, ts, false, true); + generate_function_helpers(out, *f_iter); + } +} + +void t_netstd_generator::generate_service_client(ostream& out, t_service* tservice) +{ + string extends = ""; + string extends_client = ""; + if (tservice->get_extends() != NULL) + { + extends = type_name(tservice->get_extends()); + extends_client = extends + ".Client, "; + } + else + { + extends_client = "TBaseClient, IDisposable, "; + } + + out << endl; + + generate_netstd_doc(out, tservice); + + out << indent() << "public class Client : " << extends_client << "IAsync" << endl + << indent() << "{" << endl; + indent_up(); + + out << indent() << "public Client(TProtocol protocol) : this(protocol, protocol)" << endl + << indent() << "{" << endl + << indent() << "}" << endl + << endl + << indent() << "public Client(TProtocol inputProtocol, TProtocol outputProtocol) : base(inputProtocol, outputProtocol)" + << indent() << "{" << endl + << indent() << "}" << endl; + + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator functions_iterator; + + for (functions_iterator = functions.begin(); functions_iterator != functions.end(); ++functions_iterator) + { + string function_name = correct_function_name_for_async((*functions_iterator)->get_name()); + + // async + out << indent() << "public async " << function_signature_async(*functions_iterator, "") << endl + << indent() << "{" << endl; + indent_up(); + + string argsname = (*functions_iterator)->get_name() + "Args"; + + out << indent() << "await OutputProtocol.WriteMessageBeginAsync(new TMessage(\"" << function_name + << "\", " << ((*functions_iterator)->is_oneway() ? "TMessageType.Oneway" : "TMessageType.Call") << ", SeqId), cancellationToken);" << endl + << indent() << endl + << indent() << "var args = new " << argsname << "();" << endl; + + t_struct* arg_struct = (*functions_iterator)->get_arglist(); + prepare_member_name_mapping(arg_struct); + const vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator fld_iter; + + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) + { + out << indent() << "args." << prop_name(*fld_iter) << " = " << normalize_name((*fld_iter)->get_name()) << ";" << endl; + } + + out << indent() << endl + << indent() << "await args.WriteAsync(OutputProtocol, cancellationToken);" << endl + << indent() << "await OutputProtocol.WriteMessageEndAsync(cancellationToken);" << endl + << indent() << "await OutputProtocol.Transport.FlushAsync(cancellationToken);" << endl; + + if (!(*functions_iterator)->is_oneway()) + { + string resultname = (*functions_iterator)->get_name() + "Result"; + t_struct noargs(program_); + t_struct* xs = (*functions_iterator)->get_xceptions(); + prepare_member_name_mapping(xs, xs->get_members(), resultname); + + out << indent() << endl + << indent() << "var msg = await InputProtocol.ReadMessageBeginAsync(cancellationToken);" << endl + << indent() << "if (msg.Type == TMessageType.Exception)" << endl + << indent() << "{" << endl; + indent_up(); + + out << indent() << "var x = await TApplicationException.ReadAsync(InputProtocol, cancellationToken);" << endl + << indent() << "await InputProtocol.ReadMessageEndAsync(cancellationToken);" << endl + << indent() << "throw x;" << endl; + indent_down(); + + out << indent() << "}" << endl + << endl + << indent() << "var result = new " << resultname << "();" << endl + << indent() << "await result.ReadAsync(InputProtocol, cancellationToken);" << endl + << indent() << "await InputProtocol.ReadMessageEndAsync(cancellationToken);" << endl; + + if (!(*functions_iterator)->get_returntype()->is_void()) + { + out << indent() << "if (result.__isset.success)" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "return result.Success;" << endl; + indent_down(); + out << indent() << "}" << endl; + } + + const vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) + { + out << indent() << "if (result.__isset." << normalize_name((*x_iter)->get_name()) << ")" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "throw result." << prop_name(*x_iter) << ";" << endl; + indent_down(); + out << indent() << "}" << endl; + } + + if ((*functions_iterator)->get_returntype()->is_void()) + { + out << indent() << "return;" << endl; + } + else + { + out << indent() << "throw new TApplicationException(TApplicationException.ExceptionType.MissingResult, \"" + << function_name << " failed: unknown result\");" << endl; + } + + cleanup_member_name_mapping((*functions_iterator)->get_xceptions()); + indent_down(); + out << indent() << "}" << endl << endl; + } + else + { + indent_down(); + out << indent() << "}" << endl; + } + } + + indent_down(); + out << indent() << "}" << endl << endl; +} + +void t_netstd_generator::generate_service_server(ostream& out, t_service* tservice) +{ + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + string extends = ""; + string extends_processor = ""; + if (tservice->get_extends() != NULL) + { + extends = type_name(tservice->get_extends()); + extends_processor = extends + ".AsyncProcessor, "; + } + + out << indent() << "public class AsyncProcessor : " << extends_processor << "ITAsyncProcessor" << endl + << indent() << "{" << endl; + + indent_up(); + + out << indent() << "private IAsync _iAsync;" << endl + << endl + << indent() << "public AsyncProcessor(IAsync iAsync)"; + + if (!extends.empty()) + { + out << " : base(iAsync)"; + } + + out << endl + << indent() << "{" << endl; + indent_up(); + + out << indent() << "if (iAsync == null) throw new ArgumentNullException(nameof(iAsync));" << endl + << endl + << indent() << "_iAsync = iAsync;" << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) + { + string function_name = (*f_iter)->get_name(); + out << indent() << "processMap_[\"" << correct_function_name_for_async(function_name) << "\"] = " << function_name << "_ProcessAsync;" << endl; + } + + indent_down(); + out << indent() << "}" << endl + << endl; + + if (extends.empty()) + { + out << indent() << "protected delegate Task ProcessFunction(int seqid, TProtocol iprot, TProtocol oprot, CancellationToken cancellationToken);" << endl; + } + + if (extends.empty()) + { + out << indent() << "protected Dictionary<string, ProcessFunction> processMap_ = new Dictionary<string, ProcessFunction>();" << endl; + } + + out << endl; + + if (extends.empty()) + { + out << indent() << "public async Task<bool> ProcessAsync(TProtocol iprot, TProtocol oprot)" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "return await ProcessAsync(iprot, oprot, CancellationToken.None);" << endl; + indent_down(); + out << indent() << "}" << endl << endl; + + out << indent() << "public async Task<bool> ProcessAsync(TProtocol iprot, TProtocol oprot, CancellationToken cancellationToken)" << endl; + } + else + { + out << indent() << "public new async Task<bool> ProcessAsync(TProtocol iprot, TProtocol oprot)" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "return await ProcessAsync(iprot, oprot, CancellationToken.None);" << endl; + indent_down(); + out << indent() << "}" << endl << endl; + + out << indent() << "public new async Task<bool> ProcessAsync(TProtocol iprot, TProtocol oprot, CancellationToken cancellationToken)" << endl; + } + + out << indent() << "{" << endl; + indent_up(); + out << indent() << "try" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "var msg = await iprot.ReadMessageBeginAsync(cancellationToken);" << endl + << endl + << indent() << "ProcessFunction fn;" << endl + << indent() << "processMap_.TryGetValue(msg.Name, out fn);" << endl + << endl + << indent() << "if (fn == null)" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "await TProtocolUtil.SkipAsync(iprot, TType.Struct, cancellationToken);" << endl + << indent() << "await iprot.ReadMessageEndAsync(cancellationToken);" << endl + << indent() << "var x = new TApplicationException (TApplicationException.ExceptionType.UnknownMethod, \"Invalid method name: '\" + msg.Name + \"'\");" << endl + << indent() << "await oprot.WriteMessageBeginAsync(new TMessage(msg.Name, TMessageType.Exception, msg.SeqID), cancellationToken);" << endl + << indent() << "await x.WriteAsync(oprot, cancellationToken);" << endl + << indent() << "await oprot.WriteMessageEndAsync(cancellationToken);" << endl + << indent() << "await oprot.Transport.FlushAsync(cancellationToken);" << endl + << indent() << "return true;" << endl; + indent_down(); + out << indent() << "}" << endl + << endl + << indent() << "await fn(msg.SeqID, iprot, oprot, cancellationToken);" << endl + << endl; + indent_down(); + out << indent() << "}" << endl; + out << indent() << "catch (IOException)" << endl + << indent() << "{" << endl; + indent_up(); + out << indent() << "return false;" << endl; + indent_down(); + out << indent() << "}" << endl + << endl + << indent() << "return true;" << endl; + indent_down(); + out << indent() << "}" << endl << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) + { + generate_process_function_async(out, tservice, *f_iter); + } + + indent_down(); + out << indent() << "}" << endl << endl; +} + +void t_netstd_generator::generate_function_helpers(ostream& out, t_function* tfunction) +{ + if (tfunction->is_oneway()) + { + return; + } + + t_struct result(program_, tfunction->get_name() + "_result"); + t_field success(tfunction->get_returntype(), "success", 0); + if (!tfunction->get_returntype()->is_void()) + { + result.append(&success); + } + + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& fields = xs->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + result.append(*f_iter); + } + + generate_netstd_struct_definition(out, &result, false, true, true); +} + +void t_netstd_generator::generate_process_function_async(ostream& out, t_service* tservice, t_function* tfunction) +{ + (void)tservice; + out << indent() << "public async Task " << tfunction->get_name() + << "_ProcessAsync(int seqid, TProtocol iprot, TProtocol oprot, CancellationToken cancellationToken)" << endl + << indent() << "{" << endl; + indent_up(); + + string argsname = tfunction->get_name() + "Args"; + string resultname = tfunction->get_name() + "Result"; + + out << indent() << "var args = new " << argsname << "();" << endl + << indent() << "await args.ReadAsync(iprot, cancellationToken);" << endl + << indent() << "await iprot.ReadMessageEndAsync(cancellationToken);" << endl; + + if (!tfunction->is_oneway()) + { + out << indent() << "var result = new " << resultname << "();" << endl; + } + + out << indent() << "try" << endl + << indent() << "{" << endl; + indent_up(); + + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& xceptions = xs->get_members(); + + if (xceptions.size() > 0) + { + out << indent() << "try" << endl + << indent() << "{" << endl; + indent_up(); + } + + t_struct* arg_struct = tfunction->get_arglist(); + const vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + + out << indent(); + if (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void()) + { + out << "result.Success = "; + } + + out << "await _iAsync." << normalize_name(tfunction->get_name()) << "Async("; + + bool first = true; + prepare_member_name_mapping(arg_struct); + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + if (first) + { + first = false; + } + else + { + out << ", "; + } + + out << "args." << prop_name(*f_iter); + } + + cleanup_member_name_mapping(arg_struct); + + if (!first) + { + out << ", "; + } + + out << "cancellationToken);" << endl; + + vector<t_field*>::const_iterator x_iter; + + prepare_member_name_mapping(xs, xs->get_members(), resultname); + if (xceptions.size() > 0) + { + indent_down(); + out << indent() << "}" << endl; + + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) + { + out << indent() << "catch (" << type_name((*x_iter)->get_type()) << " " << (*x_iter)->get_name() << ")" << endl + << indent() << "{" << endl; + + if (!tfunction->is_oneway()) + { + indent_up(); + out << indent() << "result." << prop_name(*x_iter) << " = " << (*x_iter)->get_name() << ";" << endl; + indent_down(); + } + out << indent() << "}" << endl; + } + } + + if (!tfunction->is_oneway()) + { + out << indent() << "await oprot.WriteMessageBeginAsync(new TMessage(\"" + << correct_function_name_for_async(tfunction->get_name()) << "\", TMessageType.Reply, seqid), cancellationToken); " << endl + << indent() << "await result.WriteAsync(oprot, cancellationToken);" << endl; + } + indent_down(); + + cleanup_member_name_mapping(xs); + + out << indent() << "}" << endl + << indent() << "catch (TTransportException)" << endl + << indent() << "{" << endl + << indent() << " throw;" << endl + << indent() << "}" << endl + << indent() << "catch (Exception ex)" << endl + << indent() << "{" << endl; + indent_up(); + + out << indent() << "Console.Error.WriteLine(\"Error occurred in processor:\");" << endl + << indent() << "Console.Error.WriteLine(ex.ToString());" << endl; + + if (tfunction->is_oneway()) + { + indent_down(); + out << indent() << "}" << endl; + } + else + { + out << indent() << "var x = new TApplicationException(TApplicationException.ExceptionType.InternalError,\" Internal error.\");" << endl + << indent() << "await oprot.WriteMessageBeginAsync(new TMessage(\"" << correct_function_name_for_async(tfunction->get_name()) + << "\", TMessageType.Exception, seqid), cancellationToken);" << endl + << indent() << "await x.WriteAsync(oprot, cancellationToken);" << endl; + indent_down(); + + out << indent() << "}" << endl + << indent() << "await oprot.WriteMessageEndAsync(cancellationToken);" << endl + << indent() << "await oprot.Transport.FlushAsync(cancellationToken);" << endl; + } + + indent_down(); + out << indent() << "}" << endl << endl; +} + +void t_netstd_generator::generate_netstd_union_reader(ostream& out, t_struct* tunion) +{ + // Thanks to THRIFT-1768, we don't need to check for required fields in the union + const vector<t_field*>& fields = tunion->get_members(); + vector<t_field*>::const_iterator f_iter; + + out << indent() << "public static async Task<" << tunion->get_name() << "> ReadAsync(TProtocol iprot, CancellationToken cancellationToken)" << endl; + scope_up(out); + + out << indent() << "iprot.IncrementRecursionDepth();" << endl; + out << indent() << "try" << endl; + scope_up(out); + + out << indent() << tunion->get_name() << " retval;" << endl; + out << indent() << "await iprot.ReadStructBeginAsync(cancellationToken);" << endl; + out << indent() << "TField field = await iprot.ReadFieldBeginAsync(cancellationToken);" << endl; + // we cannot have the first field be a stop -- we must have a single field defined + out << indent() << "if (field.Type == TType.Stop)" << endl; + scope_up(out); + out << indent() << "await iprot.ReadFieldEndAsync(cancellationToken);" << endl; + out << indent() << "retval = new ___undefined();" << endl; + scope_down(out); + out << indent() << "else" << endl; + scope_up(out); + out << indent() << "switch (field.ID)" << endl; + scope_up(out); + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + out << indent() << "case " << (*f_iter)->get_key() << ":" << endl; + indent_up(); + out << indent() << "if (field.Type == " << type_to_enum((*f_iter)->get_type()) << ") {" << endl; + indent_up(); + + out << indent() << type_name((*f_iter)->get_type()) << " temp;" << endl; + generate_deserialize_field(out, (*f_iter), "temp", true); + out << indent() << "retval = new " << (*f_iter)->get_name() << "(temp);" << endl; + + indent_down(); + out << indent() << "} else { " << endl << indent() << " await TProtocolUtil.SkipAsync(iprot, field.Type, cancellationToken);" + << endl << indent() << " retval = new ___undefined();" << endl << indent() << "}" << endl + << indent() << "break;" << endl; + indent_down(); + } + + out << indent() << "default: " << endl; + indent_up(); + out << indent() << "await TProtocolUtil.SkipAsync(iprot, field.Type, cancellationToken);" << endl << indent() + << "retval = new ___undefined();" << endl; + out << indent() << "break;" << endl; + indent_down(); + + scope_down(out); + + out << indent() << "await iprot.ReadFieldEndAsync(cancellationToken);" << endl; + + out << indent() << "if ((await iprot.ReadFieldBeginAsync(cancellationToken)).Type != TType.Stop)" << endl; + scope_up(out); + out << indent() << "throw new TProtocolException(TProtocolException.INVALID_DATA);" << endl; + scope_down(out); + + // end of else for TStop + scope_down(out); + out << indent() << "await iprot.ReadStructEndAsync(cancellationToken);" << endl; + out << indent() << "return retval;" << endl; + indent_down(); + + scope_down(out); + out << indent() << "finally" << endl; + scope_up(out); + out << indent() << "iprot.DecrementRecursionDepth();" << endl; + scope_down(out); + + out << indent() << "}" << endl << endl; +} + +void t_netstd_generator::generate_deserialize_field(ostream& out, t_field* tfield, string prefix, bool is_propertyless) +{ + t_type* type = tfield->get_type(); + while (type->is_typedef()) + { + type = static_cast<t_typedef*>(type)->get_type(); + } + + if (type->is_void()) + { + throw "CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name(); + } + + string name = prefix + (is_propertyless ? "" : prop_name(tfield)); + + if (type->is_struct() || type->is_xception()) + { + generate_deserialize_struct(out, static_cast<t_struct*>(type), name); + } + else if (type->is_container()) + { + generate_deserialize_container(out, type, name); + } + else if (type->is_base_type() || type->is_enum()) + { + out << indent() << name << " = "; + + if (type->is_enum()) + { + out << "(" << type_name(type) << ")"; + } + + out << "await iprot."; + + if (type->is_base_type()) + { + t_base_type::t_base tbase = static_cast<t_base_type*>(type)->get_base(); + switch (tbase) + { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + if (type->is_binary()) + { + out << "ReadBinaryAsync(cancellationToken);"; + } + else + { + out << "ReadStringAsync(cancellationToken);"; + } + break; + case t_base_type::TYPE_BOOL: + out << "ReadBoolAsync(cancellationToken);"; + break; + case t_base_type::TYPE_I8: + out << "ReadByteAsync(cancellationToken);"; + break; + case t_base_type::TYPE_I16: + out << "ReadI16Async(cancellationToken);"; + break; + case t_base_type::TYPE_I32: + out << "ReadI32Async(cancellationToken);"; + break; + case t_base_type::TYPE_I64: + out << "ReadI64Async(cancellationToken);"; + break; + case t_base_type::TYPE_DOUBLE: + out << "ReadDoubleAsync(cancellationToken);"; + break; + default: + throw "compiler error: no C# name for base type " + t_base_type::t_base_name(tbase); + } + } + else if (type->is_enum()) + { + out << "ReadI32Async(cancellationToken);"; + } + out << endl; + } + else + { + printf("DO NOT KNOW HOW TO DESERIALIZE FIELD '%s' TYPE '%s'\n", tfield->get_name().c_str(), type_name(type).c_str()); + } +} + +void t_netstd_generator::generate_deserialize_struct(ostream& out, t_struct* tstruct, string prefix) +{ + if (is_union_enabled() && tstruct->is_union()) + { + out << indent() << prefix << " = await " << type_name(tstruct) << ".ReadAsync(iprot, cancellationToken);" << endl; + } + else + { + out << indent() << prefix << " = new " << type_name(tstruct) << "();" << endl + << indent() << "await " << prefix << ".ReadAsync(iprot, cancellationToken);" << endl; + } +} + +void t_netstd_generator::generate_deserialize_container(ostream& out, t_type* ttype, string prefix) +{ + out << indent() << "{" << endl; + indent_up(); + + string obj; + + if (ttype->is_map()) + { + obj = tmp("_map"); + } + else if (ttype->is_set()) + { + obj = tmp("_set"); + } + else if (ttype->is_list()) + { + obj = tmp("_list"); + } + + if (ttype->is_map()) + { + out << indent() << "TMap " << obj << " = await iprot.ReadMapBeginAsync(cancellationToken);" << endl; + } + else if (ttype->is_set()) + { + out << indent() << "TSet " << obj << " = await iprot.ReadSetBeginAsync(cancellationToken);" << endl; + } + else if (ttype->is_list()) + { + out << indent() << "TList " << obj << " = await iprot.ReadListBeginAsync(cancellationToken);" << endl; + } + + out << indent() << prefix << " = new " << type_name(ttype) << "(" << obj << ".Count);" << endl; + string i = tmp("_i"); + out << indent() << "for(int " << i << " = 0; " << i << " < " << obj << ".Count; ++" << i << ")" << endl + << indent() << "{" << endl; + indent_up(); + + if (ttype->is_map()) + { + generate_deserialize_map_element(out, static_cast<t_map*>(ttype), prefix); + } + else if (ttype->is_set()) + { + generate_deserialize_set_element(out, static_cast<t_set*>(ttype), prefix); + } + else if (ttype->is_list()) + { + generate_deserialize_list_element(out, static_cast<t_list*>(ttype), prefix); + } + + indent_down(); + out << indent() << "}" << endl; + + if (ttype->is_map()) + { + out << indent() << "await iprot.ReadMapEndAsync(cancellationToken);" << endl; + } + else if (ttype->is_set()) + { + out << indent() << "await iprot.ReadSetEndAsync(cancellationToken);" << endl; + } + else if (ttype->is_list()) + { + out << indent() << "await iprot.ReadListEndAsync(cancellationToken);" << endl; + } + + indent_down(); + out << indent() << "}" << endl; +} + +void t_netstd_generator::generate_deserialize_map_element(ostream& out, t_map* tmap, string prefix) +{ + string key = tmp("_key"); + string val = tmp("_val"); + + t_field fkey(tmap->get_key_type(), key); + t_field fval(tmap->get_val_type(), val); + + out << indent() << declare_field(&fkey) << endl; + out << indent() << declare_field(&fval) << endl; + + generate_deserialize_field(out, &fkey); + generate_deserialize_field(out, &fval); + + out << indent() << prefix << "[" << key << "] = " << val << ";" << endl; +} + +void t_netstd_generator::generate_deserialize_set_element(ostream& out, t_set* tset, string prefix) +{ + string elem = tmp("_elem"); + t_field felem(tset->get_elem_type(), elem); + + out << indent() << declare_field(&felem) << endl; + + generate_deserialize_field(out, &felem); + + out << indent() << prefix << ".Add(" << elem << ");" << endl; +} + +void t_netstd_generator::generate_deserialize_list_element(ostream& out, t_list* tlist, string prefix) +{ + string elem = tmp("_elem"); + t_field felem(tlist->get_elem_type(), elem); + + out << indent() << declare_field(&felem) << endl; + + generate_deserialize_field(out, &felem); + + out << indent() << prefix << ".Add(" << elem << ");" << endl; +} + +void t_netstd_generator::generate_serialize_field(ostream& out, t_field* tfield, string prefix, bool is_propertyless) +{ + t_type* type = tfield->get_type(); + while (type->is_typedef()) + { + type = static_cast<t_typedef*>(type)->get_type(); + } + + string name = prefix + (is_propertyless ? "" : prop_name(tfield)); + + if (type->is_void()) + { + throw "CANNOT GENERATE SERIALIZE CODE FOR void TYPE: " + name; + } + + if (type->is_struct() || type->is_xception()) + { + generate_serialize_struct(out, static_cast<t_struct*>(type), name); + } + else if (type->is_container()) + { + generate_serialize_container(out, type, name); + } + else if (type->is_base_type() || type->is_enum()) + { + out << indent() << "await oprot."; + + string nullable_name = name; + + if (type->is_base_type()) + { + t_base_type::t_base tbase = static_cast<t_base_type*>(type)->get_base(); + switch (tbase) + { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + case t_base_type::TYPE_STRING: + if (type->is_binary()) + { + out << "WriteBinaryAsync("; + } + else + { + out << "WriteStringAsync("; + } + out << name << ", cancellationToken);"; + break; + case t_base_type::TYPE_BOOL: + out << "WriteBoolAsync(" << nullable_name << ", cancellationToken);"; + break; + case t_base_type::TYPE_I8: + out << "WriteByteAsync(" << nullable_name << ", cancellationToken);"; + break; + case t_base_type::TYPE_I16: + out << "WriteI16Async(" << nullable_name << ", cancellationToken);"; + break; + case t_base_type::TYPE_I32: + out << "WriteI32Async(" << nullable_name << ", cancellationToken);"; + break; + case t_base_type::TYPE_I64: + out << "WriteI64Async(" << nullable_name << ", cancellationToken);"; + break; + case t_base_type::TYPE_DOUBLE: + out << "WriteDoubleAsync(" << nullable_name << ", cancellationToken);"; + break; + default: + throw "compiler error: no C# name for base type " + t_base_type::t_base_name(tbase); + } + } + else if (type->is_enum()) + { + out << "WriteI32Async((int)" << nullable_name << ", cancellationToken);"; + } + out << endl; + } + else + { + printf("DO NOT KNOW HOW TO SERIALIZE '%s%s' TYPE '%s'\n", prefix.c_str(), tfield->get_name().c_str(), type_name(type).c_str()); + } +} + +void t_netstd_generator::generate_serialize_struct(ostream& out, t_struct* tstruct, string prefix) +{ + (void)tstruct; + out << indent() << "await " << prefix << ".WriteAsync(oprot, cancellationToken);" << endl; +} + +void t_netstd_generator::generate_serialize_container(ostream& out, t_type* ttype, string prefix) +{ + out << indent() << "{" << endl; + indent_up(); + + if (ttype->is_map()) + { + out << indent() << "await oprot.WriteMapBeginAsync(new TMap(" << type_to_enum(static_cast<t_map*>(ttype)->get_key_type()) + << ", " << type_to_enum(static_cast<t_map*>(ttype)->get_val_type()) << ", " << prefix + << ".Count), cancellationToken);" << endl; + } + else if (ttype->is_set()) + { + out << indent() << "await oprot.WriteSetBeginAsync(new TSet(" << type_to_enum(static_cast<t_set*>(ttype)->get_elem_type()) + << ", " << prefix << ".Count), cancellationToken);" << endl; + } + else if (ttype->is_list()) + { + out << indent() << "await oprot.WriteListBeginAsync(new TList(" + << type_to_enum(static_cast<t_list*>(ttype)->get_elem_type()) << ", " << prefix << ".Count), cancellationToken);" + << endl; + } + + string iter = tmp("_iter"); + if (ttype->is_map()) + { + out << indent() << "foreach (" << type_name(static_cast<t_map*>(ttype)->get_key_type()) << " " << iter + << " in " << prefix << ".Keys)"; + } + else if (ttype->is_set()) + { + out << indent() << "foreach (" << type_name(static_cast<t_set*>(ttype)->get_elem_type()) << " " << iter + << " in " << prefix << ")"; + } + else if (ttype->is_list()) + { + out << indent() << "foreach (" << type_name(static_cast<t_list*>(ttype)->get_elem_type()) << " " << iter + << " in " << prefix << ")"; + } + + out << endl; + out << indent() << "{" << endl; + indent_up(); + + if (ttype->is_map()) + { + generate_serialize_map_element(out, static_cast<t_map*>(ttype), iter, prefix); + } + else if (ttype->is_set()) + { + generate_serialize_set_element(out, static_cast<t_set*>(ttype), iter); + } + else if (ttype->is_list()) + { + generate_serialize_list_element(out, static_cast<t_list*>(ttype), iter); + } + + indent_down(); + out << indent() << "}" << endl; + + if (ttype->is_map()) + { + out << indent() << "await oprot.WriteMapEndAsync(cancellationToken);" << endl; + } + else if (ttype->is_set()) + { + out << indent() << "await oprot.WriteSetEndAsync(cancellationToken);" << endl; + } + else if (ttype->is_list()) + { + out << indent() << "await oprot.WriteListEndAsync(cancellationToken);" << endl; + } + + indent_down(); + out << indent() << "}" << endl; +} + +void t_netstd_generator::generate_serialize_map_element(ostream& out, t_map* tmap, string iter, string map) +{ + t_field kfield(tmap->get_key_type(), iter); + generate_serialize_field(out, &kfield, ""); + t_field vfield(tmap->get_val_type(), map + "[" + iter + "]"); + generate_serialize_field(out, &vfield, ""); +} + +void t_netstd_generator::generate_serialize_set_element(ostream& out, t_set* tset, string iter) +{ + t_field efield(tset->get_elem_type(), iter); + generate_serialize_field(out, &efield, ""); +} + +void t_netstd_generator::generate_serialize_list_element(ostream& out, t_list* tlist, string iter) +{ + t_field efield(tlist->get_elem_type(), iter); + generate_serialize_field(out, &efield, ""); +} + +void t_netstd_generator::generate_property(ostream& out, t_field* tfield, bool isPublic, bool generateIsset) +{ + generate_netstd_property(out, tfield, isPublic, generateIsset, "_"); +} + +void t_netstd_generator::generate_netstd_property(ostream& out, t_field* tfield, bool isPublic, bool generateIsset, string fieldPrefix) +{ + if ((is_serialize_enabled() || is_wcf_enabled()) && isPublic) + { + out << indent() << "[DataMember(Order = 0)]" << endl; + } + bool is_required = field_is_required(tfield); + if (is_required) + { + out << indent() << (isPublic ? "public " : "private ") << type_name(tfield->get_type()) << " " << prop_name(tfield) << " { get; set; }" << endl; + } + else + { + out << indent() << (isPublic ? "public " : "private ") << type_name(tfield->get_type()) << " " << prop_name(tfield) << endl + << indent() << "{" << endl; + indent_up(); + + out << indent() << "get" << endl + << indent() << "{" << endl; + indent_up(); + + bool use_nullable = false; + + out << indent() << "return " << fieldPrefix + tfield->get_name() << ";" << endl; + indent_down(); + out << indent() << "}" << endl + << indent() << "set" << endl + << indent() << "{" << endl; + indent_up(); + + if (use_nullable) + { + if (generateIsset) + { + out << indent() << "__isset." << normalize_name(tfield->get_name()) << " = value.HasValue;" << endl; + } + out << indent() << "if (value.HasValue) this." << fieldPrefix + tfield->get_name() << " = value.Value;" << endl; + } + else + { + if (generateIsset) + { + out << indent() << "__isset." << normalize_name(tfield->get_name()) << " = true;" << endl; + } + out << indent() << "this." << fieldPrefix + tfield->get_name() << " = value;" << endl; + } + + indent_down(); + out << indent() << "}" << endl; + indent_down(); + out << indent() << "}" << endl; + } + out << endl; +} + +string t_netstd_generator::make_valid_csharp_identifier(string const& fromName) +{ + string str = fromName; + if (str.empty()) + { + return str; + } + + // tests rely on this + assert(('A' < 'Z') && ('a' < 'z') && ('0' < '9')); + + // if the first letter is a number, we add an additional underscore in front of it + char c = str.at(0); + if (('0' <= c) && (c <= '9')) + { + str = "_" + str; + } + + // following chars: letter, number or underscore + for (size_t i = 0; i < str.size(); ++i) + { + c = str.at(i); + if (('A' > c || c > 'Z') && ('a' > c || c > 'z') && ('0' > c || c > '9') && '_' != c) + { + str.replace(i, 1, "_"); + } + } + + return str; +} + +void t_netstd_generator::cleanup_member_name_mapping(void* scope) +{ + if (member_mapping_scopes.empty()) + { + throw "internal error: cleanup_member_name_mapping() no scope active"; + } + + member_mapping_scope& active = member_mapping_scopes.back(); + if (active.scope_member != scope) + { + throw "internal error: cleanup_member_name_mapping() called for wrong struct"; + } + + member_mapping_scopes.pop_back(); +} + +string t_netstd_generator::get_mapped_member_name(string name) +{ + if (!member_mapping_scopes.empty()) + { + member_mapping_scope& active = member_mapping_scopes.back(); + map<string, string>::iterator iter = active.mapping_table.find(name); + if (active.mapping_table.end() != iter) + { + return iter->second; + } + } + + pverbose("no mapping for member %s\n", name.c_str()); + return name; +} + +void t_netstd_generator::prepare_member_name_mapping(t_struct* tstruct) +{ + prepare_member_name_mapping(tstruct, tstruct->get_members(), tstruct->get_name()); +} + +void t_netstd_generator::prepare_member_name_mapping(void* scope, const vector<t_field*>& members, const string& structname) +{ + // begin new scope + member_mapping_scopes.emplace_back(); + member_mapping_scope& active = member_mapping_scopes.back(); + active.scope_member = scope; + + // current C# generator policy: + // - prop names are always rendered with an Uppercase first letter + // - struct names are used as given + std::set<string> used_member_names; + vector<t_field*>::const_iterator iter; + + // prevent name conflicts with struct (CS0542 error) + used_member_names.insert(structname); + + // prevent name conflicts with known methods (THRIFT-2942) + used_member_names.insert("Read"); + used_member_names.insert("Write"); + + for (iter = members.begin(); iter != members.end(); ++iter) + { + string oldname = (*iter)->get_name(); + string newname = prop_name(*iter, true); + while (true) + { + // new name conflicts with another member + if (used_member_names.find(newname) != used_member_names.end()) + { + pverbose("struct %s: member %s conflicts with another member\n", structname.c_str(), newname.c_str()); + newname += '_'; + continue; + } + + // add always, this helps us to detect edge cases like + // different spellings ("foo" and "Foo") within the same struct + pverbose("struct %s: member mapping %s => %s\n", structname.c_str(), oldname.c_str(), newname.c_str()); + active.mapping_table[oldname] = newname; + used_member_names.insert(newname); + break; + } + } +} + +string t_netstd_generator::prop_name(t_field* tfield, bool suppress_mapping) +{ + string name(tfield->get_name()); + if (suppress_mapping) + { + name[0] = toupper(name[0]); + } + else + { + name = get_mapped_member_name(name); + } + return name; +} + +string t_netstd_generator::type_name(t_type* ttype) +{ + while (ttype->is_typedef()) + { + ttype = static_cast<t_typedef*>(ttype)->get_type(); + } + + if (ttype->is_base_type()) + { + return base_type_name(static_cast<t_base_type*>(ttype)); + } + + if (ttype->is_map()) + { + t_map* tmap = static_cast<t_map*>(ttype); + return "Dictionary<" + type_name(tmap->get_key_type()) + ", " + type_name(tmap->get_val_type()) + ">"; + } + + if (ttype->is_set()) + { + t_set* tset = static_cast<t_set*>(ttype); + return "THashSet<" + type_name(tset->get_elem_type()) + ">"; + } + + if (ttype->is_list()) + { + t_list* tlist = static_cast<t_list*>(ttype); + return "List<" + type_name(tlist->get_elem_type()) + ">"; + } + + t_program* program = ttype->get_program(); + if (program != NULL && program != program_) + { + string ns = program->get_namespace("netstd"); + if (!ns.empty()) + { + return ns + "." + normalize_name(ttype->get_name()); + } + } + + return normalize_name(ttype->get_name()); +} + +string t_netstd_generator::base_type_name(t_base_type* tbase) +{ + switch (tbase->get_base()) + { + case t_base_type::TYPE_VOID: + return "void"; + case t_base_type::TYPE_STRING: + { + if (tbase->is_binary()) + { + return "byte[]"; + } + return "string"; + } + case t_base_type::TYPE_BOOL: + return "bool"; + case t_base_type::TYPE_I8: + return "sbyte"; + case t_base_type::TYPE_I16: + return "short"; + case t_base_type::TYPE_I32: + return "int"; + case t_base_type::TYPE_I64: + return "long"; + case t_base_type::TYPE_DOUBLE: + return "double"; + default: + throw "compiler error: no C# name for base type " + t_base_type::t_base_name(tbase->get_base()); + } +} + +string t_netstd_generator::declare_field(t_field* tfield, bool init, string prefix) +{ + string result = type_name(tfield->get_type()) + " " + prefix + tfield->get_name(); + if (init) + { + t_type* ttype = tfield->get_type(); + while (ttype->is_typedef()) + { + ttype = static_cast<t_typedef*>(ttype)->get_type(); + } + if (ttype->is_base_type() && field_has_default(tfield)) + { + std::ofstream dummy; + result += " = " + render_const_value(dummy, tfield->get_name(), ttype, tfield->get_value()); + } + else if (ttype->is_base_type()) + { + t_base_type::t_base tbase = static_cast<t_base_type*>(ttype)->get_base(); + switch (tbase) + { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + result += " = null"; + break; + case t_base_type::TYPE_BOOL: + result += " = false"; + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + result += " = 0"; + break; + case t_base_type::TYPE_DOUBLE: + result += " = (double)0"; + break; + } + } + else if (ttype->is_enum()) + { + result += " = (" + type_name(ttype) + ")0"; + } + else if (ttype->is_container()) + { + result += " = new " + type_name(ttype) + "()"; + } + else + { + result += " = new " + type_name(ttype) + "()"; + } + } + return result + ";"; +} + +string t_netstd_generator::function_signature(t_function* tfunction, string prefix) +{ + t_type* ttype = tfunction->get_returntype(); + return type_name(ttype) + " " + normalize_name(prefix + tfunction->get_name()) + "(" + argument_list(tfunction->get_arglist()) + ")"; +} + +string t_netstd_generator::function_signature_async(t_function* tfunction, string prefix) +{ + t_type* ttype = tfunction->get_returntype(); + string task = "Task"; + if (!ttype->is_void()) + { + task += "<" + type_name(ttype) + ">"; + } + + string result = task + " " + normalize_name(prefix + tfunction->get_name()) + "Async("; + string args = argument_list(tfunction->get_arglist()); + result += args; + if (!args.empty()) + { + result += ", "; + } + result += "CancellationToken cancellationToken = default(CancellationToken))"; + + return result; +} + +string t_netstd_generator::argument_list(t_struct* tstruct) +{ + string result = ""; + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + if (first) + { + first = false; + } + else + { + result += ", "; + } + result += type_name((*f_iter)->get_type()) + " " + normalize_name((*f_iter)->get_name()); + } + return result; +} + +string t_netstd_generator::type_to_enum(t_type* type) +{ + while (type->is_typedef()) + { + type = static_cast<t_typedef*>(type)->get_type(); + } + + if (type->is_base_type()) + { + t_base_type::t_base tbase = static_cast<t_base_type*>(type)->get_base(); + switch (tbase) + { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + return "TType.String"; + case t_base_type::TYPE_BOOL: + return "TType.Bool"; + case t_base_type::TYPE_I8: + return "TType.Byte"; + case t_base_type::TYPE_I16: + return "TType.I16"; + case t_base_type::TYPE_I32: + return "TType.I32"; + case t_base_type::TYPE_I64: + return "TType.I64"; + case t_base_type::TYPE_DOUBLE: + return "TType.Double"; + } + } + else if (type->is_enum()) + { + return "TType.I32"; + } + else if (type->is_struct() || type->is_xception()) + { + return "TType.Struct"; + } + else if (type->is_map()) + { + return "TType.Map"; + } + else if (type->is_set()) + { + return "TType.Set"; + } + else if (type->is_list()) + { + return "TType.List"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +void t_netstd_generator::generate_netstd_docstring_comment(ostream& out, string contents) +{ + docstring_comment(out, "/// <summary>" + endl, "/// ", contents, "/// </summary>" + endl); +} + +void t_netstd_generator::generate_netstd_doc(ostream& out, t_field* field) +{ + if (field->get_type()->is_enum()) + { + string combined_message = field->get_doc() + endl + "<seealso cref=\"" + get_enum_class_name(field->get_type()) + "\"/>"; + generate_netstd_docstring_comment(out, combined_message); + } + else + { + generate_netstd_doc(out, static_cast<t_doc*>(field)); + } +} + +void t_netstd_generator::generate_netstd_doc(ostream& out, t_doc* tdoc) +{ + if (tdoc->has_doc()) + { + generate_netstd_docstring_comment(out, tdoc->get_doc()); + } +} + +void t_netstd_generator::generate_netstd_doc(ostream& out, t_function* tfunction) +{ + if (tfunction->has_doc()) + { + stringstream ps; + const vector<t_field*>& fields = tfunction->get_arglist()->get_members(); + vector<t_field*>::const_iterator p_iter; + for (p_iter = fields.begin(); p_iter != fields.end(); ++p_iter) + { + t_field* p = *p_iter; + ps << endl << "<param name=\"" << p->get_name() << "\">"; + if (p->has_doc()) + { + string str = p->get_doc(); + str.erase(remove(str.begin(), str.end(), '\n'), str.end()); + ps << str; + } + ps << "</param>"; + } + + docstring_comment(out, + "", + "/// ", + "<summary>" + endl + tfunction->get_doc() + "</summary>" + ps.str(), + ""); + } +} + +void t_netstd_generator::docstring_comment(ostream& out, const string& comment_start, const string& line_prefix, const string& contents, const string& comment_end) +{ + if (comment_start != "") + { + out << indent() << comment_start; + } + + stringstream docs(contents, std::ios_base::in); + + while (!(docs.eof() || docs.fail())) + { + char line[1024]; + docs.getline(line, 1024); + + // Just prnt a newline when the line & prefix are empty. + if (strlen(line) == 0 && line_prefix == "" && !docs.eof()) + { + out << endl; + } + else if (strlen(line) > 0 || !docs.eof()) + { // skip the empty last line + out << indent() << line_prefix << line << endl; + } + } + if (comment_end != "") + { + out << indent() << comment_end; + } +} + +string t_netstd_generator::get_enum_class_name(t_type* type) +{ + string package = ""; + t_program* program = type->get_program(); + if (program != NULL && program != program_) + { + package = program->get_namespace("netstd") + "."; + } + return package + type->get_name(); +} + +THRIFT_REGISTER_GENERATOR( + netstd, + "C#", + " wcf: Adds bindings for WCF to generated classes.\n" + " serial: Add serialization support to generated classes.\n" + " union: Use new union typing, which includes a static read function for union types.\n" +) diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_netstd_generator.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_netstd_generator.h new file mode 100644 index 000000000..fd9e6c070 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_netstd_generator.h @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +#include <cassert> + +#include <string> +#include <fstream> +#include <iostream> +#include <vector> +#include <cctype> + +#include <stdlib.h> +#include <sys/stat.h> +#include <sstream> + +#include "thrift/platform.h" +#include "thrift/generate/t_oop_generator.h" + +using std::map; +using std::ostream; +using std::ostringstream; +using std::string; +using std::stringstream; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes + +class t_netstd_generator : public t_oop_generator +{ + + struct member_mapping_scope + { + public: + member_mapping_scope() : scope_member(0) { } + void* scope_member; + map<string, string> mapping_table; + }; + +public: + t_netstd_generator(t_program* program, const map<string, string>& parsed_options, const string& option_string); + + bool is_wcf_enabled() const; + bool is_nullable_enabled() const; + bool is_hashcode_enabled() const; + bool is_serialize_enabled() const; + bool is_union_enabled() const; + map<string, int> get_keywords_list() const; + + // overrides + void init_generator(); + void close_generator(); + void generate_consts(vector<t_const*> consts); + void generate_consts(ostream& out, vector<t_const*> consts); + void generate_typedef(t_typedef* ttypedef); + void generate_enum(t_enum* tenum); + void generate_enum(ostream& out, t_enum* tenum); + void generate_struct(t_struct* tstruct); + void generate_xception(t_struct* txception); + void generate_service(t_service* tservice); + + void generate_property(ostream& out, t_field* tfield, bool isPublic, bool generateIsset); + void generate_netstd_property(ostream& out, t_field* tfield, bool isPublic, bool includeIsset = true, string fieldPrefix = ""); + bool print_const_value(ostream& out, string name, t_type* type, t_const_value* value, bool in_static, bool defval = false, bool needtype = false); + string render_const_value(ostream& out, string name, t_type* type, t_const_value* value); + void print_const_constructor(ostream& out, vector<t_const*> consts); + void print_const_def_value(ostream& out, string name, t_type* type, t_const_value* value); + void generate_netstd_struct(t_struct* tstruct, bool is_exception); + void generate_netstd_union(t_struct* tunion); + void generate_netstd_struct_definition(ostream& out, t_struct* tstruct, bool is_xception = false, bool in_class = false, bool is_result = false); + void generate_netstd_union_definition(ostream& out, t_struct* tunion); + void generate_netstd_union_class(ostream& out, t_struct* tunion, t_field* tfield); + void generate_netstd_wcffault(ostream& out, t_struct* tstruct); + void generate_netstd_struct_reader(ostream& out, t_struct* tstruct); + void generate_netstd_struct_result_writer(ostream& out, t_struct* tstruct); + void generate_netstd_struct_writer(ostream& out, t_struct* tstruct); + void generate_netstd_struct_tostring(ostream& out, t_struct* tstruct); + void generate_netstd_struct_equals(ostream& out, t_struct* tstruct); + void generate_netstd_struct_hashcode(ostream& out, t_struct* tstruct); + void generate_netstd_union_reader(ostream& out, t_struct* tunion); + void generate_function_helpers(ostream& out, t_function* tfunction); + void generate_service_interface(ostream& out, t_service* tservice); + void generate_service_helpers(ostream& out, t_service* tservice); + void generate_service_client(ostream& out, t_service* tservice); + void generate_service_server(ostream& out, t_service* tservice); + void generate_process_function_async(ostream& out, t_service* tservice, t_function* function); + void generate_deserialize_field(ostream& out, t_field* tfield, string prefix = "", bool is_propertyless = false); + void generate_deserialize_struct(ostream& out, t_struct* tstruct, string prefix = ""); + void generate_deserialize_container(ostream& out, t_type* ttype, string prefix = ""); + void generate_deserialize_set_element(ostream& out, t_set* tset, string prefix = ""); + void generate_deserialize_map_element(ostream& out, t_map* tmap, string prefix = ""); + void generate_deserialize_list_element(ostream& out, t_list* list, string prefix = ""); + void generate_serialize_field(ostream& out, t_field* tfield, string prefix = "", bool is_propertyless = false); + void generate_serialize_struct(ostream& out, t_struct* tstruct, string prefix = ""); + void generate_serialize_container(ostream& out, t_type* ttype, string prefix = ""); + void generate_serialize_map_element(ostream& out, t_map* tmap, string iter, string map); + void generate_serialize_set_element(ostream& out, t_set* tmap, string iter); + void generate_serialize_list_element(ostream& out, t_list* tlist, string iter); + void generate_netstd_doc(ostream& out, t_field* field); + void generate_netstd_doc(ostream& out, t_doc* tdoc); + void generate_netstd_doc(ostream& out, t_function* tdoc); + void generate_netstd_docstring_comment(ostream& out, string contents); + void docstring_comment(ostream& out, const string& comment_start, const string& line_prefix, const string& contents, const string& comment_end); + void start_netstd_namespace(ostream& out); + void end_netstd_namespace(ostream& out); + + string netstd_type_usings() const; + string netstd_thrift_usings() const; + + string type_name(t_type* ttype); + string base_type_name(t_base_type* tbase); + string declare_field(t_field* tfield, bool init = false, string prefix = ""); + string function_signature_async(t_function* tfunction, string prefix = ""); + string function_signature(t_function* tfunction, string prefix = ""); + string argument_list(t_struct* tstruct); + string type_to_enum(t_type* ttype); + string prop_name(t_field* tfield, bool suppress_mapping = false); + string get_enum_class_name(t_type* type); + +private: + string namespace_name_; + string namespace_dir_; + + bool nullable_; + bool union_; + bool hashcode_; + bool serialize_; + bool wcf_; + + string wcf_namespace_; + map<string, int> netstd_keywords; + vector<member_mapping_scope> member_mapping_scopes; + + void init_keywords(); + string normalize_name(string name); + string make_valid_csharp_identifier(string const& fromName); + void prepare_member_name_mapping(t_struct* tstruct); + void prepare_member_name_mapping(void* scope, const vector<t_field*>& members, const string& structname); + void cleanup_member_name_mapping(void* scope); + string get_mapped_member_name(string oldname); +}; diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_ocaml_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_ocaml_generator.cc new file mode 100644 index 000000000..a3781e8ee --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_ocaml_generator.cc @@ -0,0 +1,1762 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <string> +#include <fstream> +#include <iostream> +#include <vector> + +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sstream> +#include "thrift/platform.h" +#include "thrift/version.h" +#include "thrift/generate/t_oop_generator.h" + +using std::ios; +using std::map; +using std::ostream; +using std::ostringstream; +using std::string; +using std::stringstream; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes + +/** + * OCaml code generator. + * + */ +class t_ocaml_generator : public t_oop_generator { +public: + t_ocaml_generator(t_program* program, + const std::map<std::string, std::string>& parsed_options, + const std::string& option_string) + : t_oop_generator(program) { + (void)option_string; + std::map<std::string, std::string>::const_iterator iter; + + /* no options yet */ + for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + throw "unknown option ocaml:" + iter->first; + } + + out_dir_base_ = "gen-ocaml"; + } + + /** + * Init and close methods + */ + + void init_generator() override; + void close_generator() override; + + /** + * Program-level generation functions + */ + void generate_program() override; + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_const(t_const* tconst) override; + void generate_struct(t_struct* tstruct) override; + void generate_xception(t_struct* txception) override; + void generate_service(t_service* tservice) override; + + std::string render_const_value(t_type* type, t_const_value* value); + bool struct_member_persistent(t_field* tmember); + bool struct_member_omitable(t_field* tmember); + bool struct_member_default_cheaply_comparable(t_field* tmember); + std::string struct_member_copy_of(t_type* type, string what); + + /** + * Struct generation code + */ + + void generate_ocaml_struct(t_struct* tstruct, bool is_exception); + void generate_ocaml_struct_definition(std::ostream& out, + t_struct* tstruct, + bool is_xception = false); + void generate_ocaml_struct_member(std::ostream& out, string tname, t_field* tmember); + void generate_ocaml_struct_sig(std::ostream& out, t_struct* tstruct, bool is_exception); + void generate_ocaml_struct_reader(std::ostream& out, t_struct* tstruct); + void generate_ocaml_struct_writer(std::ostream& out, t_struct* tstruct); + void generate_ocaml_function_helpers(t_function* tfunction); + void generate_ocaml_method_copy(std::ostream& out, const vector<t_field*>& members); + void generate_ocaml_member_copy(std::ostream& out, t_field* member); + + /** + * Service-level generation functions + */ + + void generate_service_helpers(t_service* tservice); + void generate_service_interface(t_service* tservice); + void generate_service_client(t_service* tservice); + void generate_service_server(t_service* tservice); + void generate_process_function(t_service* tservice, t_function* tfunction); + + /** + * Serialization constructs + */ + + void generate_deserialize_field(std::ostream& out, t_field* tfield, std::string prefix); + + void generate_deserialize_struct(std::ostream& out, t_struct* tstruct); + + void generate_deserialize_container(std::ostream& out, t_type* ttype); + + void generate_deserialize_set_element(std::ostream& out, t_set* tset); + + void generate_deserialize_list_element(std::ostream& out, + t_list* tlist, + std::string prefix = ""); + void generate_deserialize_type(std::ostream& out, t_type* type); + + void generate_serialize_field(std::ostream& out, t_field* tfield, std::string name = ""); + + void generate_serialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); + + void generate_serialize_container(std::ostream& out, t_type* ttype, std::string prefix = ""); + + void generate_serialize_map_element(std::ostream& out, + t_map* tmap, + std::string kiter, + std::string viter); + + void generate_serialize_set_element(std::ostream& out, t_set* tmap, std::string iter); + + void generate_serialize_list_element(std::ostream& out, t_list* tlist, std::string iter); + + /** + * Helper rendering functions + */ + + std::string ocaml_autogen_comment(); + std::string ocaml_imports(); + std::string type_name(t_type* ttype); + std::string function_signature(t_function* tfunction, std::string prefix = ""); + std::string function_type(t_function* tfunc, bool method = false, bool options = false); + std::string argument_list(t_struct* tstruct); + std::string type_to_enum(t_type* ttype); + std::string render_ocaml_type(t_type* type); + +private: + /** + * File streams + */ + + ofstream_with_content_based_conditional_update f_types_; + ofstream_with_content_based_conditional_update f_consts_; + ofstream_with_content_based_conditional_update f_service_; + + ofstream_with_content_based_conditional_update f_types_i_; + ofstream_with_content_based_conditional_update f_service_i_; +}; + +/* + * This is necessary because we want typedefs to appear later, + * after all the types have been declared. + */ +void t_ocaml_generator::generate_program() { + // Initialize the generator + init_generator(); + + // Generate enums + vector<t_enum*> enums = program_->get_enums(); + vector<t_enum*>::iterator en_iter; + for (en_iter = enums.begin(); en_iter != enums.end(); ++en_iter) { + generate_enum(*en_iter); + } + + // Generate structs + vector<t_struct*> structs = program_->get_structs(); + vector<t_struct*>::iterator st_iter; + for (st_iter = structs.begin(); st_iter != structs.end(); ++st_iter) { + generate_struct(*st_iter); + } + + // Generate xceptions + vector<t_struct*> xceptions = program_->get_xceptions(); + vector<t_struct*>::iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + generate_xception(*x_iter); + } + + // Generate typedefs + vector<t_typedef*> typedefs = program_->get_typedefs(); + vector<t_typedef*>::iterator td_iter; + for (td_iter = typedefs.begin(); td_iter != typedefs.end(); ++td_iter) { + generate_typedef(*td_iter); + } + + // Generate services + vector<t_service*> services = program_->get_services(); + vector<t_service*>::iterator sv_iter; + for (sv_iter = services.begin(); sv_iter != services.end(); ++sv_iter) { + service_name_ = get_service_name(*sv_iter); + generate_service(*sv_iter); + } + + // Generate constants + vector<t_const*> consts = program_->get_consts(); + generate_consts(consts); + + // Close the generator + close_generator(); +} + +/** + * Prepares for file generation by opening up the necessary file output + * streams. + * + * @param tprogram The program to generate + */ +void t_ocaml_generator::init_generator() { + // Make output directory + MKDIR(get_out_dir().c_str()); + + // Make output file + string f_types_name = get_out_dir() + program_name_ + "_types.ml"; + f_types_.open(f_types_name.c_str()); + string f_types_i_name = get_out_dir() + program_name_ + "_types.mli"; + f_types_i_.open(f_types_i_name.c_str()); + + string f_consts_name = get_out_dir() + program_name_ + "_consts.ml"; + f_consts_.open(f_consts_name.c_str()); + + // Print header + f_types_ << ocaml_autogen_comment() << endl << ocaml_imports() << endl; + f_types_i_ << ocaml_autogen_comment() << endl << ocaml_imports() << endl; + f_consts_ << ocaml_autogen_comment() << endl << ocaml_imports() << endl << "open " + << capitalize(program_name_) << "_types" << endl; +} + +/** + * Autogen'd comment + */ +string t_ocaml_generator::ocaml_autogen_comment() { + return std::string("(*\n") + " Autogenerated by Thrift Compiler (" + THRIFT_VERSION + ")\n" + "\n" + + " DO NOT EDIT UNLESS YOU ARE SURE YOU KNOW WHAT YOU ARE DOING\n" + "*)\n"; +} + +/** + * Prints standard thrift imports + */ +string t_ocaml_generator::ocaml_imports() { + return "open Thrift"; +} + +/** + * Closes the type files + */ +void t_ocaml_generator::close_generator() { + // Close types file + f_types_.close(); +} + +/** + * Generates a typedef. Ez. + * + * @param ttypedef The type definition + */ +void t_ocaml_generator::generate_typedef(t_typedef* ttypedef) { + f_types_ << indent() << "type " << decapitalize(ttypedef->get_symbolic()) << " = " + << render_ocaml_type(ttypedef->get_type()) << endl << endl; + f_types_i_ << indent() << "type " << decapitalize(ttypedef->get_symbolic()) << " = " + << render_ocaml_type(ttypedef->get_type()) << endl << endl; +} + +/** + * Generates code for an enumerated type. + * the values. + * + * @param tenum The enumeration + */ +void t_ocaml_generator::generate_enum(t_enum* tenum) { + indent(f_types_) << "module " << capitalize(tenum->get_name()) << " = " << endl << "struct" + << endl; + indent(f_types_i_) << "module " << capitalize(tenum->get_name()) << " : " << endl << "sig" + << endl; + indent_up(); + indent(f_types_) << "type t = " << endl; + indent(f_types_i_) << "type t = " << endl; + indent_up(); + vector<t_enum_value*> constants = tenum->get_constants(); + vector<t_enum_value*>::iterator c_iter; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + string name = capitalize((*c_iter)->get_name()); + indent(f_types_) << "| " << name << endl; + indent(f_types_i_) << "| " << name << endl; + } + indent_down(); + + indent(f_types_) << "let to_i = function" << endl; + indent(f_types_i_) << "val to_i : t -> Int32.t" << endl; + indent_up(); + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + int value = (*c_iter)->get_value(); + string name = capitalize((*c_iter)->get_name()); + indent(f_types_) << "| " << name << " -> " << value << "l" << endl; + } + indent_down(); + + indent(f_types_) << "let of_i = function" << endl; + indent(f_types_i_) << "val of_i : Int32.t -> t" << endl; + indent_up(); + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + int value = (*c_iter)->get_value(); + string name = capitalize((*c_iter)->get_name()); + indent(f_types_) << "| " << value << "l -> " << name << endl; + } + indent(f_types_) << "| _ -> raise Thrift_error" << endl; + indent_down(); + indent_down(); + indent(f_types_) << "end" << endl; + indent(f_types_i_) << "end" << endl; +} + +/** + * Generate a constant value + */ +void t_ocaml_generator::generate_const(t_const* tconst) { + t_type* type = tconst->get_type(); + string name = decapitalize(tconst->get_name()); + t_const_value* value = tconst->get_value(); + + indent(f_consts_) << "let " << name << " = " << render_const_value(type, value) << endl << endl; +} + +/** + * Prints the value of a constant with the given type. Note that type checking + * is NOT performed in this function as it is always run beforehand using the + * validate_types method in main.cc + */ +string t_ocaml_generator::render_const_value(t_type* type, t_const_value* value) { + type = get_true_type(type); + std::ostringstream out; + // OCaml requires all floating point numbers contain a decimal point + out.setf(ios::showpoint); + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + out << '"' << get_escaped_string(value) << '"'; + break; + case t_base_type::TYPE_BOOL: + out << (value->get_integer() > 0 ? "true" : "false"); + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + out << value->get_integer(); + break; + case t_base_type::TYPE_I32: + out << value->get_integer() << "l"; + break; + case t_base_type::TYPE_I64: + out << value->get_integer() << "L"; + break; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) { + out << value->get_integer() << ".0"; + } else { + out << value->get_double(); + } + break; + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + t_enum* tenum = (t_enum*)type; + vector<t_enum_value*> constants = tenum->get_constants(); + vector<t_enum_value*>::iterator c_iter; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + int val = (*c_iter)->get_value(); + if (val == value->get_integer()) { + indent(out) << capitalize(tenum->get_name()) << "." << capitalize((*c_iter)->get_name()); + break; + } + } + } else if (type->is_struct() || type->is_xception()) { + string cname = type_name(type); + string ct = tmp("_c"); + out << endl; + indent_up(); + indent(out) << "(let " << ct << " = new " << cname << " in" << endl; + indent_up(); + const vector<t_field*>& fields = ((t_struct*)type)->get_members(); + vector<t_field*>::const_iterator f_iter; + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + t_type* field_type = NULL; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field_type = (*f_iter)->get_type(); + } + } + if (field_type == NULL) { + throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string(); + } + string fname = v_iter->first->get_string(); + out << indent(); + out << ct << "#set_" << fname << " "; + out << render_const_value(field_type, v_iter->second); + out << ";" << endl; + } + indent(out) << ct << ")"; + indent_down(); + indent_down(); + } else if (type->is_map()) { + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + string hm = tmp("_hm"); + out << endl; + indent_up(); + indent(out) << "(let " << hm << " = Hashtbl.create " << val.size() << " in" << endl; + indent_up(); + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string key = render_const_value(ktype, v_iter->first); + string val = render_const_value(vtype, v_iter->second); + indent(out) << "Hashtbl.add " << hm << " " << key << " " << val << ";" << endl; + } + indent(out) << hm << ")"; + indent_down(); + indent_down(); + } else if (type->is_list()) { + t_type* etype; + etype = ((t_list*)type)->get_elem_type(); + out << "[" << endl; + indent_up(); + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + out << indent(); + out << render_const_value(etype, *v_iter); + out << ";" << endl; + } + indent_down(); + indent(out) << "]"; + } else if (type->is_set()) { + t_type* etype = ((t_set*)type)->get_elem_type(); + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + string hm = tmp("_hm"); + indent(out) << "(let " << hm << " = Hashtbl.create " << val.size() << " in" << endl; + indent_up(); + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string val = render_const_value(etype, *v_iter); + indent(out) << "Hashtbl.add " << hm << " " << val << " true;" << endl; + } + indent(out) << hm << ")" << endl; + indent_down(); + out << endl; + } else { + throw "CANNOT GENERATE CONSTANT FOR TYPE: " + type->get_name(); + } + return out.str(); +} + +/** + * Generates a "struct" + */ +void t_ocaml_generator::generate_struct(t_struct* tstruct) { + generate_ocaml_struct(tstruct, false); +} + +/** + * Generates a struct definition for a thrift exception. Basically the same + * as a struct, but also has an exception declaration. + * + * @param txception The struct definition + */ +void t_ocaml_generator::generate_xception(t_struct* txception) { + generate_ocaml_struct(txception, true); +} + +/** + * Generates an OCaml struct + */ +void t_ocaml_generator::generate_ocaml_struct(t_struct* tstruct, bool is_exception) { + generate_ocaml_struct_definition(f_types_, tstruct, is_exception); + generate_ocaml_struct_sig(f_types_i_, tstruct, is_exception); +} + +void t_ocaml_generator::generate_ocaml_method_copy(ostream& out, const vector<t_field*>& members) { + vector<t_field*>::const_iterator m_iter; + + /* Create a copy of the current object */ + indent(out) << "method copy =" << endl; + indent_up(); + indent_up(); + indent(out) << "let _new = Oo.copy self in" << endl; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) + generate_ocaml_member_copy(out, *m_iter); + + indent_down(); + indent(out) << "_new" << endl; + indent_down(); +} + +string t_ocaml_generator::struct_member_copy_of(t_type* type, string what) { + if (type->is_struct() || type->is_xception()) { + return what + string("#copy"); + } + if (type->is_map()) { + string copy_of_k = struct_member_copy_of(((t_map*)type)->get_key_type(), "k"); + string copy_of_v = struct_member_copy_of(((t_map*)type)->get_val_type(), "v"); + + if (copy_of_k == "k" && copy_of_v == "v") { + return string("(Hashtbl.copy ") + what + string(")"); + } else { + return string( + "((fun oh -> let nh = Hashtbl.create (Hashtbl.length oh) in Hashtbl.iter (fun k v " + "-> Hashtbl.add nh ") + copy_of_k + string(" ") + copy_of_v + string(") oh; nh) ") + + what + ")"; + } + } + if (type->is_set()) { + string copy_of = struct_member_copy_of(((t_set*)type)->get_elem_type(), "k"); + + if (copy_of == "k") { + return string("(Hashtbl.copy ") + what + string(")"); + } else { + return string( + "((fun oh -> let nh = Hashtbl.create (Hashtbl.length oh) in Hashtbl.iter (fun k v " + "-> Hashtbl.add nh ") + copy_of + string(" true") + string(") oh; nh) ") + what + + ")"; + } + } + if (type->is_list()) { + string copy_of = struct_member_copy_of(((t_list*)type)->get_elem_type(), "x"); + if (copy_of != "x") { + return string("(List.map (fun x -> ") + copy_of + string(") ") + what + string(")"); + } else { + return what; + } + } + return what; +} + +void t_ocaml_generator::generate_ocaml_member_copy(ostream& out, t_field* tmember) { + string mname = decapitalize(tmember->get_name()); + t_type* type = get_true_type(tmember->get_type()); + + string grab_field = string("self#grab_") + mname; + string copy_of = struct_member_copy_of(type, grab_field); + if (copy_of != grab_field) { + indent(out); + if (!struct_member_persistent(tmember)) { + out << "if _" << mname << " <> None then" << endl; + indent(out) << " "; + } + out << "_new#set_" << mname << " " << copy_of << ";" << endl; + } +} + +/** + * Generates a struct definition for a thrift data type. + * + * @param tstruct The struct definition + */ +void t_ocaml_generator::generate_ocaml_struct_definition(ostream& out, + t_struct* tstruct, + bool is_exception) { + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + string tname = type_name(tstruct); + indent(out) << "class " << tname << " =" << endl; + indent(out) << "object (self)" << endl; + + indent_up(); + + if (members.size() > 0) { + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + generate_ocaml_struct_member(out, tname, (*m_iter)); + out << endl; + } + } + generate_ocaml_method_copy(out, members); + generate_ocaml_struct_writer(out, tstruct); + indent_down(); + indent(out) << "end" << endl; + + if (is_exception) { + indent(out) << "exception " << capitalize(tname) << " of " << tname << endl; + } + + generate_ocaml_struct_reader(out, tstruct); +} + +/** + * Generates a structure member for a thrift data type. + * + * @param tname Name of the parent structure for the member + * @param tmember Member definition + */ +void t_ocaml_generator::generate_ocaml_struct_member(ostream& out, + string tname, + t_field* tmember) { + string x = tmp("_x"); + string mname = decapitalize(tmember->get_name()); + + indent(out) << "val mutable _" << mname << " : " << render_ocaml_type(tmember->get_type()); + t_const_value* val = tmember->get_value(); + if (val) { + if (struct_member_persistent(tmember)) + out << " = " << render_const_value(tmember->get_type(), tmember->get_value()) << endl; + else + out << " option = Some " << render_const_value(tmember->get_type(), tmember->get_value()) + << endl; + } else { + // assert(!struct_member_persistent(tmember)) + out << " option = None" << endl; + } + + if (struct_member_persistent(tmember)) { + indent(out) << "method get_" << mname << " = Some _" << mname << endl; + indent(out) << "method grab_" << mname << " = _" << mname << endl; + indent(out) << "method set_" << mname << " " << x << " = _" << mname << " <- " << x << endl; + } else { + indent(out) << "method get_" << mname << " = _" << mname << endl; + indent(out) << "method grab_" << mname << " = match _" << mname + << " with None->raise (Field_empty \"" << tname << "." << mname << "\") | Some " + << x << " -> " << x << endl; + indent(out) << "method set_" << mname << " " << x << " = _" << mname << " <- Some " << x + << endl; + indent(out) << "method unset_" << mname << " = _" << mname << " <- None" << endl; + } + + indent(out) << "method reset_" << mname << " = _" << mname << " <- "; + if (val) { + if (struct_member_persistent(tmember)) + out << render_const_value(tmember->get_type(), tmember->get_value()) << endl; + else + out << "Some " << render_const_value(tmember->get_type(), tmember->get_value()) << endl; + } else { + out << "None" << endl; + } +} + +/** + * Check whether a member of the structure can not have undefined value + * + * @param tmember Member definition + */ +bool t_ocaml_generator::struct_member_persistent(t_field* tmember) { + t_const_value* val = tmember->get_value(); + return (val ? true : false); +} + +/** + * Check whether a member of the structure can be skipped during encoding + * + * @param tmember Member definition + */ +bool t_ocaml_generator::struct_member_omitable(t_field* tmember) { + return (tmember->get_req() != t_field::T_REQUIRED); +} + +/** + * Figure out whether a member of the structure has + * a cheaply comparable default value. + * + * @param tmember Member definition + */ +bool t_ocaml_generator::struct_member_default_cheaply_comparable(t_field* tmember) { + t_type* type = get_true_type(tmember->get_type()); + t_const_value* val = tmember->get_value(); + if (!val) { + return false; + } else if (type->is_base_type()) { + // Base types are generally cheaply compared for structural equivalence. + switch (((t_base_type*)type)->get_base()) { + case t_base_type::TYPE_DOUBLE: + if (val->get_double() == 0.0) + return true; + else + return false; + default: + return true; + } + } else if (type->is_list()) { + // Empty lists are cheaply compared for structural equivalence. + // Is empty list? + if (val->get_list().size() == 0) + return true; + else + return false; + } else { + return false; + } +} + +/** + * Generates a struct definition for a thrift data type. + * + * @param tstruct The struct definition + */ +void t_ocaml_generator::generate_ocaml_struct_sig(ostream& out, + t_struct* tstruct, + bool is_exception) { + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + string tname = type_name(tstruct); + indent(out) << "class " << tname << " :" << endl; + indent(out) << "object ('a)" << endl; + + indent_up(); + + string x = tmp("_x"); + if (members.size() > 0) { + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + string mname = decapitalize((*m_iter)->get_name()); + string type = render_ocaml_type((*m_iter)->get_type()); + indent(out) << "method get_" << mname << " : " << type << " option" << endl; + indent(out) << "method grab_" << mname << " : " << type << endl; + indent(out) << "method set_" << mname << " : " << type << " -> unit" << endl; + if (!struct_member_persistent(*m_iter)) + indent(out) << "method unset_" << mname << " : unit" << endl; + indent(out) << "method reset_" << mname << " : unit" << endl; + } + } + indent(out) << "method copy : 'a" << endl; + indent(out) << "method write : Protocol.t -> unit" << endl; + indent_down(); + indent(out) << "end" << endl; + + if (is_exception) { + indent(out) << "exception " << capitalize(tname) << " of " << tname << endl; + } + + indent(out) << "val read_" << tname << " : Protocol.t -> " << tname << endl; +} + +/** + * Generates the read method for a struct + */ +void t_ocaml_generator::generate_ocaml_struct_reader(ostream& out, t_struct* tstruct) { + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + string sname = type_name(tstruct); + string str = tmp("_str"); + string t = tmp("_t"); + string id = tmp("_id"); + indent(out) << "let rec read_" << sname << " (iprot : Protocol.t) =" << endl; + indent_up(); + indent(out) << "let " << str << " = new " << sname << " in" << endl; + indent_up(); + indent(out) << "ignore(iprot#readStructBegin);" << endl; + + // Loop over reading in fields + indent(out) << "(try while true do" << endl; + indent_up(); + indent_up(); + + // Read beginning field marker + indent(out) << "let (_," << t << "," << id << ") = iprot#readFieldBegin in" << endl; + + // Check for field STOP marker and break + indent(out) << "if " << t << " = Protocol.T_STOP then" << endl; + indent_up(); + indent(out) << "raise Break" << endl; + indent_down(); + indent(out) << "else ();" << endl; + + indent(out) << "(match " << id << " with " << endl; + indent_up(); + // Generate deserialization code for known cases + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + indent(out) << "| " << (*f_iter)->get_key() << " -> ("; + out << "if " << t << " = " << type_to_enum((*f_iter)->get_type()) << " then" << endl; + indent_up(); + indent_up(); + generate_deserialize_field(out, *f_iter, str); + indent_down(); + out << indent() << "else" << endl << indent() << " iprot#skip " << t << ")" << endl; + indent_down(); + } + + // In the default case we skip the field + out << indent() << "| _ -> " + << "iprot#skip " << t << ");" << endl; + indent_down(); + // Read field end marker + indent(out) << "iprot#readFieldEnd;" << endl; + indent_down(); + indent(out) << "done; ()" << endl; + indent_down(); + indent(out) << "with Break -> ());" << endl; + + indent(out) << "iprot#readStructEnd;" << endl; + + indent(out) << str << endl << endl; + indent_down(); + indent_down(); +} + +void t_ocaml_generator::generate_ocaml_struct_writer(ostream& out, t_struct* tstruct) { + string name = tstruct->get_name(); + const vector<t_field*>& fields = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator f_iter; + string str = tmp("_str"); + string f = tmp("_f"); + + indent(out) << "method write (oprot : Protocol.t) =" << endl; + indent_up(); + indent(out) << "oprot#writeStructBegin \"" << name << "\";" << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* tmember = (*f_iter); + string mname = "_" + decapitalize(tmember->get_name()); + string _v; + + if (struct_member_persistent(tmember)) { + + if (struct_member_omitable(tmember) && struct_member_default_cheaply_comparable(tmember)) { + _v = "_v"; + // Avoid redundant encoding of members having default values. + indent(out) << "(match " << mname << " with " + << render_const_value(tmember->get_type(), tmember->get_value()) << " -> () | " + << _v << " -> " << endl; + } else { + _v = mname; + indent(out) << "(" << endl; + } + + } else { + + indent(out) << "(match " << mname << " with "; + + if (struct_member_omitable(tmember)) { + out << "None -> ()"; + + if (struct_member_default_cheaply_comparable(tmember)) { + // Avoid redundant encoding of members having default values. + out << " | Some " << render_const_value(tmember->get_type(), tmember->get_value()) + << " -> ()"; + } + out << " | Some _v -> " << endl; + } else { + out << endl; + indent(out) << "| None -> raise (Field_empty \"" << type_name(tstruct) << "." << mname + << "\")" << endl; + indent(out) << "| Some _v -> " << endl; + } + + _v = "_v"; + } + indent_up(); + // Write field header + indent(out) << "oprot#writeFieldBegin(\"" << tmember->get_name() << "\"," + << type_to_enum(tmember->get_type()) << "," << tmember->get_key() << ");" << endl; + + // Write field contents + generate_serialize_field(out, tmember, _v); + + // Write field closer + indent(out) << "oprot#writeFieldEnd" << endl; + + indent_down(); + indent(out) << ");" << endl; + } + + // Write the struct map + out << indent() << "oprot#writeFieldStop;" << endl << indent() << "oprot#writeStructEnd" << endl; + + indent_down(); +} + +/** + * Generates a thrift service. + * + * @param tservice The service definition + */ +void t_ocaml_generator::generate_service(t_service* tservice) { + string f_service_name = get_out_dir() + capitalize(service_name_) + ".ml"; + f_service_.open(f_service_name.c_str()); + string f_service_i_name = get_out_dir() + capitalize(service_name_) + ".mli"; + f_service_i_.open(f_service_i_name.c_str()); + + f_service_ << ocaml_autogen_comment() << endl << ocaml_imports() << endl; + f_service_i_ << ocaml_autogen_comment() << endl << ocaml_imports() << endl; + + /* if (tservice->get_extends() != NULL) { + f_service_ << + "open " << capitalize(tservice->get_extends()->get_name()) << endl; + f_service_i_ << + "open " << capitalize(tservice->get_extends()->get_name()) << endl; + } + */ + f_service_ << "open " << capitalize(program_name_) << "_types" << endl << endl; + + f_service_i_ << "open " << capitalize(program_name_) << "_types" << endl << endl; + + // Generate the three main parts of the service + generate_service_helpers(tservice); + generate_service_interface(tservice); + generate_service_client(tservice); + generate_service_server(tservice); + + // Close service file + f_service_.close(); + f_service_i_.close(); +} + +/** + * Generates helper functions for a service. + * + * @param tservice The service to generate a header definition for + */ +void t_ocaml_generator::generate_service_helpers(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + indent(f_service_) << "(* HELPER FUNCTIONS AND STRUCTURES *)" << endl << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* ts = (*f_iter)->get_arglist(); + generate_ocaml_struct_definition(f_service_, ts, false); + generate_ocaml_function_helpers(*f_iter); + } +} + +/** + * Generates a struct and helpers for a function. + * + * @param tfunction The function + */ +void t_ocaml_generator::generate_ocaml_function_helpers(t_function* tfunction) { + t_struct result(program_, decapitalize(tfunction->get_name()) + "_result"); + t_field success(tfunction->get_returntype(), "success", 0); + if (!tfunction->get_returntype()->is_void()) { + result.append(&success); + } + + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& fields = xs->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + result.append(*f_iter); + } + generate_ocaml_struct_definition(f_service_, &result, false); +} + +/** + * Generates a service interface definition. + * + * @param tservice The service to generate a header definition for + */ +void t_ocaml_generator::generate_service_interface(t_service* tservice) { + f_service_ << indent() << "class virtual iface =" << endl << "object (self)" << endl; + f_service_i_ << indent() << "class virtual iface :" << endl << "object" << endl; + + indent_up(); + + if (tservice->get_extends() != NULL) { + string extends = type_name(tservice->get_extends()); + indent(f_service_) << "inherit " << extends << ".iface" << endl; + indent(f_service_i_) << "inherit " << extends << ".iface" << endl; + } + + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + string ft = function_type(*f_iter, true, true); + f_service_ << indent() << "method virtual " << decapitalize((*f_iter)->get_name()) << " : " + << ft << endl; + f_service_i_ << indent() << "method virtual " << decapitalize((*f_iter)->get_name()) << " : " + << ft << endl; + } + indent_down(); + indent(f_service_) << "end" << endl << endl; + indent(f_service_i_) << "end" << endl << endl; +} + +/** + * Generates a service client definition. Note that in OCaml, the client doesn't implement iface. + *This is because + * The client does not (and should not have to) deal with arguments being None. + * + * @param tservice The service to generate a server for. + */ +void t_ocaml_generator::generate_service_client(t_service* tservice) { + string extends = ""; + indent(f_service_) << "class client (iprot : Protocol.t) (oprot : Protocol.t) =" << endl + << "object (self)" << endl; + indent(f_service_i_) << "class client : Protocol.t -> Protocol.t -> " << endl << "object" << endl; + indent_up(); + + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + indent(f_service_) << "inherit " << extends << ".client iprot oprot as super" << endl; + indent(f_service_i_) << "inherit " << extends << ".client" << endl; + } + indent(f_service_) << "val mutable seqid = 0" << endl; + + // Generate client method implementations + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* arg_struct = (*f_iter)->get_arglist(); + const vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator fld_iter; + string funname = (*f_iter)->get_name(); + + // Open function + indent(f_service_) << "method " << function_signature(*f_iter) << " = " << endl; + indent(f_service_i_) << "method " << decapitalize((*f_iter)->get_name()) << " : " + << function_type(*f_iter, true, false) << endl; + indent_up(); + indent(f_service_) << "self#send_" << funname; + + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + f_service_ << " " << decapitalize((*fld_iter)->get_name()); + } + f_service_ << ";" << endl; + + if (!(*f_iter)->is_oneway()) { + f_service_ << indent(); + f_service_ << "self#recv_" << funname << endl; + } + indent_down(); + + indent(f_service_) << "method private send_" << function_signature(*f_iter) << " = " << endl; + indent_up(); + + std::string argsname = decapitalize((*f_iter)->get_name() + "_args"); + + // Serialize the request header + f_service_ << indent() << "oprot#writeMessageBegin (\"" << (*f_iter)->get_name() << "\", " + << ((*f_iter)->is_oneway() ? "Protocol.ONEWAY" : "Protocol.CALL") << ", seqid);" + << endl; + + f_service_ << indent() << "let args = new " << argsname << " in" << endl; + indent_up(); + + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + f_service_ << indent() << "args#set_" << (*fld_iter)->get_name() << " " + << (*fld_iter)->get_name() << ";" << endl; + } + + // Write to the stream + f_service_ << indent() << "args#write oprot;" << endl << indent() << "oprot#writeMessageEnd;" + << endl << indent() << "oprot#getTransport#flush" << endl; + + indent_down(); + indent_down(); + + if (!(*f_iter)->is_oneway()) { + std::string resultname = decapitalize((*f_iter)->get_name() + "_result"); + t_struct noargs(program_); + + t_function recv_function((*f_iter)->get_returntype(), + string("recv_") + (*f_iter)->get_name(), + &noargs); + // Open function + f_service_ << indent() << "method private " << function_signature(&recv_function) << " =" + << endl; + indent_up(); + + // TODO(mcslee): Validate message reply here, seq ids etc. + + f_service_ << indent() << "let (fname, mtype, rseqid) = iprot#readMessageBegin in" << endl; + indent_up(); + f_service_ << indent() << "(if mtype = Protocol.EXCEPTION then" << endl << indent() + << " let x = Application_Exn.read iprot in" << endl; + indent_up(); + f_service_ << indent() << " (iprot#readMessageEnd;" << indent() + << " raise (Application_Exn.E x))" << endl; + indent_down(); + f_service_ << indent() << "else ());" << endl; + string res = "_"; + + t_struct* xs = (*f_iter)->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + + if (!(*f_iter)->get_returntype()->is_void() || xceptions.size() > 0) { + res = "result"; + } + f_service_ << indent() << "let " << res << " = read_" << resultname << " iprot in" << endl; + indent_up(); + f_service_ << indent() << "iprot#readMessageEnd;" << endl; + + // Careful, only return _result if not a void function + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_ << indent() << "match result#get_success with Some v -> v | None -> (" << endl; + indent_up(); + } + + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + f_service_ << indent() << "(match result#get_" << (*x_iter)->get_name() + << " with None -> () | Some _v ->" << endl; + indent(f_service_) << " raise (" << capitalize(type_name((*x_iter)->get_type())) + << " _v));" << endl; + } + + // Careful, only return _result if not a void function + if ((*f_iter)->get_returntype()->is_void()) { + indent(f_service_) << "()" << endl; + } else { + f_service_ + << indent() + << "raise (Application_Exn.E (Application_Exn.create Application_Exn.MISSING_RESULT \"" + << (*f_iter)->get_name() << " failed: unknown result\")))" << endl; + indent_down(); + } + + // Close function + indent_down(); + indent_down(); + indent_down(); + } + } + + indent_down(); + indent(f_service_) << "end" << endl << endl; + indent(f_service_i_) << "end" << endl << endl; +} + +/** + * Generates a service server definition. + * + * @param tservice The service to generate a server for. + */ +void t_ocaml_generator::generate_service_server(t_service* tservice) { + // Generate the dispatch methods + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + // Generate the header portion + indent(f_service_) << "class processor (handler : iface) =" << endl << indent() << "object (self)" + << endl; + indent(f_service_i_) << "class processor : iface ->" << endl << indent() << "object" << endl; + indent_up(); + + f_service_ << indent() << "inherit Processor.t" << endl << endl; + f_service_i_ << indent() << "inherit Processor.t" << endl << endl; + string extends = ""; + + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + indent(f_service_) << "inherit " + extends + ".processor (handler :> " + extends + ".iface)" + << endl; + indent(f_service_i_) << "inherit " + extends + ".processor" << endl; + } + + if (extends.empty()) { + indent(f_service_) << "val processMap = Hashtbl.create " << functions.size() << endl; + } + indent(f_service_i_) + << "val processMap : (string, int * Protocol.t * Protocol.t -> unit) Hashtbl.t" << endl; + + // Generate the server implementation + indent(f_service_) << "method process iprot oprot =" << endl; + indent(f_service_i_) << "method process : Protocol.t -> Protocol.t -> bool" << endl; + indent_up(); + + f_service_ << indent() << "let (name, typ, seqid) = iprot#readMessageBegin in" << endl; + indent_up(); + // TODO(mcslee): validate message + + // HOT: dictionary function lookup + f_service_ << indent() << "if Hashtbl.mem processMap name then" << endl << indent() + << " (Hashtbl.find processMap name) (seqid, iprot, oprot)" << endl << indent() + << "else (" << endl << indent() << " iprot#skip(Protocol.T_STRUCT);" << endl + << indent() << " iprot#readMessageEnd;" << endl << indent() + << " let x = Application_Exn.create Application_Exn.UNKNOWN_METHOD (\"Unknown " + "function \"^name) in" << endl << indent() + << " oprot#writeMessageBegin(name, Protocol.EXCEPTION, seqid);" << endl << indent() + << " x#write oprot;" << endl << indent() << " oprot#writeMessageEnd;" << endl + << indent() << " oprot#getTransport#flush" << endl << indent() << ");" << endl; + + // Read end of args field, the T_STOP, and the struct close + f_service_ << indent() << "true" << endl; + indent_down(); + indent_down(); + // Generate the process subfunctions + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_process_function(tservice, *f_iter); + } + + indent(f_service_) << "initializer" << endl; + indent_up(); + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + f_service_ << indent() << "Hashtbl.add processMap \"" << (*f_iter)->get_name() + << "\" self#process_" << (*f_iter)->get_name() << ";" << endl; + } + indent_down(); + + indent_down(); + indent(f_service_) << "end" << endl << endl; + indent(f_service_i_) << "end" << endl << endl; +} + +/** + * Generates a process function definition. + * + * @param tfunction The function to write a dispatcher for + */ +void t_ocaml_generator::generate_process_function(t_service* tservice, t_function* tfunction) { + (void)tservice; + // Open function + indent(f_service_) << "method private process_" << tfunction->get_name() + << " (seqid, iprot, oprot) =" << endl; + indent_up(); + + string argsname = decapitalize(tfunction->get_name()) + "_args"; + string resultname = decapitalize(tfunction->get_name()) + "_result"; + + // Generate the function call + t_struct* arg_struct = tfunction->get_arglist(); + const std::vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + + string args = "args"; + if (fields.size() == 0) { + args = "_"; + } + + f_service_ << indent() << "let " << args << " = read_" << argsname << " iprot in" << endl; + indent_up(); + f_service_ << indent() << "iprot#readMessageEnd;" << endl; + + t_struct* xs = tfunction->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + + // Declare result for non oneway function + if (!tfunction->is_oneway()) { + f_service_ << indent() << "let result = new " << resultname << " in" << endl; + indent_up(); + } + + // Try block for a function with exceptions + if (xceptions.size() > 0) { + f_service_ << indent() << "(try" << endl; + indent_up(); + } + + f_service_ << indent(); + if (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void()) { + f_service_ << "result#set_success "; + } + f_service_ << "(handler#" << tfunction->get_name(); + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + f_service_ << " args#get_" << (*f_iter)->get_name(); + } + f_service_ << ");" << endl; + + if (xceptions.size() > 0) { + indent_down(); + indent(f_service_) << "with" << endl; + indent_up(); + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + f_service_ << indent() << "| " << capitalize(type_name((*x_iter)->get_type())) << " " + << (*x_iter)->get_name() << " -> " << endl; + indent_up(); + indent_up(); + if (!tfunction->is_oneway()) { + f_service_ << indent() << "result#set_" << (*x_iter)->get_name() << " " + << (*x_iter)->get_name() << endl; + } else { + indent(f_service_) << "()"; + } + indent_down(); + indent_down(); + } + indent_down(); + f_service_ << indent() << ");" << endl; + } + + // Shortcut out here for oneway functions + if (tfunction->is_oneway()) { + f_service_ << indent() << "()" << endl; + indent_down(); + indent_down(); + return; + } + + f_service_ << indent() << "oprot#writeMessageBegin (\"" << tfunction->get_name() + << "\", Protocol.REPLY, seqid);" << endl << indent() << "result#write oprot;" << endl + << indent() << "oprot#writeMessageEnd;" << endl << indent() + << "oprot#getTransport#flush" << endl; + + // Close function + indent_down(); + indent_down(); + indent_down(); +} + +/** + * Deserializes a field of any type. + */ +void t_ocaml_generator::generate_deserialize_field(ostream& out, t_field* tfield, string prefix) { + t_type* type = tfield->get_type(); + + string name = decapitalize(tfield->get_name()); + indent(out) << prefix << "#set_" << name << " "; + generate_deserialize_type(out, type); + out << endl; +} + +/** + * Deserializes a field of any type. + */ +void t_ocaml_generator::generate_deserialize_type(ostream& out, t_type* type) { + type = get_true_type(type); + + if (type->is_void()) { + throw "CANNOT GENERATE DESERIALIZE CODE FOR void TYPE"; + } + + if (type->is_struct() || type->is_xception()) { + generate_deserialize_struct(out, (t_struct*)type); + } else if (type->is_container()) { + generate_deserialize_container(out, type); + } else if (type->is_base_type()) { + out << "iprot#"; + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct"; + break; + case t_base_type::TYPE_STRING: + out << "readString"; + break; + case t_base_type::TYPE_BOOL: + out << "readBool"; + break; + case t_base_type::TYPE_I8: + out << "readByte"; + break; + case t_base_type::TYPE_I16: + out << "readI16"; + break; + case t_base_type::TYPE_I32: + out << "readI32"; + break; + case t_base_type::TYPE_I64: + out << "readI64"; + break; + case t_base_type::TYPE_DOUBLE: + out << "readDouble"; + break; + default: + throw "compiler error: no ocaml name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + string ename = capitalize(type->get_name()); + out << "(" << ename << ".of_i iprot#readI32)"; + } else { + printf("DO NOT KNOW HOW TO DESERIALIZE TYPE '%s'\n", type->get_name().c_str()); + } +} + +/** + * Generates an unserializer for a struct, calling read() + */ +void t_ocaml_generator::generate_deserialize_struct(ostream& out, t_struct* tstruct) { + string prefix = ""; + t_program* program = tstruct->get_program(); + if (program != NULL && program != program_) { + prefix = capitalize(program->get_name()) + "_types."; + } + string name = decapitalize(tstruct->get_name()); + out << "(" << prefix << "read_" << name << " iprot)"; +} + +/** + * Serialize a container by writing out the header followed by + * data and then a footer. + */ +void t_ocaml_generator::generate_deserialize_container(ostream& out, t_type* ttype) { + string size = tmp("_size"); + string ktype = tmp("_ktype"); + string vtype = tmp("_vtype"); + string etype = tmp("_etype"); + string con = tmp("_con"); + + t_field fsize(g_type_i32, size); + t_field fktype(g_type_i8, ktype); + t_field fvtype(g_type_i8, vtype); + t_field fetype(g_type_i8, etype); + + out << endl; + indent_up(); + // Declare variables, read header + if (ttype->is_map()) { + indent(out) << "(let (" << ktype << "," << vtype << "," << size << ") = iprot#readMapBegin in" + << endl; + indent(out) << "let " << con << " = Hashtbl.create " << size << " in" << endl; + indent_up(); + indent(out) << "for i = 1 to " << size << " do" << endl; + indent_up(); + indent(out) << "let _k = "; + generate_deserialize_type(out, ((t_map*)ttype)->get_key_type()); + out << " in" << endl; + indent(out) << "let _v = "; + generate_deserialize_type(out, ((t_map*)ttype)->get_val_type()); + out << " in" << endl; + indent_up(); + indent(out) << "Hashtbl.add " << con << " _k _v" << endl; + indent_down(); + indent_down(); + indent(out) << "done; iprot#readMapEnd; " << con << ")"; + indent_down(); + } else if (ttype->is_set()) { + indent(out) << "(let (" << etype << "," << size << ") = iprot#readSetBegin in" << endl; + indent(out) << "let " << con << " = Hashtbl.create " << size << " in" << endl; + indent_up(); + indent(out) << "for i = 1 to " << size << " do" << endl; + indent_up(); + indent(out) << "Hashtbl.add " << con << " "; + generate_deserialize_type(out, ((t_set*)ttype)->get_elem_type()); + out << " true" << endl; + indent_down(); + indent(out) << "done; iprot#readSetEnd; " << con << ")"; + indent_down(); + } else if (ttype->is_list()) { + indent(out) << "(let (" << etype << "," << size << ") = iprot#readListBegin in" << endl; + indent_up(); + indent(out) << "let " << con << " = (Array.to_list (Array.init " << size << " (fun _ -> "; + generate_deserialize_type(out, ((t_list*)ttype)->get_elem_type()); + out << "))) in" << endl; + indent_up(); + indent(out) << "iprot#readListEnd; " << con << ")"; + indent_down(); + indent_down(); + } + indent_down(); +} + +/** + * Serializes a field of any type. + * + * @param tfield The field to serialize + * @param prefix Name to prepend to field name + */ +void t_ocaml_generator::generate_serialize_field(ostream& out, t_field* tfield, string name) { + t_type* type = get_true_type(tfield->get_type()); + + // Do nothing for void types + if (type->is_void()) { + throw "CANNOT GENERATE SERIALIZE CODE FOR void TYPE: " + tfield->get_name(); + } + + if (name.length() == 0) { + name = decapitalize(tfield->get_name()); + } + + if (type->is_struct() || type->is_xception()) { + generate_serialize_struct(out, (t_struct*)type, name); + } else if (type->is_container()) { + generate_serialize_container(out, type, name); + } else if (type->is_base_type() || type->is_enum()) { + + indent(out) << "oprot#"; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + out << "writeString(" << name << ")"; + break; + case t_base_type::TYPE_BOOL: + out << "writeBool(" << name << ")"; + break; + case t_base_type::TYPE_I8: + out << "writeByte(" << name << ")"; + break; + case t_base_type::TYPE_I16: + out << "writeI16(" << name << ")"; + break; + case t_base_type::TYPE_I32: + out << "writeI32(" << name << ")"; + break; + case t_base_type::TYPE_I64: + out << "writeI64(" << name << ")"; + break; + case t_base_type::TYPE_DOUBLE: + out << "writeDouble(" << name << ")"; + break; + default: + throw "compiler error: no ocaml name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + string ename = capitalize(type->get_name()); + out << "writeI32(" << ename << ".to_i " << name << ")"; + } + + } else { + printf("DO NOT KNOW HOW TO SERIALIZE FIELD '%s' TYPE '%s'\n", + tfield->get_name().c_str(), + type->get_name().c_str()); + } + out << ";" << endl; +} + +/** + * Serializes all the members of a struct. + * + * @param tstruct The struct to serialize + * @param prefix String prefix to attach to all fields + */ +void t_ocaml_generator::generate_serialize_struct(ostream& out, t_struct* tstruct, string prefix) { + (void)tstruct; + indent(out) << prefix << "#write(oprot)"; +} + +void t_ocaml_generator::generate_serialize_container(ostream& out, t_type* ttype, string prefix) { + if (ttype->is_map()) { + indent(out) << "oprot#writeMapBegin(" << type_to_enum(((t_map*)ttype)->get_key_type()) << ","; + out << type_to_enum(((t_map*)ttype)->get_val_type()) << ","; + out << "Hashtbl.length " << prefix << ");" << endl; + } else if (ttype->is_set()) { + indent(out) << "oprot#writeSetBegin(" << type_to_enum(((t_set*)ttype)->get_elem_type()) << ","; + out << "Hashtbl.length " << prefix << ");" << endl; + } else if (ttype->is_list()) { + indent(out) << "oprot#writeListBegin(" << type_to_enum(((t_list*)ttype)->get_elem_type()) + << ","; + out << "List.length " << prefix << ");" << endl; + } + + if (ttype->is_map()) { + string kiter = tmp("_kiter"); + string viter = tmp("_viter"); + indent(out) << "Hashtbl.iter (fun " << kiter << " -> fun " << viter << " -> " << endl; + indent_up(); + generate_serialize_map_element(out, (t_map*)ttype, kiter, viter); + indent_down(); + indent(out) << ") " << prefix << ";" << endl; + } else if (ttype->is_set()) { + string iter = tmp("_iter"); + indent(out) << "Hashtbl.iter (fun " << iter << " -> fun _ -> "; + indent_up(); + generate_serialize_set_element(out, (t_set*)ttype, iter); + indent_down(); + indent(out) << ") " << prefix << ";" << endl; + } else if (ttype->is_list()) { + string iter = tmp("_iter"); + indent(out) << "List.iter (fun " << iter << " -> "; + indent_up(); + generate_serialize_list_element(out, (t_list*)ttype, iter); + indent_down(); + indent(out) << ") " << prefix << ";" << endl; + } + + if (ttype->is_map()) { + indent(out) << "oprot#writeMapEnd"; + } else if (ttype->is_set()) { + indent(out) << "oprot#writeSetEnd"; + } else if (ttype->is_list()) { + indent(out) << "oprot#writeListEnd"; + } +} + +/** + * Serializes the members of a map. + * + */ +void t_ocaml_generator::generate_serialize_map_element(ostream& out, + t_map* tmap, + string kiter, + string viter) { + t_field kfield(tmap->get_key_type(), kiter); + generate_serialize_field(out, &kfield); + + t_field vfield(tmap->get_val_type(), viter); + generate_serialize_field(out, &vfield); +} + +/** + * Serializes the members of a set. + */ +void t_ocaml_generator::generate_serialize_set_element(ostream& out, t_set* tset, string iter) { + t_field efield(tset->get_elem_type(), iter); + generate_serialize_field(out, &efield); +} + +/** + * Serializes the members of a list. + */ +void t_ocaml_generator::generate_serialize_list_element(ostream& out, t_list* tlist, string iter) { + t_field efield(tlist->get_elem_type(), iter); + generate_serialize_field(out, &efield); +} + +/** + * Renders a function signature of the form 'name args' + * + * @param tfunction Function definition + * @return String of rendered function definition + */ +string t_ocaml_generator::function_signature(t_function* tfunction, string prefix) { + return prefix + decapitalize(tfunction->get_name()) + " " + + argument_list(tfunction->get_arglist()); +} + +string t_ocaml_generator::function_type(t_function* tfunc, bool method, bool options) { + string result = ""; + + const vector<t_field*>& fields = tfunc->get_arglist()->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + result += render_ocaml_type((*f_iter)->get_type()); + if (options) + result += " option"; + result += " -> "; + } + if (fields.empty() && !method) { + result += "unit -> "; + } + result += render_ocaml_type(tfunc->get_returntype()); + return result; +} + +/** + * Renders a field list + */ +string t_ocaml_generator::argument_list(t_struct* tstruct) { + string result = ""; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + result += " "; + } + result += (*f_iter)->get_name(); + } + return result; +} + +string t_ocaml_generator::type_name(t_type* ttype) { + string prefix = ""; + t_program* program = ttype->get_program(); + if (program != NULL && program != program_) { + if (!ttype->is_service()) { + prefix = capitalize(program->get_name()) + "_types."; + } + } + + string name = ttype->get_name(); + if (ttype->is_service()) { + name = capitalize(name); + } else { + name = decapitalize(name); + } + return prefix + name; +} + +/** + * Converts the parse type to a Protocol.t_type enum + */ +string t_ocaml_generator::type_to_enum(t_type* type) { + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + return "Protocol.T_VOID"; + case t_base_type::TYPE_STRING: + return "Protocol.T_STRING"; + case t_base_type::TYPE_BOOL: + return "Protocol.T_BOOL"; + case t_base_type::TYPE_I8: + return "Protocol.T_BYTE"; + case t_base_type::TYPE_I16: + return "Protocol.T_I16"; + case t_base_type::TYPE_I32: + return "Protocol.T_I32"; + case t_base_type::TYPE_I64: + return "Protocol.T_I64"; + case t_base_type::TYPE_DOUBLE: + return "Protocol.T_DOUBLE"; + } + } else if (type->is_enum()) { + return "Protocol.T_I32"; + } else if (type->is_struct() || type->is_xception()) { + return "Protocol.T_STRUCT"; + } else if (type->is_map()) { + return "Protocol.T_MAP"; + } else if (type->is_set()) { + return "Protocol.T_SET"; + } else if (type->is_list()) { + return "Protocol.T_LIST"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +/** + * Converts the parse type to an ocaml type + */ +string t_ocaml_generator::render_ocaml_type(t_type* type) { + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + return "unit"; + case t_base_type::TYPE_STRING: + return "string"; + case t_base_type::TYPE_BOOL: + return "bool"; + case t_base_type::TYPE_I8: + return "int"; + case t_base_type::TYPE_I16: + return "int"; + case t_base_type::TYPE_I32: + return "Int32.t"; + case t_base_type::TYPE_I64: + return "Int64.t"; + case t_base_type::TYPE_DOUBLE: + return "float"; + } + } else if (type->is_enum()) { + return capitalize(((t_enum*)type)->get_name()) + ".t"; + } else if (type->is_struct() || type->is_xception()) { + return type_name((t_struct*)type); + } else if (type->is_map()) { + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + return "(" + render_ocaml_type(ktype) + "," + render_ocaml_type(vtype) + ") Hashtbl.t"; + } else if (type->is_set()) { + t_type* etype = ((t_set*)type)->get_elem_type(); + return "(" + render_ocaml_type(etype) + ",bool) Hashtbl.t"; + } else if (type->is_list()) { + t_type* etype = ((t_list*)type)->get_elem_type(); + return render_ocaml_type(etype) + " list"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +THRIFT_REGISTER_GENERATOR(ocaml, "OCaml", "") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_oop_generator.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_oop_generator.h new file mode 100644 index 000000000..f8da54792 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_oop_generator.h @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef T_OOP_GENERATOR_H +#define T_OOP_GENERATOR_H + +#include <string> +#include <iostream> + +#include "thrift/common.h" +#include "thrift/generate/t_generator.h" + +#include <algorithm> + +/** + * Class with utility methods shared across common object oriented languages. + * Specifically, most of this stuff is for C++/Java. + * + */ +class t_oop_generator : public t_generator { +public: + t_oop_generator(t_program* program) : t_generator(program) {} + + /** + * Scoping, using curly braces! + */ + + void scope_up(std::ostream& out) { + indent(out) << "{" << std::endl; + indent_up(); + } + + void scope_down(std::ostream& out) { + indent_down(); + indent(out) << "}" << std::endl; + } + + std::string upcase_string(std::string original) { + std::transform(original.begin(), original.end(), original.begin(), (int (*)(int))toupper); + return original; + } + + virtual std::string get_enum_class_name(t_type* type) { + std::string package = ""; + t_program* program = type->get_program(); + if (program != NULL && program != program_) { + package = program->get_namespace("java") + "."; + } + return package + type->get_name(); + } + + virtual void generate_java_docstring_comment(std::ostream& out, std::string contents) { + generate_docstring_comment(out, "/**\n", " * ", contents, " */\n"); + } + + virtual void generate_java_doc(std::ostream& out, t_field* field) { + if (field->get_type()->is_enum()) { + std::string combined_message = field->get_doc() + "\n@see " + + get_enum_class_name(field->get_type()); + generate_java_docstring_comment(out, combined_message); + } else { + generate_java_doc(out, (t_doc*)field); + } + } + + /** + * Emits a JavaDoc comment if the provided object has a doc in Thrift + */ + virtual void generate_java_doc(std::ostream& out, t_doc* tdoc) { + if (tdoc->has_doc()) { + generate_java_docstring_comment(out, tdoc->get_doc()); + } + } + + /** + * Emits a JavaDoc comment if the provided function object has a doc in Thrift + */ + virtual void generate_java_doc(std::ostream& out, t_function* tfunction) { + if (tfunction->has_doc()) { + std::stringstream ss; + ss << tfunction->get_doc(); + const std::vector<t_field*>& fields = tfunction->get_arglist()->get_members(); + std::vector<t_field*>::const_iterator p_iter; + for (p_iter = fields.begin(); p_iter != fields.end(); ++p_iter) { + t_field* p = *p_iter; + ss << "\n@param " << p->get_name(); + if (p->has_doc()) { + ss << " " << p->get_doc(); + } + } + generate_docstring_comment(out, "/**\n", " * ", ss.str(), " */\n"); + } + } +}; + +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_perl_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_perl_generator.cc new file mode 100644 index 000000000..80729cbfd --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_perl_generator.cc @@ -0,0 +1,1683 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <string> +#include <fstream> +#include <iostream> +#include <vector> +#include <list> + +#include <stdlib.h> +#include <sys/stat.h> +#include <sstream> +#include "thrift/platform.h" +#include "thrift/version.h" +#include "thrift/generate/t_oop_generator.h" + +using std::map; +using std::ostream; +using std::ostringstream; +using std::string; +using std::stringstream; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes + +/** + * PERL code generator. + * + */ +class t_perl_generator : public t_oop_generator { +public: + t_perl_generator(t_program* program, + const std::map<std::string, std::string>& parsed_options, + const std::string& option_string) + : t_oop_generator(program), f_types_use_includes_emitted_(false) { + (void)option_string; + std::map<std::string, std::string>::const_iterator iter; + + /* no options yet */ + for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + throw "unknown option perl:" + iter->first; + } + + out_dir_base_ = "gen-perl"; + escape_['$'] = "\\$"; + escape_['@'] = "\\@"; + } + + /** + * Init and close methods + */ + + void init_generator() override; + void close_generator() override; + + /** + * Program-level generation functions + */ + + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_const(t_const* tconst) override; + void generate_struct(t_struct* tstruct) override; + void generate_xception(t_struct* txception) override; + void generate_service(t_service* tservice) override; + + std::string render_const_value(t_type* type, t_const_value* value); + + /** + * Structs! + */ + + void generate_perl_struct(t_struct* tstruct, bool is_exception); + void generate_perl_struct_definition(std::ostream& out, + t_struct* tstruct, + bool is_xception = false); + void generate_perl_struct_reader(std::ostream& out, t_struct* tstruct); + void generate_perl_struct_writer(std::ostream& out, t_struct* tstruct); + void generate_perl_function_helpers(t_function* tfunction); + + /** + * Service-level generation functions + */ + + void generate_service_helpers(t_service* tservice); + void generate_service_interface(t_service* tservice); + void generate_service_rest(t_service* tservice); + void generate_service_client(t_service* tservice); + void generate_service_processor(t_service* tservice); + void generate_process_function(t_service* tservice, t_function* tfunction); + void generate_use_includes(std::ostream& os, bool& done, t_type *type, bool selfish); + + /** + * Serialization constructs + */ + + void generate_deserialize_field(std::ostream& out, + t_field* tfield, + std::string prefix = "", + bool inclass = false); + + void generate_deserialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); + + void generate_deserialize_container(std::ostream& out, t_type* ttype, std::string prefix = ""); + + void generate_deserialize_set_element(std::ostream& out, t_set* tset, std::string prefix = ""); + + void generate_deserialize_map_element(std::ostream& out, t_map* tmap, std::string prefix = ""); + + void generate_deserialize_list_element(std::ostream& out, + t_list* tlist, + std::string prefix = ""); + + void generate_serialize_field(std::ostream& out, t_field* tfield, std::string prefix = ""); + + void generate_serialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); + + void generate_serialize_container(std::ostream& out, t_type* ttype, std::string prefix = ""); + + void generate_serialize_map_element(std::ostream& out, + t_map* tmap, + std::string kiter, + std::string viter); + + void generate_serialize_set_element(std::ostream& out, t_set* tmap, std::string iter); + + void generate_serialize_list_element(std::ostream& out, t_list* tlist, std::string iter); + + /** + * Helper rendering functions + */ + + std::string perl_includes(); + std::string declare_field(t_field* tfield, bool init = false, bool obj = false); + std::string function_signature(t_function* tfunction, std::string prefix = ""); + std::string argument_list(t_struct* tstruct); + std::string type_to_enum(t_type* ttype); + + std::string autogen_comment() override { + return std::string("#\n") + "# Autogenerated by Thrift Compiler (" + THRIFT_VERSION + ")\n" + + "#\n" + "# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n" + "#\n"; + } + + void perl_namespace_dirs(t_program* p, std::list<std::string>& dirs) { + std::string ns = p->get_namespace("perl"); + std::string::size_type loc; + + if (ns.size() > 0) { + while ((loc = ns.find(".")) != std::string::npos) { + dirs.push_back(ns.substr(0, loc)); + ns = ns.substr(loc + 1); + } + } + + if (ns.size() > 0) { + dirs.push_back(ns); + } + } + + std::string perl_namespace(t_program* p) { + std::string ns = p->get_namespace("perl"); + std::string result = ""; + std::string::size_type loc; + + if (ns.size() > 0) { + while ((loc = ns.find(".")) != std::string::npos) { + result += ns.substr(0, loc); + result += "::"; + ns = ns.substr(loc + 1); + } + + if (ns.size() > 0) { + result += ns + "::"; + } + } + + return result; + } + + std::string get_namespace_out_dir() { + std::string outdir = get_out_dir(); + std::list<std::string> dirs; + perl_namespace_dirs(program_, dirs); + std::list<std::string>::iterator it; + for (it = dirs.begin(); it != dirs.end(); it++) { + outdir += *it + "/"; + } + return outdir; + } + +private: + /** + * File streams + */ + ofstream_with_content_based_conditional_update f_types_; + ofstream_with_content_based_conditional_update f_consts_; + ofstream_with_content_based_conditional_update f_helpers_; + ofstream_with_content_based_conditional_update f_service_; + + bool f_types_use_includes_emitted_; +}; + +/** + * Prepares for file generation by opening up the necessary file output + * streams. + * + * @param tprogram The program to generate + */ +void t_perl_generator::init_generator() { + // Make output directory + MKDIR(get_out_dir().c_str()); + + string outdir = get_out_dir(); + std::list<std::string> dirs; + perl_namespace_dirs(program_, dirs); + std::list<std::string>::iterator it; + for (it = dirs.begin(); it != dirs.end(); it++) { + outdir += *it + "/"; + MKDIR(outdir.c_str()); + } + + // Make output file + string f_types_name = outdir + "Types.pm"; + f_types_.open(f_types_name.c_str()); + string f_consts_name = outdir + "Constants.pm"; + f_consts_.open(f_consts_name.c_str()); + + // Print header + f_types_ << autogen_comment() << perl_includes(); + + // Print header + f_consts_ << autogen_comment() << "package " << perl_namespace(program_) << "Constants;" << endl + << perl_includes() << endl; +} + +/** + * Prints standard java imports + */ +string t_perl_generator::perl_includes() { + string inc; + + inc = "use 5.10.0;\n"; + inc += "use strict;\n"; + inc += "use warnings;\n"; + inc += "use Thrift::Exception;\n"; + inc += "use Thrift::MessageType;\n"; + inc += "use Thrift::Type;\n\n"; + + return inc; +} + +/** + * Close up (or down) some filez. + */ +void t_perl_generator::close_generator() { + // Close types file + f_types_ << "1;" << endl; + f_types_.close(); + + f_consts_ << "1;" << endl; + f_consts_.close(); +} + +/** + * Generates a typedef. This is not done in PERL, types are all implicit. + * + * @param ttypedef The type definition + */ +void t_perl_generator::generate_typedef(t_typedef* ttypedef) { + (void)ttypedef; +} + +/** + * Generates code for an enumerated type. Since define is expensive to lookup + * in PERL, we use a global array for this. + * + * @param tenum The enumeration + */ +void t_perl_generator::generate_enum(t_enum* tenum) { + f_types_ << "package " << perl_namespace(program_) << tenum->get_name() << ";" << endl; + + vector<t_enum_value*> constants = tenum->get_constants(); + vector<t_enum_value*>::iterator c_iter; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + int value = (*c_iter)->get_value(); + f_types_ << "use constant " << (*c_iter)->get_name() << " => " << value << ";" << endl; + } +} + +/** + * Generate a constant value + */ +void t_perl_generator::generate_const(t_const* tconst) { + t_type* type = tconst->get_type(); + string name = tconst->get_name(); + t_const_value* value = tconst->get_value(); + + f_consts_ << "use constant " << name << " => "; + f_consts_ << render_const_value(type, value); + f_consts_ << ";" << endl << endl; +} + +/** + * Prints the value of a constant with the given type. Note that type checking + * is NOT performed in this function as it is always run beforehand using the + * validate_types method in main.cc + */ +string t_perl_generator::render_const_value(t_type* type, t_const_value* value) { + std::ostringstream out; + + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + out << '"' << get_escaped_string(value) << '"'; + break; + case t_base_type::TYPE_BOOL: + out << (value->get_integer() > 0 ? "1" : "0"); + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + out << value->get_integer(); + break; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) { + out << value->get_integer(); + } else { + out << value->get_double(); + } + break; + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << value->get_integer(); + } else if (type->is_struct() || type->is_xception()) { + out << perl_namespace(type->get_program()) << type->get_name() << "->new({" << endl; + indent_up(); + + const vector<t_field*>& fields = ((t_struct*)type)->get_members(); + vector<t_field*>::const_iterator f_iter; + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + t_type* field_type = NULL; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field_type = (*f_iter)->get_type(); + } + } + if (field_type == NULL) { + throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string(); + } + indent(out) << render_const_value(g_type_string, v_iter->first); + out << " => "; + out << render_const_value(field_type, v_iter->second); + out << ","; + out << endl; + } + indent_down(); + indent(out) << "})"; + } else if (type->is_map()) { + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + out << "{" << endl; + indent_up(); + + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + indent(out) << render_const_value(ktype, v_iter->first); + out << " => "; + out << render_const_value(vtype, v_iter->second); + out << "," << endl; + } + indent_down(); + indent(out) << "}"; + } else if (type->is_list() || type->is_set()) { + t_type* etype; + if (type->is_list()) { + etype = ((t_list*)type)->get_elem_type(); + } else { + etype = ((t_set*)type)->get_elem_type(); + } + out << "[" << endl; + indent_up(); + + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + + indent(out) << render_const_value(etype, *v_iter); + if (type->is_set()) { + out << " => 1"; + } + out << "," << endl; + } + indent_down(); + indent(out) << "]"; + } + return out.str(); +} + +/** + * Make a struct + */ +void t_perl_generator::generate_struct(t_struct* tstruct) { + generate_perl_struct(tstruct, false); +} + +/** + * Generates a struct definition for a thrift exception. Basically the same + * as a struct but extends the Exception class. + * + * @param txception The struct definition + */ +void t_perl_generator::generate_xception(t_struct* txception) { + generate_perl_struct(txception, true); +} + +/** + * Structs can be normal or exceptions. + */ +void t_perl_generator::generate_perl_struct(t_struct* tstruct, bool is_exception) { + generate_use_includes(f_types_, f_types_use_includes_emitted_, tstruct, false); + generate_perl_struct_definition(f_types_, tstruct, is_exception); +} + +/** + * Generates a struct definition for a thrift data type. This is nothing in PERL + * where the objects are all just associative arrays (unless of course we + * decide to start using objects for them...) + * + * @param tstruct The struct definition + */ +void t_perl_generator::generate_perl_struct_definition(ostream& out, + t_struct* tstruct, + bool is_exception) { + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + out << "package " << perl_namespace(tstruct->get_program()) << tstruct->get_name() << ";\n"; + if (is_exception) { + out << "use base qw(Thrift::TException);\n"; + } + + // Create simple acessor methods + out << "use base qw(Class::Accessor);\n"; + + if (members.size() > 0) { + out << perl_namespace(tstruct->get_program()) << tstruct->get_name() << "->mk_accessors( qw( "; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* t = get_true_type((*m_iter)->get_type()); + if (!t->is_xception()) { + out << (*m_iter)->get_name() << " "; + } + } + + out << ") );\n"; + } + + out << endl; + + // new() + indent_up(); + out << "sub new {" << endl << indent() << "my $classname = shift;" << endl << indent() + << "my $self = {};" << endl << indent() << "my $vals = shift || {};" << endl; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + string dval = "undef"; + t_type* t = get_true_type((*m_iter)->get_type()); + if ((*m_iter)->get_value() != NULL && !(t->is_struct() || t->is_xception())) { + dval = render_const_value((*m_iter)->get_type(), (*m_iter)->get_value()); + } + out << indent() << "$self->{" << (*m_iter)->get_name() << "} = " << dval << ";" << endl; + } + + // Generate constructor from array + if (members.size() > 0) { + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* t = get_true_type((*m_iter)->get_type()); + if ((*m_iter)->get_value() != NULL && (t->is_struct() || t->is_xception())) { + indent(out) << "$self->{" << (*m_iter)->get_name() + << "} = " << render_const_value(t, (*m_iter)->get_value()) << ";" << endl; + } + } + + out << indent() << "if (UNIVERSAL::isa($vals,'HASH')) {" << endl; + indent_up(); + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + out << indent() << "if (defined $vals->{" << (*m_iter)->get_name() << "}) {" << endl + << indent() << " $self->{" << (*m_iter)->get_name() << "} = $vals->{" + << (*m_iter)->get_name() << "};" << endl << indent() << "}" << endl; + } + indent_down(); + out << indent() << "}" << endl; + } + + out << indent() << "return bless ($self, $classname);" << endl; + indent_down(); + out << "}\n\n"; + + out << "sub getName {" << endl << indent() << " return '" << tstruct->get_name() << "';" << endl + << indent() << "}" << endl << endl; + + generate_perl_struct_reader(out, tstruct); + generate_perl_struct_writer(out, tstruct); +} + +/** + * Generates the read() method for a struct + */ +void t_perl_generator::generate_perl_struct_reader(ostream& out, t_struct* tstruct) { + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + out << "sub read {" << endl; + + indent_up(); + + out << indent() << "my ($self, $input) = @_;" << endl << indent() << "my $xfer = 0;" << endl + << indent() << "my $fname;" << endl << indent() << "my $ftype = 0;" << endl << indent() + << "my $fid = 0;" << endl; + + indent(out) << "$xfer += $input->readStructBegin(\\$fname);" << endl; + + // Loop over reading in fields + indent(out) << "while (1)" << endl; + + scope_up(out); + + indent(out) << "$xfer += $input->readFieldBegin(\\$fname, \\$ftype, \\$fid);" << endl; + + // Check for field STOP marker and break + indent(out) << "if ($ftype == Thrift::TType::STOP) {" << endl; + indent_up(); + indent(out) << "last;" << endl; + indent_down(); + indent(out) << "}" << endl; + + // Switch statement on the field we are reading + indent(out) << "SWITCH: for($fid)" << endl; + + scope_up(out); + + // Generate deserialization code for known cases + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + + indent(out) << "/^" << (*f_iter)->get_key() << "$/ && do{"; + indent(out) << "if ($ftype == " << type_to_enum((*f_iter)->get_type()) << ") {" << endl; + + indent_up(); + generate_deserialize_field(out, *f_iter, "self->"); + indent_down(); + + indent(out) << "} else {" << endl; + + indent(out) << " $xfer += $input->skip($ftype);" << endl; + + out << indent() << "}" << endl << indent() << "last; };" << endl; + } + // In the default case we skip the field + + indent(out) << " $xfer += $input->skip($ftype);" << endl; + + scope_down(out); + + indent(out) << "$xfer += $input->readFieldEnd();" << endl; + + scope_down(out); + + indent(out) << "$xfer += $input->readStructEnd();" << endl; + + indent(out) << "return $xfer;" << endl; + + indent_down(); + out << indent() << "}" << endl << endl; +} + +/** + * Generates the write() method for a struct + */ +void t_perl_generator::generate_perl_struct_writer(ostream& out, t_struct* tstruct) { + string name = tstruct->get_name(); + const vector<t_field*>& fields = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator f_iter; + + out << "sub write {" << endl; + + indent_up(); + indent(out) << "my ($self, $output) = @_;" << endl; + indent(out) << "my $xfer = 0;" << endl; + + indent(out) << "$xfer += $output->writeStructBegin('" << name << "');" << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + out << indent() << "if (defined $self->{" << (*f_iter)->get_name() << "}) {" << endl; + indent_up(); + + indent(out) << "$xfer += $output->writeFieldBegin(" + << "'" << (*f_iter)->get_name() << "', " << type_to_enum((*f_iter)->get_type()) + << ", " << (*f_iter)->get_key() << ");" << endl; + + // Write field contents + generate_serialize_field(out, *f_iter, "self->"); + + indent(out) << "$xfer += $output->writeFieldEnd();" << endl; + + indent_down(); + indent(out) << "}" << endl; + } + + out << indent() << "$xfer += $output->writeFieldStop();" << endl << indent() + << "$xfer += $output->writeStructEnd();" << endl; + + out << indent() << "return $xfer;" << endl; + + indent_down(); + out << indent() << "}" << endl << endl; +} + +/** + * Generates use clauses for included entities + * + * @param os The output stream + * @param done A flag reference to debounce the action + * @param type The type being processed + * @param selfish Flag to indicate if the current namespace types should be "use"d as well. + */ +void t_perl_generator::generate_use_includes(std::ostream& os, bool& done, t_type *type, bool selfish) { + t_program *current = type->get_program(); + if (current && !done) { + std::vector<t_program*>& currInc = current->get_includes(); + std::vector<t_program*>::size_type numInc = currInc.size(); + if (selfish) { + os << "use " << perl_namespace(current) << "Types;" << endl; + } + for (std::vector<t_program*>::size_type i = 0; i < numInc; ++i) { + t_program* incProgram = currInc.at(i); + os << "use " << perl_namespace(incProgram) << "Types;" << endl; + } + os << endl; + done = true; + } +} + +/** + * Generates a thrift service. + * + * @param tservice The service definition + */ +void t_perl_generator::generate_service(t_service* tservice) { + string f_service_name = get_namespace_out_dir() + service_name_ + ".pm"; + f_service_.open(f_service_name.c_str()); + + f_service_ << autogen_comment() << perl_includes(); + + bool done = false; + generate_use_includes(f_service_, done, tservice, true); + + t_service* extends_s = tservice->get_extends(); + if (extends_s != NULL) { + f_service_ << "use " << perl_namespace(extends_s->get_program()) << extends_s->get_name() << ";" + << endl; + } + + f_service_ << endl; + + // Generate the three main parts of the service (well, two for now in PERL) + generate_service_helpers(tservice); + generate_service_interface(tservice); + generate_service_rest(tservice); + generate_service_client(tservice); + generate_service_processor(tservice); + + // Close service file + f_service_ << "1;" << endl; + f_service_.close(); +} + +/** + * Generates a service server definition. + * + * @param tservice The service to generate a server for. + */ +void t_perl_generator::generate_service_processor(t_service* tservice) { + // Generate the dispatch methods + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + string extends = ""; + string extends_processor = ""; + t_service* extends_s = tservice->get_extends(); + if (extends_s != NULL) { + extends = perl_namespace(extends_s->get_program()) + extends_s->get_name(); + extends_processor = "use base qw(" + extends + "Processor);"; + } + + indent_up(); + + // Generate the header portion + f_service_ << "package " << perl_namespace(program_) << service_name_ << "Processor;" << endl + << endl << "use strict;" << endl << extends_processor << endl << endl; + + if (extends.empty()) { + f_service_ << "sub new {" << endl; + + indent_up(); + + f_service_ << indent() << "my ($classname, $handler) = @_;" << endl << indent() + << "my $self = {};" << endl; + + f_service_ << indent() << "$self->{handler} = $handler;" << endl; + + f_service_ << indent() << "return bless ($self, $classname);" << endl; + + indent_down(); + + f_service_ << "}" << endl << endl; + } + + // Generate the server implementation + f_service_ << "sub process {" << endl; + indent_up(); + + f_service_ << indent() << "my ($self, $input, $output) = @_;" << endl; + + f_service_ << indent() << "my $rseqid = 0;" << endl << indent() << "my $fname = undef;" << endl + << indent() << "my $mtype = 0;" << endl << endl; + + f_service_ << indent() << "$input->readMessageBegin(\\$fname, \\$mtype, \\$rseqid);" << endl; + + // HOT: check for method implementation + f_service_ << indent() << "my $methodname = 'process_'.$fname;" << endl << indent() + << "if (!$self->can($methodname)) {" << endl; + indent_up(); + + f_service_ << indent() << "$input->skip(Thrift::TType::STRUCT);" << endl << indent() + << "$input->readMessageEnd();" << endl << indent() + << "my $x = Thrift::TApplicationException->new('Function '.$fname.' not implemented.', " + "Thrift::TApplicationException::UNKNOWN_METHOD);" << endl << indent() + << "$output->writeMessageBegin($fname, Thrift::TMessageType::EXCEPTION, $rseqid);" << endl + << indent() << "$x->write($output);" << endl << indent() + << "$output->writeMessageEnd();" << endl << indent() + << "$output->getTransport()->flush();" << endl << indent() << "return;" << endl; + + indent_down(); + f_service_ << indent() << "}" << endl << indent() + << "$self->$methodname($rseqid, $input, $output);" << endl << indent() << "return 1;" + << endl; + + indent_down(); + + f_service_ << "}" << endl << endl; + + // Generate the process subfunctions + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_process_function(tservice, *f_iter); + } +} + +/** + * Generates a process function definition. + * + * @param tfunction The function to write a dispatcher for + */ +void t_perl_generator::generate_process_function(t_service* tservice, t_function* tfunction) { + // Open function + f_service_ << "sub process_" << tfunction->get_name() << " {" << endl; + + indent_up(); + + f_service_ << indent() << "my ($self, $seqid, $input, $output) = @_;" << endl; + + string argsname = perl_namespace(tservice->get_program()) + service_name_ + "_" + + tfunction->get_name() + "_args"; + string resultname = perl_namespace(tservice->get_program()) + service_name_ + "_" + + tfunction->get_name() + "_result"; + + f_service_ << indent() << "my $args = " << argsname << "->new();" << endl << indent() + << "$args->read($input);" << endl; + + f_service_ << indent() << "$input->readMessageEnd();" << endl; + + t_struct* xs = tfunction->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + + // Declare result for non oneway function + if (!tfunction->is_oneway()) { + f_service_ << indent() << "my $result = " << resultname << "->new();" << endl; + } + + // Try block for a function with exceptions + if (xceptions.size() > 0) { + f_service_ << indent() << "eval {" << endl; + indent_up(); + } + + // Generate the function call + t_struct* arg_struct = tfunction->get_arglist(); + const std::vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + + f_service_ << indent(); + if (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void()) { + f_service_ << "$result->{success} = "; + } + f_service_ << "$self->{handler}->" << tfunction->get_name() << "("; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << "$args->" << (*f_iter)->get_name(); + } + f_service_ << ");" << endl; + + if (!tfunction->is_oneway() && xceptions.size() > 0) { + indent_down(); + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + f_service_ << indent() << "}; if( UNIVERSAL::isa($@,'" + << perl_namespace((*x_iter)->get_type()->get_program()) + << (*x_iter)->get_type()->get_name() << "') ){ " << endl; + + indent_up(); + f_service_ << indent() << "$result->{" << (*x_iter)->get_name() << "} = $@;" << endl; + f_service_ << indent() << "$@ = undef;" << endl; + indent_down(); + f_service_ << indent(); + } + f_service_ << "}" << endl; + + // catch-all for unexpected exceptions (THRIFT-3191) + f_service_ << indent() << "if ($@) {" << endl; + indent_up(); + f_service_ << indent() << "$@ =~ s/^\\s+|\\s+$//g;" << endl + << indent() << "my $err = Thrift::TApplicationException->new(\"Unexpected Exception: \" . $@, Thrift::TApplicationException::INTERNAL_ERROR);" << endl + << indent() << "$output->writeMessageBegin('" << tfunction->get_name() << "', Thrift::TMessageType::EXCEPTION, $seqid);" << endl + << indent() << "$err->write($output);" << endl + << indent() << "$output->writeMessageEnd();" << endl + << indent() << "$output->getTransport()->flush();" << endl + << indent() << "$@ = undef;" << endl + << indent() << "return;" << endl; + indent_down(); + f_service_ << indent() << "}" << endl; + } + + // Shortcut out here for oneway functions + if (tfunction->is_oneway()) { + f_service_ << indent() << "return;" << endl; + indent_down(); + f_service_ << "}" << endl; + return; + } + + // Serialize the reply + f_service_ << indent() << "$output->writeMessageBegin('" << tfunction->get_name() << "', Thrift::TMessageType::REPLY, $seqid);" << endl + << indent() << "$result->write($output);" << endl + << indent() << "$output->writeMessageEnd();" << endl + << indent() << "$output->getTransport()->flush();" << endl; + + // Close function + indent_down(); + f_service_ << "}" << endl << endl; +} + +/** + * Generates helper functions for a service. + * + * @param tservice The service to generate a header definition for + */ +void t_perl_generator::generate_service_helpers(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + f_service_ << "# HELPER FUNCTIONS AND STRUCTURES" << endl << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* ts = (*f_iter)->get_arglist(); + string name = ts->get_name(); + ts->set_name(service_name_ + "_" + name); + generate_perl_struct_definition(f_service_, ts, false); + generate_perl_function_helpers(*f_iter); + ts->set_name(name); + } +} + +/** + * Generates a struct and helpers for a function. + * + * @param tfunction The function + */ +void t_perl_generator::generate_perl_function_helpers(t_function* tfunction) { + t_struct result(program_, service_name_ + "_" + tfunction->get_name() + "_result"); + t_field success(tfunction->get_returntype(), "success", 0); + if (!tfunction->get_returntype()->is_void()) { + result.append(&success); + } + + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& fields = xs->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + result.append(*f_iter); + } + + generate_perl_struct_definition(f_service_, &result, false); +} + +/** + * Generates a service interface definition. + * + * @param tservice The service to generate a header definition for + */ +void t_perl_generator::generate_service_interface(t_service* tservice) { + string extends_if = ""; + t_service* extends_s = tservice->get_extends(); + if (extends_s != NULL) { + extends_if = "use base qw(" + perl_namespace(extends_s->get_program()) + extends_s->get_name() + + "If);"; + } + + f_service_ << "package " << perl_namespace(program_) << service_name_ << "If;" << endl << endl + << "use strict;" << endl << extends_if << endl << endl; + + indent_up(); + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + f_service_ << "sub " << function_signature(*f_iter) << endl << " die 'implement interface';\n}" + << endl << endl; + } + indent_down(); +} + +/** + * Generates a REST interface + */ +void t_perl_generator::generate_service_rest(t_service* tservice) { + string extends = ""; + string extends_if = ""; + t_service* extends_s = tservice->get_extends(); + if (extends_s != NULL) { + extends = extends_s->get_name(); + extends_if = "use base qw(" + perl_namespace(extends_s->get_program()) + extends_s->get_name() + + "Rest);"; + } + f_service_ << "package " << perl_namespace(program_) << service_name_ << "Rest;" << endl << endl + << "use strict;" << endl << extends_if << endl << endl; + + if (extends.empty()) { + f_service_ << "sub new {" << endl; + + indent_up(); + + f_service_ << indent() << "my ($classname, $impl) = @_;" << endl << indent() + << "my $self ={ impl => $impl };" << endl << endl << indent() + << "return bless($self,$classname);" << endl; + + indent_down(); + + f_service_ << "}" << endl << endl; + } + + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + f_service_ << "sub " << (*f_iter)->get_name() << "{" << endl; + + indent_up(); + + f_service_ << indent() << "my ($self, $request) = @_;" << endl << endl; + + const vector<t_field*>& args = (*f_iter)->get_arglist()->get_members(); + vector<t_field*>::const_iterator a_iter; + for (a_iter = args.begin(); a_iter != args.end(); ++a_iter) { + t_type* atype = get_true_type((*a_iter)->get_type()); + string req = "$request->{'" + (*a_iter)->get_name() + "'}"; + f_service_ << indent() << "my $" << (*a_iter)->get_name() << " = (" << req << ") ? " << req + << " : undef;" << endl; + if (atype->is_string() && ((t_base_type*)atype)->is_string_list()) { + f_service_ << indent() << "my @" << (*a_iter)->get_name() << " = split(/,/, $" + << (*a_iter)->get_name() << ");" << endl << indent() << "$" + << (*a_iter)->get_name() << " = \\@" << (*a_iter)->get_name() << endl; + } + } + f_service_ << indent() << "return $self->{impl}->" << (*f_iter)->get_name() << "(" + << argument_list((*f_iter)->get_arglist()) << ");" << endl; + indent_down(); + indent(f_service_) << "}" << endl << endl; + } +} + +/** + * Generates a service client definition. + * + * @param tservice The service to generate a server for. + */ +void t_perl_generator::generate_service_client(t_service* tservice) { + string extends = ""; + string extends_client = ""; + t_service* extends_s = tservice->get_extends(); + if (extends_s != NULL) { + extends = perl_namespace(extends_s->get_program()) + extends_s->get_name(); + extends_client = "use base qw(" + extends + "Client);"; + } + + f_service_ << "package " << perl_namespace(program_) << service_name_ << "Client;" << endl << endl + << extends_client << endl << "use base qw(" << perl_namespace(program_) + << service_name_ << "If);" << endl; + + // Constructor function + f_service_ << "sub new {" << endl; + + indent_up(); + + f_service_ << indent() << "my ($classname, $input, $output) = @_;" << endl << indent() + << "my $self = {};" << endl; + + if (!extends.empty()) { + f_service_ << indent() << "$self = $classname->SUPER::new($input, $output);" << endl; + } else { + f_service_ << indent() << "$self->{input} = $input;" << endl << indent() + << "$self->{output} = defined $output ? $output : $input;" << endl << indent() + << "$self->{seqid} = 0;" << endl; + } + + f_service_ << indent() << "return bless($self,$classname);" << endl; + + indent_down(); + + f_service_ << "}" << endl << endl; + + // Generate client method implementations + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* arg_struct = (*f_iter)->get_arglist(); + const vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator fld_iter; + string funname = (*f_iter)->get_name(); + + // Open function + f_service_ << "sub " << function_signature(*f_iter) << endl; + + indent_up(); + + indent(f_service_) << indent() << "$self->send_" << funname << "("; + + bool first = true; + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << "$" << (*fld_iter)->get_name(); + } + f_service_ << ");" << endl; + + if (!(*f_iter)->is_oneway()) { + f_service_ << indent(); + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_ << "return "; + } + f_service_ << "$self->recv_" << funname << "();" << endl; + } + + indent_down(); + + f_service_ << "}" << endl << endl; + + f_service_ << "sub send_" << function_signature(*f_iter) << endl; + + indent_up(); + + std::string argsname = perl_namespace(tservice->get_program()) + service_name_ + "_" + + (*f_iter)->get_name() + "_args"; + + // Serialize the request header + f_service_ << indent() << "$self->{output}->writeMessageBegin('" << (*f_iter)->get_name() + << "', " << ((*f_iter)->is_oneway() ? "Thrift::TMessageType::ONEWAY" : "Thrift::TMessageType::CALL") + << ", $self->{seqid});" << endl; + + f_service_ << indent() << "my $args = " << argsname << "->new();" << endl; + + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + f_service_ << indent() << "$args->{" << (*fld_iter)->get_name() << "} = $" + << (*fld_iter)->get_name() << ";" << endl; + } + + // Write to the stream + f_service_ << indent() << "$args->write($self->{output});" << endl << indent() + << "$self->{output}->writeMessageEnd();" << endl << indent() + << "$self->{output}->getTransport()->flush();" << endl; + + indent_down(); + + f_service_ << "}" << endl; + + if (!(*f_iter)->is_oneway()) { + std::string resultname = perl_namespace(tservice->get_program()) + service_name_ + "_" + + (*f_iter)->get_name() + "_result"; + t_struct noargs(program_); + + t_function recv_function((*f_iter)->get_returntype(), + string("recv_") + (*f_iter)->get_name(), + &noargs); + // Open function + f_service_ << endl << "sub " << function_signature(&recv_function) << endl; + + indent_up(); + + f_service_ << indent() << "my $rseqid = 0;" << endl << indent() << "my $fname;" << endl + << indent() << "my $mtype = 0;" << endl << endl; + + f_service_ << indent() << "$self->{input}->readMessageBegin(\\$fname, \\$mtype, \\$rseqid);" + << endl << indent() << "if ($mtype == Thrift::TMessageType::EXCEPTION) {" << endl + << indent() << " my $x = Thrift::TApplicationException->new();" << endl << indent() + << " $x->read($self->{input});" << endl << indent() + << " $self->{input}->readMessageEnd();" << endl << indent() << " die $x;" << endl + << indent() << "}" << endl; + + f_service_ << indent() << "my $result = " << resultname << "->new();" << endl << indent() + << "$result->read($self->{input});" << endl; + + f_service_ << indent() << "$self->{input}->readMessageEnd();" << endl << endl; + + // Careful, only return result if not a void function + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_ << indent() << "if (defined $result->{success} ) {" << endl << indent() + << " return $result->{success};" << endl << indent() << "}" << endl; + } + + t_struct* xs = (*f_iter)->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + f_service_ << indent() << "if (defined $result->{" << (*x_iter)->get_name() << "}) {" + << endl << indent() << " die $result->{" << (*x_iter)->get_name() << "};" + << endl << indent() << "}" << endl; + } + + // Careful, only return _result if not a void function + if ((*f_iter)->get_returntype()->is_void()) { + indent(f_service_) << "return;" << endl; + } else { + f_service_ << indent() << "die \"" << (*f_iter)->get_name() << " failed: unknown result\";" + << endl; + } + + // Close function + indent_down(); + f_service_ << "}" << endl; + } + } +} + +/** + * Deserializes a field of any type. + */ +void t_perl_generator::generate_deserialize_field(ostream& out, + t_field* tfield, + string prefix, + bool inclass) { + (void)inclass; + t_type* type = get_true_type(tfield->get_type()); + + if (type->is_void()) { + throw "CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name(); + } + + string name = tfield->get_name(); + + // Hack for when prefix is defined (always a hash ref) + if (!prefix.empty()) { + name = prefix + "{" + tfield->get_name() + "}"; + } + + if (type->is_struct() || type->is_xception()) { + generate_deserialize_struct(out, (t_struct*)type, name); + } else if (type->is_container()) { + generate_deserialize_container(out, type, name); + } else if (type->is_base_type() || type->is_enum()) { + indent(out) << "$xfer += $input->"; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + out << "readString(\\$" << name << ");"; + break; + case t_base_type::TYPE_BOOL: + out << "readBool(\\$" << name << ");"; + break; + case t_base_type::TYPE_I8: + out << "readByte(\\$" << name << ");"; + break; + case t_base_type::TYPE_I16: + out << "readI16(\\$" << name << ");"; + break; + case t_base_type::TYPE_I32: + out << "readI32(\\$" << name << ");"; + break; + case t_base_type::TYPE_I64: + out << "readI64(\\$" << name << ");"; + break; + case t_base_type::TYPE_DOUBLE: + out << "readDouble(\\$" << name << ");"; + break; + default: + throw "compiler error: no PERL name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "readI32(\\$" << name << ");"; + } + out << endl; + + } else { + printf("DO NOT KNOW HOW TO DESERIALIZE FIELD '%s' TYPE '%s'\n", + tfield->get_name().c_str(), + type->get_name().c_str()); + } +} + +/** + * Generates an unserializer for a variable. This makes two key assumptions, + * first that there is a const char* variable named data that points to the + * buffer for deserialization, and that there is a variable protocol which + * is a reference to a TProtocol serialization object. + */ +void t_perl_generator::generate_deserialize_struct(ostream& out, + t_struct* tstruct, + string prefix) { + out << indent() << "$" << prefix << " = " << perl_namespace(tstruct->get_program()) + << tstruct->get_name() << "->new();" << endl << indent() << "$xfer += $" << prefix + << "->read($input);" << endl; +} + +void t_perl_generator::generate_deserialize_container(ostream& out, t_type* ttype, string prefix) { + scope_up(out); + + string size = tmp("_size"); + string ktype = tmp("_ktype"); + string vtype = tmp("_vtype"); + string etype = tmp("_etype"); + + t_field fsize(g_type_i32, size); + t_field fktype(g_type_i8, ktype); + t_field fvtype(g_type_i8, vtype); + t_field fetype(g_type_i8, etype); + + out << indent() << "my $" << size << " = 0;" << endl; + + // Declare variables, read header + if (ttype->is_map()) { + out << indent() << "$" << prefix << " = {};" << endl << indent() << "my $" << ktype << " = 0;" + << endl << indent() << "my $" << vtype << " = 0;" << endl; + + out << indent() << "$xfer += $input->readMapBegin(" + << "\\$" << ktype << ", \\$" << vtype << ", \\$" << size << ");" << endl; + + } else if (ttype->is_set()) { + + out << indent() << "$" << prefix << " = {};" << endl << indent() << "my $" << etype << " = 0;" + << endl << indent() << "$xfer += $input->readSetBegin(" + << "\\$" << etype << ", \\$" << size << ");" << endl; + + } else if (ttype->is_list()) { + + out << indent() << "$" << prefix << " = [];" << endl << indent() << "my $" << etype << " = 0;" + << endl << indent() << "$xfer += $input->readListBegin(" + << "\\$" << etype << ", \\$" << size << ");" << endl; + } + + // For loop iterates over elements + string i = tmp("_i"); + indent(out) << "for (my $" << i << " = 0; $" << i << " < $" << size << "; ++$" << i << ")" + << endl; + + scope_up(out); + + if (ttype->is_map()) { + generate_deserialize_map_element(out, (t_map*)ttype, prefix); + } else if (ttype->is_set()) { + generate_deserialize_set_element(out, (t_set*)ttype, prefix); + } else if (ttype->is_list()) { + generate_deserialize_list_element(out, (t_list*)ttype, prefix); + } + + scope_down(out); + + // Read container end + if (ttype->is_map()) { + indent(out) << "$xfer += $input->readMapEnd();" << endl; + } else if (ttype->is_set()) { + indent(out) << "$xfer += $input->readSetEnd();" << endl; + } else if (ttype->is_list()) { + indent(out) << "$xfer += $input->readListEnd();" << endl; + } + + scope_down(out); +} + +/** + * Generates code to deserialize a map + */ +void t_perl_generator::generate_deserialize_map_element(ostream& out, t_map* tmap, string prefix) { + string key = tmp("key"); + string val = tmp("val"); + t_field fkey(tmap->get_key_type(), key); + t_field fval(tmap->get_val_type(), val); + + indent(out) << declare_field(&fkey, true, true) << endl; + indent(out) << declare_field(&fval, true, true) << endl; + + generate_deserialize_field(out, &fkey); + generate_deserialize_field(out, &fval); + + indent(out) << "$" << prefix << "->{$" << key << "} = $" << val << ";" << endl; +} + +void t_perl_generator::generate_deserialize_set_element(ostream& out, t_set* tset, string prefix) { + string elem = tmp("elem"); + t_field felem(tset->get_elem_type(), elem); + + indent(out) << "my $" << elem << " = undef;" << endl; + + generate_deserialize_field(out, &felem); + + indent(out) << "$" << prefix << "->{$" << elem << "} = 1;" << endl; +} + +void t_perl_generator::generate_deserialize_list_element(ostream& out, + t_list* tlist, + string prefix) { + string elem = tmp("elem"); + t_field felem(tlist->get_elem_type(), elem); + + indent(out) << "my $" << elem << " = undef;" << endl; + + generate_deserialize_field(out, &felem); + + indent(out) << "push(@{$" << prefix << "},$" << elem << ");" << endl; +} + +/** + * Serializes a field of any type. + * + * @param tfield The field to serialize + * @param prefix Name to prepend to field name + */ +void t_perl_generator::generate_serialize_field(ostream& out, t_field* tfield, string prefix) { + t_type* type = get_true_type(tfield->get_type()); + + // Do nothing for void types + if (type->is_void()) { + throw "CANNOT GENERATE SERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name(); + } + + if (type->is_struct() || type->is_xception()) { + generate_serialize_struct(out, (t_struct*)type, prefix + "{" + tfield->get_name() + "}"); + } else if (type->is_container()) { + generate_serialize_container(out, type, prefix + "{" + tfield->get_name() + "}"); + } else if (type->is_base_type() || type->is_enum()) { + + string name = tfield->get_name(); + + // Hack for when prefix is defined (always a hash ref) + if (!prefix.empty()) + name = prefix + "{" + tfield->get_name() + "}"; + + indent(out) << "$xfer += $output->"; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + out << "writeString($" << name << ");"; + break; + case t_base_type::TYPE_BOOL: + out << "writeBool($" << name << ");"; + break; + case t_base_type::TYPE_I8: + out << "writeByte($" << name << ");"; + break; + case t_base_type::TYPE_I16: + out << "writeI16($" << name << ");"; + break; + case t_base_type::TYPE_I32: + out << "writeI32($" << name << ");"; + break; + case t_base_type::TYPE_I64: + out << "writeI64($" << name << ");"; + break; + case t_base_type::TYPE_DOUBLE: + out << "writeDouble($" << name << ");"; + break; + default: + throw "compiler error: no PERL name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "writeI32($" << name << ");"; + } + out << endl; + + } else { + printf("DO NOT KNOW HOW TO SERIALIZE FIELD '%s%s' TYPE '%s'\n", + prefix.c_str(), + tfield->get_name().c_str(), + type->get_name().c_str()); + } +} + +/** + * Serializes all the members of a struct. + * + * @param tstruct The struct to serialize + * @param prefix String prefix to attach to all fields + */ +void t_perl_generator::generate_serialize_struct(ostream& out, t_struct* tstruct, string prefix) { + (void)tstruct; + indent(out) << "$xfer += $" << prefix << "->write($output);" << endl; +} + +/** + * Writes out a container + */ +void t_perl_generator::generate_serialize_container(ostream& out, t_type* ttype, string prefix) { + scope_up(out); + + if (ttype->is_map()) { + indent(out) << "$xfer += $output->writeMapBegin(" + << type_to_enum(((t_map*)ttype)->get_key_type()) << ", " + << type_to_enum(((t_map*)ttype)->get_val_type()) << ", " + << "scalar(keys %{$" << prefix << "}));" << endl; + } else if (ttype->is_set()) { + indent(out) << "$xfer += $output->writeSetBegin(" + << type_to_enum(((t_set*)ttype)->get_elem_type()) << ", " + << "scalar(@{$" << prefix << "}));" << endl; + + } else if (ttype->is_list()) { + + indent(out) << "$xfer += $output->writeListBegin(" + << type_to_enum(((t_list*)ttype)->get_elem_type()) << ", " + << "scalar(@{$" << prefix << "}));" << endl; + } + + scope_up(out); + + if (ttype->is_map()) { + string kiter = tmp("kiter"); + string viter = tmp("viter"); + indent(out) << "while( my ($" << kiter << ",$" << viter << ") = each %{$" << prefix << "}) " + << endl; + + scope_up(out); + generate_serialize_map_element(out, (t_map*)ttype, kiter, viter); + scope_down(out); + + } else if (ttype->is_set()) { + string iter = tmp("iter"); + indent(out) << "foreach my $" << iter << " (@{$" << prefix << "})" << endl; + scope_up(out); + generate_serialize_set_element(out, (t_set*)ttype, iter); + scope_down(out); + + } else if (ttype->is_list()) { + string iter = tmp("iter"); + indent(out) << "foreach my $" << iter << " (@{$" << prefix << "}) " << endl; + scope_up(out); + generate_serialize_list_element(out, (t_list*)ttype, iter); + scope_down(out); + } + + scope_down(out); + + if (ttype->is_map()) { + indent(out) << "$xfer += $output->writeMapEnd();" << endl; + } else if (ttype->is_set()) { + indent(out) << "$xfer += $output->writeSetEnd();" << endl; + } else if (ttype->is_list()) { + indent(out) << "$xfer += $output->writeListEnd();" << endl; + } + + scope_down(out); +} + +/** + * Serializes the members of a map. + * + */ +void t_perl_generator::generate_serialize_map_element(ostream& out, + t_map* tmap, + string kiter, + string viter) { + t_field kfield(tmap->get_key_type(), kiter); + generate_serialize_field(out, &kfield); + + t_field vfield(tmap->get_val_type(), viter); + generate_serialize_field(out, &vfield); +} + +/** + * Serializes the members of a set. + */ +void t_perl_generator::generate_serialize_set_element(ostream& out, t_set* tset, string iter) { + t_field efield(tset->get_elem_type(), iter); + generate_serialize_field(out, &efield); +} + +/** + * Serializes the members of a list. + */ +void t_perl_generator::generate_serialize_list_element(ostream& out, t_list* tlist, string iter) { + t_field efield(tlist->get_elem_type(), iter); + generate_serialize_field(out, &efield); +} + +/** + * Declares a field, which may include initialization as necessary. + * + * @param ttype The type + */ +string t_perl_generator::declare_field(t_field* tfield, bool init, bool obj) { + string result = "my $" + tfield->get_name(); + if (init) { + t_type* type = get_true_type(tfield->get_type()); + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + break; + case t_base_type::TYPE_STRING: + result += " = ''"; + break; + case t_base_type::TYPE_BOOL: + result += " = 0"; + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + result += " = 0"; + break; + case t_base_type::TYPE_DOUBLE: + result += " = 0.0"; + break; + default: + throw "compiler error: no PERL initializer for base type " + + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + result += " = 0"; + } else if (type->is_container()) { + result += " = []"; + } else if (type->is_struct() || type->is_xception()) { + if (obj) { + result += " = " + perl_namespace(type->get_program()) + type->get_name() + "->new()"; + } else { + result += " = undef"; + } + } + } + return result + ";"; +} + +/** + * Renders a function signature of the form 'type name(args)' + * + * @param tfunction Function definition + * @return String of rendered function definition + */ +string t_perl_generator::function_signature(t_function* tfunction, string prefix) { + + string str; + + str = prefix + tfunction->get_name() + "{\n"; + str += " my $self = shift;\n"; + + // Need to create perl function arg inputs + const vector<t_field*>& fields = tfunction->get_arglist()->get_members(); + vector<t_field*>::const_iterator f_iter; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + str += " my $" + (*f_iter)->get_name() + " = shift;\n"; + } + + return str; +} + +/** + * Renders a field list + */ +string t_perl_generator::argument_list(t_struct* tstruct) { + string result = ""; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + result += ", "; + } + result += "$" + (*f_iter)->get_name(); + } + return result; +} + +/** + * Converts the parse type to a C++ enum string for the given type. + */ +string t_perl_generator::type_to_enum(t_type* type) { + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + return "Thrift::TType::STRING"; + case t_base_type::TYPE_BOOL: + return "Thrift::TType::BOOL"; + case t_base_type::TYPE_I8: + return "Thrift::TType::BYTE"; + case t_base_type::TYPE_I16: + return "Thrift::TType::I16"; + case t_base_type::TYPE_I32: + return "Thrift::TType::I32"; + case t_base_type::TYPE_I64: + return "Thrift::TType::I64"; + case t_base_type::TYPE_DOUBLE: + return "Thrift::TType::DOUBLE"; + } + } else if (type->is_enum()) { + return "Thrift::TType::I32"; + } else if (type->is_struct() || type->is_xception()) { + return "Thrift::TType::STRUCT"; + } else if (type->is_map()) { + return "Thrift::TType::MAP"; + } else if (type->is_set()) { + return "Thrift::TType::SET"; + } else if (type->is_list()) { + return "Thrift::TType::LIST"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +THRIFT_REGISTER_GENERATOR(perl, "Perl", "") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_php_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_php_generator.cc new file mode 100644 index 000000000..79bd5a283 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_php_generator.cc @@ -0,0 +1,2785 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <string> +#include <fstream> +#include <iostream> +#include <vector> + +#include <stdlib.h> +#include <sys/stat.h> +#include <sstream> +#include "thrift/platform.h" +#include "thrift/generate/t_oop_generator.h" + +using std::map; +using std::ostream; +using std::ostringstream; +using std::string; +using std::stringstream; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes + +#define NSGLOBAL (nsglobal_.size() ? nsglobal_ : "") +#define NSGLOBAL_A ("\\" + NSGLOBAL) +#define NSGLOBAL_B (NSGLOBAL + "\\") +#define NSGLOBAL_AB ("\\" + NSGLOBAL + "\\") + +/** + * PHP code generator. + * + */ +class t_php_generator : public t_oop_generator { +public: + t_php_generator(t_program* program, + const std::map<std::string, std::string>& parsed_options, + const std::string& option_string) + : t_oop_generator(program) { + (void)option_string; + std::map<std::string, std::string>::const_iterator iter; + + binary_inline_ = false; + rest_ = false; + phps_ = false; + oop_ = false; + validate_ = false; + json_serializable_ = false; + nsglobal_ = ""; // by default global namespace is empty + classmap_ = false; + for (iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + if (iter->first.compare("inlined") == 0) { + binary_inline_ = true; + } else if (iter->first.compare("rest") == 0) { + rest_ = true; + } else if (iter->first.compare("server") == 0) { + phps_ = true; + } else if (iter->first.compare("oop") == 0) { + oop_ = true; + } else if (iter->first.compare("validate") == 0) { + validate_ = true; + } else if (iter->first.compare("json") == 0) { + json_serializable_ = true; + } else if (iter->first.compare("nsglobal") == 0) { + nsglobal_ = iter->second; + } else if (iter->first.compare("classmap") == 0) { + classmap_ = true; + } else if (iter->first.compare("psr4") == 0) { + if(classmap_){ + throw "psr4 and classmap are mutually exclusive."; + } else { + pwarning(0, "psr4 is default option! needn't add psr4 option!\n"); + } + } else { + throw "unknown option php:" + iter->first; + } + } + + if (oop_ && binary_inline_) { + throw "oop and inlined are mutually exclusive."; + } + + out_dir_base_ = (binary_inline_ ? "gen-phpi" : "gen-php"); + escape_['$'] = "\\$"; + } + + std::string indent_str() const override { + return " "; + } + + static bool is_valid_namespace(const std::string& sub_namespace); + + /** + * Init and close methods + */ + + void init_generator() override; + void close_generator() override; + + /** + * Program-level generation functions + */ + + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_consts(vector<t_const*> consts) override; + void generate_struct(t_struct* tstruct) override; + void generate_xception(t_struct* txception) override; + void generate_service(t_service* tservice) override; + + std::string render_const_value(t_type* type, t_const_value* value); + + /** + * Structs! + */ + + void generate_php_struct(t_struct* tstruct, bool is_exception); + void generate_php_struct_definition(std::ostream& out, + t_struct* tstruct, + bool is_xception = false, + bool is_result = false); + void generate_php_struct_reader(std::ostream& out, t_struct* tstruct, bool is_result); + void generate_php_struct_writer(std::ostream& out, t_struct* tstruct, bool is_result); + void generate_php_function_helpers(t_service* tservice, t_function* tfunction); + void generate_php_struct_required_validator(ostream& out, + t_struct* tstruct, + std::string method_name, + bool write_mode); + void generate_php_struct_read_validator(ostream& out, t_struct* tstruct); + void generate_php_struct_write_validator(ostream& out, t_struct* tstruct); + void generate_php_struct_json_serialize(ostream& out, t_struct* tstruct, bool is_result); + bool needs_php_write_validator(t_struct* tstruct, bool is_result); + bool needs_php_read_validator(t_struct* tstruct, bool is_result); + int get_php_num_required_fields(const vector<t_field*>& fields, bool write_mode); + + void generate_php_type_spec(std::ostream& out, t_type* t); + void generate_php_struct_spec(std::ostream& out, t_struct* tstruct); + + /** + * Service-level generation functions + */ + + void generate_service_helpers(t_service* tservice); + void generate_service_interface(t_service* tservice); + void generate_service_rest(t_service* tservice); + void generate_service_client(t_service* tservice); + void generate_service_processor(t_service* tservice); + void generate_process_function(std::ostream& out, t_service* tservice, t_function* tfunction); + void generate_service_header(t_service* tservice, std::ostream& file); + void generate_program_header(std::ostream& file); + + /** + * Serialization constructs + */ + + void generate_deserialize_field(std::ostream& out, + t_field* tfield, + std::string prefix = "", + bool inclass = false); + + void generate_deserialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); + + void generate_deserialize_container(std::ostream& out, t_type* ttype, std::string prefix = ""); + + void generate_deserialize_set_element(std::ostream& out, t_set* tset, std::string prefix = ""); + + void generate_deserialize_map_element(std::ostream& out, t_map* tmap, std::string prefix = ""); + + void generate_deserialize_list_element(std::ostream& out, + t_list* tlist, + std::string prefix = ""); + + void generate_serialize_field(std::ostream& out, t_field* tfield, std::string prefix = ""); + + void generate_serialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); + + void generate_serialize_container(std::ostream& out, t_type* ttype, std::string prefix = ""); + + void generate_serialize_map_element(std::ostream& out, + t_map* tmap, + std::string kiter, + std::string viter); + + void generate_serialize_set_element(std::ostream& out, t_set* tmap, std::string iter); + + void generate_serialize_list_element(std::ostream& out, t_list* tlist, std::string iter); + + void generate_php_doc(std::ostream& out, t_doc* tdoc); + + void generate_php_doc(std::ostream& out, t_field* tfield); + + void generate_php_doc(std::ostream& out, t_function* tfunction); + + void generate_php_docstring_comment(std::ostream& out, string contents); + + /** + * Helper rendering functions + */ + + std::string php_includes(); + std::string declare_field(t_field* tfield, bool init = false, bool obj = false); + std::string function_signature(t_function* tfunction, std::string prefix = ""); + std::string argument_list(t_struct* tstruct, bool addTypeHints = true); + std::string type_to_cast(t_type* ttype); + std::string type_to_enum(t_type* ttype); + std::string type_to_phpdoc(t_type* ttype); + + bool php_is_scalar(t_type *ttype) { + ttype = ttype->get_true_type(); + if(ttype->is_base_type()) { + return true; + } else if(ttype->is_enum()) { + return true; + } else { + return false; + } + } + + std::string php_namespace_base(const t_program* p) { + std::string ns = p->get_namespace("php"); + const char* delimiter = "\\"; + size_t position = ns.find('.'); + while (position != string::npos) { + ns.replace(position, 1, delimiter); + position = ns.find('.', position + 1); + } + return ns; + } + + // general use namespace prefixing: \my\namespace\ or my_namespace_ + string php_namespace(const t_program* p) { + string ns = php_namespace_base(p); + return (nsglobal_.size() ? NSGLOBAL_AB : NSGLOBAL_B) + (ns.size() ? (ns + "\\") : ""); + } + + // return the namespace of a file: + // global\ns\sub\ns or global\ns or sub\ns + string php_namespace_suffix(const t_program* p) { + string ns = php_namespace_base(p); + + return NSGLOBAL + + (ns.size() && NSGLOBAL.size() ? "\\" : "") + + ns; + } + + // add a directory to already existing namespace + string php_namespace_directory(string directory, bool end = true) { + (void)directory; + if (end) { + return ";"; + } else { + return ""; + } + } + + // writing an autload identifier into globa;ls: my\namespace\ or my_namespace_ + string php_namespace_autoload(const t_program* p) { + std::string ns = php_namespace_base(p); + return (nsglobal_.size() ? NSGLOBAL_B : NSGLOBAL) + (ns.size() ? (ns + "\\") : ""); + } + + // declaring a type: typename or my_namespace_typename + string php_namespace_declaration(t_type* t) { return t->get_name(); } + + std::string php_path(t_program* p) { + std::string ns = p->get_namespace("php.path"); + if (ns.empty()) { + return p->get_name(); + } + + // Transform the java-style namespace into a path. + for (char & n : ns) { + if (n == '.') { + n = '/'; + } + } + + return ns + '/'; + } + + /** + * Transform class_method into ClassMethod + * + * @param str + * @return stirng + */ + string classify(string str) { + string classe = ""; + + vector<string> x = split(str, '_'); + + for (const auto & i : x) { + classe = classe + capitalize(i); + } + + return classe; + } + + /** + * Split method + * @param s + * @param delim + * @param elems + * @return + */ + vector<string>& split(const string& s, char delim, vector<string>& elems) { + stringstream ss(s); + string item; + + while (getline(ss, item, delim)) { + elems.push_back(item); + } + + return elems; + } + + vector<string> split(const string& s, char delim) { + vector<string> elems; + + return split(s, delim, elems); + } + + /** + * Capitalize method + * @param str + * @return + */ + string capitalize(string str) { + string::iterator it(str.begin()); + + if (it != str.end()) + str[0] = toupper((unsigned char)str[0]); + + // while(++it != str.end()) + // { + // *it = tolower((unsigned char)*it); + // } + return str; + } + +private: + /** + * File streams + */ + ofstream_with_content_based_conditional_update f_types_; + ofstream_with_content_based_conditional_update f_service_; + + std::string package_dir_; + /** + * Generate protocol-independent template? Or Binary inline code? + */ + bool binary_inline_; + + /** + * Generate a REST handler class + */ + bool rest_; + + /** + * Generate stubs for a PHP server + */ + bool phps_; + + /** + * Whether to use OOP base class TBase + */ + bool oop_; + + /** + * Whether to generate old-style PHP file to use classmap autoloading + */ + bool classmap_; + + /** + * Whether to generate validator code + */ + bool validate_; + + /** + * Whether to generate JsonSerializable classes + */ + bool json_serializable_; + + /** + * Global namespace for PHP 5.3 + */ + std::string nsglobal_; +}; + +bool t_php_generator::is_valid_namespace(const std::string& sub_namespace) { + return sub_namespace == "path"; +} + +/** + * Prepares for file generation by opening up the necessary file output + * streams. + * + * @param tprogram The program to generate + */ +void t_php_generator::init_generator() { + // Make output directory + MKDIR(get_out_dir().c_str()); + + // Create Real directory Namespaces + vector<string> NSx = split(php_namespace_suffix(get_program()), '\\'); + package_dir_ = get_out_dir(); + + for (const auto & i : NSx) { + package_dir_ = package_dir_ + "/" + i + "/"; + MKDIR(package_dir_.c_str()); + } + + // Prepare output file for all the types in classmap mode + if (classmap_) { + // Make output file + string f_types_name = package_dir_ + "Types.php"; + f_types_.open(f_types_name.c_str()); + generate_program_header(f_types_); + } +} + +/** + * Prints standard php includes + */ +string t_php_generator::php_includes() { + string includes = "use Thrift\\Base\\TBase;\n" + "use Thrift\\Type\\TType;\n" + "use Thrift\\Type\\TMessageType;\n" + "use Thrift\\Exception\\TException;\n" + "use Thrift\\Exception\\TProtocolException;\n" + "use Thrift\\Protocol\\TProtocol;\n" + "use Thrift\\Protocol\\TBinaryProtocolAccelerated;\n" + "use Thrift\\Exception\\TApplicationException;\n"; + + if (json_serializable_) { + includes += "use JsonSerializable;\n" + "use stdClass;\n"; + } + + return includes; +} + +/** + * Close up (or down) some filez. + */ +void t_php_generator::close_generator() { + if (classmap_) { + // Close types file + f_types_.close(); + } +} + +/** + * Generates a typedef. This is not done in PHP, types are all implicit. + * + * @param ttypedef The type definition + */ +void t_php_generator::generate_typedef(t_typedef* ttypedef) { + (void)ttypedef; +} + +/** + * Generates service header contains namespace suffix and includes inside file specified + */ +void t_php_generator::generate_service_header(t_service* tservice, std::ostream& file) { + file << "<?php" << endl; + if (!php_namespace_suffix(tservice->get_program()).empty()) { + file << "namespace " << php_namespace_suffix(tservice->get_program()) << ";" << endl + << endl; + } + file << autogen_comment() << php_includes(); + + file << endl; +} + +/** + * Generates program header contains namespace suffix and includes inside file specified + */ +void t_php_generator::generate_program_header(std::ostream& file) { + file << "<?php" << endl; + if (!php_namespace_suffix(get_program()).empty()) { + file << "namespace " << php_namespace_suffix(get_program()) << ";" << endl + << endl; + } + file << autogen_comment() << php_includes(); + + file << endl; +} + +/** + * Generates code for an enumerated type. Since define is expensive to lookup + * in PHP, we use a global array for this. + * + * @param tenum The enumeration + */ +void t_php_generator::generate_enum(t_enum* tenum) { + ofstream_with_content_based_conditional_update& f_enum = f_types_; + if (!classmap_) { + string f_enum_name = package_dir_ + tenum->get_name() + ".php"; + f_enum.open(f_enum_name.c_str()); + generate_program_header(f_enum); + } + + vector<t_enum_value*> constants = tenum->get_constants(); + vector<t_enum_value*>::iterator c_iter; + + // We're also doing it this way to see how it performs. It's more legible + // code but you can't do things like an 'extract' on it, which is a bit of + // a downer. + generate_php_doc(f_enum, tenum); + f_enum << "final class " << tenum->get_name() << endl + << "{" << endl; + indent_up(); + + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + int value = (*c_iter)->get_value(); + generate_php_doc(f_enum, *c_iter); + indent(f_enum) << "const " << (*c_iter)->get_name() << " = " << value << ";" << endl + << endl; + } + + indent(f_enum) << "static public $__names = array(" << endl; + + indent_up(); + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + int value = (*c_iter)->get_value(); + indent(f_enum) << value << " => '" << (*c_iter)->get_name() << "'," << endl; + } + indent_down(); + indent(f_enum) << ");" << endl; + + indent_down(); + + f_enum << "}" << endl << endl; + if (!classmap_) { + f_enum.close(); + } +} + +/** + * Generate constant class + * + * Override the one from t_generator + */ +void t_php_generator::generate_consts(vector<t_const*> consts) { + vector<t_const*>::iterator c_iter; + + // Create class only if needed + if (consts.size() > 0) { + + ofstream_with_content_based_conditional_update& f_consts = f_types_; + if (!classmap_) { + string f_consts_name = package_dir_ + "Constant.php"; + f_consts.open(f_consts_name.c_str()); + generate_program_header(f_consts); + } + f_consts << "final class Constant extends \\Thrift\\Type\\TConstant"<< endl + << "{" << endl; + + indent_up(); + + // Create static property + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + string name = (*c_iter)->get_name(); + + indent(f_consts) << "static protected $" << name << ";" << endl; + } + + // Create init function + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + string name = (*c_iter)->get_name(); + + f_consts << endl; + + f_consts << indent() << "protected static function init_" << name << "()" <<endl + << indent() << "{" << endl; + indent_up(); + + indent(f_consts) << "return "; + generate_php_doc(f_consts, *c_iter); + f_consts << render_const_value((*c_iter)->get_type(), (*c_iter)->get_value()); + f_consts << ";" << endl; + + indent_down(); + indent(f_consts) << "}" << endl; + } + + indent_down(); + f_consts << "}" << endl; + if (!classmap_) { + f_consts.close(); + } + } +} + +/** + * Prints the value of a constant with the given type. Note that type checking + * is NOT performed in this function as it is always run beforehand using the + * validate_types method in main.cc + */ +string t_php_generator::render_const_value(t_type* type, t_const_value* value) { + std::ostringstream out; + type = get_true_type(type); + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + out << '"' << get_escaped_string(value) << '"'; + break; + case t_base_type::TYPE_BOOL: + out << (value->get_integer() > 0 ? "true" : "false"); + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + out << value->get_integer(); + break; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) { + out << value->get_integer(); + } else { + out << value->get_double(); + } + break; + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + indent(out) << value->get_integer(); + } else if (type->is_struct() || type->is_xception()) { + out << "new " << php_namespace(type->get_program()) << type->get_name() << "(array(" << endl; + indent_up(); + const vector<t_field*>& fields = ((t_struct*)type)->get_members(); + vector<t_field*>::const_iterator f_iter; + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + t_type* field_type = NULL; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field_type = (*f_iter)->get_type(); + } + } + if (field_type == NULL) { + throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string(); + } + out << indent(); + out << render_const_value(g_type_string, v_iter->first); + out << " => "; + out << render_const_value(field_type, v_iter->second); + out << "," << endl; + } + indent_down(); + indent(out) << "))"; + } else if (type->is_map()) { + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + out << "array(" << endl; + indent_up(); + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + out << indent(); + out << render_const_value(ktype, v_iter->first); + out << " => "; + out << render_const_value(vtype, v_iter->second); + out << "," << endl; + } + indent_down(); + indent(out) << ")"; + } else if (type->is_list() || type->is_set()) { + t_type* etype; + if (type->is_list()) { + etype = ((t_list*)type)->get_elem_type(); + } else { + etype = ((t_set*)type)->get_elem_type(); + } + out << "array(" << endl; + indent_up(); + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + out << indent(); + out << render_const_value(etype, *v_iter); + if (type->is_set()) { + out << " => true"; + } + out << "," << endl; + } + indent_down(); + indent(out) << ")"; + } + return out.str(); +} + +/** + * Make a struct + */ +void t_php_generator::generate_struct(t_struct* tstruct) { + generate_php_struct(tstruct, false); +} + +/** + * Generates a struct definition for a thrift exception. Basically the same + * as a struct but extends the Exception class. + * + * @param txception The struct definition + */ +void t_php_generator::generate_xception(t_struct* txception) { + generate_php_struct(txception, true); +} + +/** + * Structs can be normal or exceptions. + */ +void t_php_generator::generate_php_struct(t_struct* tstruct, bool is_exception) { + ofstream_with_content_based_conditional_update& f_struct = f_types_; + if (!classmap_) { + string f_struct_name = package_dir_ + tstruct->get_name() + ".php"; + f_struct.open(f_struct_name.c_str()); + generate_program_header(f_struct); + } + generate_php_struct_definition(f_struct, tstruct, is_exception); + if (!classmap_) { + f_struct.close(); + } +} + +void t_php_generator::generate_php_type_spec(ostream& out, t_type* t) { + t = get_true_type(t); + indent(out) << "'type' => " << type_to_enum(t) << "," << endl; + + if (t->is_base_type() || t->is_enum()) { + // Noop, type is all we need + } else if (t->is_struct() || t->is_xception()) { + indent(out) << "'class' => '" << php_namespace(t->get_program()) << t->get_name() << "'," + << endl; + } else if (t->is_map()) { + t_type* ktype = get_true_type(((t_map*)t)->get_key_type()); + t_type* vtype = get_true_type(((t_map*)t)->get_val_type()); + indent(out) << "'ktype' => " << type_to_enum(ktype) << "," << endl; + indent(out) << "'vtype' => " << type_to_enum(vtype) << "," << endl; + indent(out) << "'key' => array(" << endl; + indent_up(); + generate_php_type_spec(out, ktype); + indent_down(); + indent(out) << ")," << endl; + indent(out) << "'val' => array(" << endl; + indent_up(); + generate_php_type_spec(out, vtype); + indent(out) << ")," << endl; + indent_down(); + } else if (t->is_list() || t->is_set()) { + t_type* etype; + if (t->is_list()) { + etype = get_true_type(((t_list*)t)->get_elem_type()); + } else { + etype = get_true_type(((t_set*)t)->get_elem_type()); + } + indent(out) << "'etype' => " << type_to_enum(etype) << "," << endl; + indent(out) << "'elem' => array(" << endl; + indent_up(); + generate_php_type_spec(out, etype); + indent(out) << ")," << endl; + indent_down(); + } else { + throw "compiler error: no type for php struct spec field"; + } +} + +/** + * Generates the struct specification structure, which fully qualifies enough + * type information to generalize serialization routines. + */ +void t_php_generator::generate_php_struct_spec(ostream& out, t_struct* tstruct) { + indent(out) << "static public $_TSPEC = array(" << endl; + indent_up(); + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* t = get_true_type((*m_iter)->get_type()); + indent(out) << (*m_iter)->get_key() << " => array(" << endl; + indent_up(); + out << indent() << "'var' => '" << (*m_iter)->get_name() << "'," << endl; + out << indent() << "'isRequired' => " << ((*m_iter)->get_req() == t_field::T_REQUIRED ? "true" : "false") << "," << endl; + generate_php_type_spec(out, t); + indent_down(); + indent(out) << ")," << endl; + } + + indent_down(); + indent(out) << ");" << endl << endl; +} + +/** + * Generates a struct definition for a thrift data type. This is nothing in PHP + * where the objects are all just associative arrays (unless of course we + * decide to start using objects for them...) + * + * @param tstruct The struct definition + */ +void t_php_generator::generate_php_struct_definition(ostream& out, + t_struct* tstruct, + bool is_exception, + bool is_result) { + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + generate_php_doc(out, tstruct); + out << "class " << php_namespace_declaration(tstruct); + if (is_exception) { + out << " extends " + << "TException"; + } else if (oop_) { + out << " extends " + << "TBase"; + } + if (json_serializable_) { + out << " implements JsonSerializable"; + } + out << endl + << "{" << endl; + indent_up(); + + out << indent() << "static public $isValidate = " << (validate_ ? "true" : "false") << ";" << endl << endl; + + generate_php_struct_spec(out, tstruct); + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + string dval = "null"; + t_type* t = get_true_type((*m_iter)->get_type()); + if ((*m_iter)->get_value() != NULL && !(t->is_struct() || t->is_xception())) { + dval = render_const_value((*m_iter)->get_type(), (*m_iter)->get_value()); + } + generate_php_doc(out, *m_iter); + indent(out) << "public $" << (*m_iter)->get_name() << " = " << dval << ";" << endl; + } + + out << endl; + + // Generate constructor from array + string param = (members.size() > 0) ? "$vals = null" : ""; + out << indent() << "public function __construct(" << param << ")"<< endl + << indent() << "{" << endl; + indent_up(); + + if (members.size() > 0) { + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* t = get_true_type((*m_iter)->get_type()); + if ((*m_iter)->get_value() != NULL && (t->is_struct() || t->is_xception())) { + indent(out) << "$this->" << (*m_iter)->get_name() << " = " + << render_const_value(t, (*m_iter)->get_value()) << ";" << endl; + } + } + out << indent() << "if (is_array($vals)) {" << endl; + indent_up(); + if (oop_) { + out << indent() << "parent::__construct(self::$_TSPEC, $vals);" << endl; + } else { + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + out << indent() << "if (isset($vals['" << (*m_iter)->get_name() << "'])) {" << endl; + + indent_up(); + out << indent() << "$this->" << (*m_iter)->get_name() << " = $vals['" + << (*m_iter)->get_name() << "'];" << endl; + + indent_down(); + out << indent() << "}" << endl; + } + } + indent_down(); + out << indent() << "}" << endl; + } + scope_down(out); + out << endl; + + out << indent() << "public function getName()" << endl + << indent() << "{" << endl; + + indent_up(); + out << indent() << "return '" << tstruct->get_name() << "';" << endl; + + indent_down(); + out << indent() << "}" << endl << endl; + + out << endl; + generate_php_struct_reader(out, tstruct, is_result); + out << endl; + generate_php_struct_writer(out, tstruct, is_result); + if (needs_php_read_validator(tstruct, is_result)) { + out << endl; + generate_php_struct_read_validator(out, tstruct); + } + if (needs_php_write_validator(tstruct, is_result)) { + out << endl; + generate_php_struct_write_validator(out, tstruct); + } + if (json_serializable_) { + out << endl; + generate_php_struct_json_serialize(out, tstruct, is_result); + } + + indent_down(); + out << indent() << "}" << endl; +} + +/** + * Generates the read() method for a struct + */ +void t_php_generator::generate_php_struct_reader(ostream& out, t_struct* tstruct, bool is_result) { + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + indent(out) << "public function read($input)" << endl; + scope_up(out); + + if (oop_) { + if (needs_php_read_validator(tstruct, is_result)) { + indent(out) << "$tmp = $this->_read('" << tstruct->get_name() << "', self::$_TSPEC, $input);" + << endl; + indent(out) << "$this->_validateForRead();" << endl; + indent(out) << "return $tmp;" << endl; + } else { + indent(out) << "return $this->_read('" << tstruct->get_name() << "', self::$_TSPEC, $input);" + << endl; + } + scope_down(out); + out << endl; + return; + } + + out << indent() << "$xfer = 0;" << endl << indent() << "$fname = null;" << endl << indent() + << "$ftype = 0;" << endl << indent() << "$fid = 0;" << endl; + + // Declare stack tmp variables + if (!binary_inline_) { + indent(out) << "$xfer += $input->readStructBegin($fname);" << endl; + } + + // Loop over reading in fields + indent(out) << "while (true) {" << endl; + + indent_up(); + + // Read beginning field marker + if (binary_inline_) { + t_field fftype(g_type_i8, "ftype"); + t_field ffid(g_type_i16, "fid"); + generate_deserialize_field(out, &fftype); + out << indent() << "if ($ftype == " + << "TType::STOP) {" << endl << indent() << " break;" << endl << indent() << "}" << endl; + generate_deserialize_field(out, &ffid); + } else { + indent(out) << "$xfer += $input->readFieldBegin($fname, $ftype, $fid);" << endl; + // Check for field STOP marker and break + indent(out) << "if ($ftype == " + << "TType::STOP) {" << endl; + indent_up(); + indent(out) << "break;" << endl; + indent_down(); + indent(out) << "}" << endl; + } + + // Switch statement on the field we are reading + indent(out) << "switch ($fid) {" << endl; + + indent_up(); + + // Generate deserialization code for known cases + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + indent(out) << "case " << (*f_iter)->get_key() << ":" << endl; + indent_up(); + indent(out) << "if ($ftype == " << type_to_enum((*f_iter)->get_type()) << ") {" << endl; + indent_up(); + generate_deserialize_field(out, *f_iter, "this->"); + indent_down(); + out << indent() << "} else {" << endl; + + indent_up(); + if (binary_inline_) { + indent(out) << "$xfer += TProtocol::skipBinary($input, $ftype);" << endl; + } else { + indent(out) << "$xfer += $input->skip($ftype);" << endl; + } + + indent_down(); + out << indent() << "}" << endl << indent() << "break;" << endl; + indent_down(); + } + + // In the default case we skip the field + indent(out) << "default:" << endl; + + indent_up(); + if (binary_inline_) { + indent(out) << "$xfer += " + << "TProtocol::skipBinary($input, $ftype);" << endl; + } else { + indent(out) << "$xfer += $input->skip($ftype);" << endl; + } + indent(out) << "break;" << endl; + indent_down(); + + scope_down(out); + + if (!binary_inline_) { + // Read field end marker + indent(out) << "$xfer += $input->readFieldEnd();" << endl; + } + + scope_down(out); + + if (!binary_inline_) { + indent(out) << "$xfer += $input->readStructEnd();" << endl; + } + + if (needs_php_read_validator(tstruct, is_result)) { + indent(out) << "$this->_validateForRead();" << endl; + } + + indent(out) << "return $xfer;" << endl; + + indent_down(); + out << indent() << "}" << endl; +} + +/** + * Generates the write() method for a struct + */ +void t_php_generator::generate_php_struct_writer(ostream& out, t_struct* tstruct, bool is_result) { + string name = tstruct->get_name(); + const vector<t_field*>& fields = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator f_iter; + + if (binary_inline_) { + indent(out) << "public function write(&$output)" << endl; + } else { + indent(out) << "public function write($output)" << endl; + } + indent(out) << "{" << endl; + indent_up(); + + if (needs_php_write_validator(tstruct, is_result)) { + indent(out) << "$this->_validateForWrite();" << endl; + } + + if (oop_) { + indent(out) << "return $this->_write('" << tstruct->get_name() << "', self::$_TSPEC, $output);" + << endl; + scope_down(out); + out << endl; + return; + } + + indent(out) << "$xfer = 0;" << endl; + + if (!binary_inline_) { + indent(out) << "$xfer += $output->writeStructBegin('" << name << "');" << endl; + } + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + out << indent() << "if ($this->" << (*f_iter)->get_name() << " !== null) {" << endl; + indent_up(); + + t_type* type = get_true_type((*f_iter)->get_type()); + string expect; + if (type->is_container()) { + expect = "array"; + } else if (type->is_struct()) { + expect = "object"; + } + if (!expect.empty()) { + out << indent() << "if (!is_" << expect << "($this->" << (*f_iter)->get_name() << ")) {" + << endl; + indent_up(); + out << indent() << "throw new " + << "TProtocolException('Bad type in structure.', " + << "TProtocolException::INVALID_DATA);" << endl; + scope_down(out); + } + + // Write field header + if (binary_inline_) { + out << indent() << "$output .= pack('c', " << type_to_enum((*f_iter)->get_type()) << ");" + << endl << indent() << "$output .= pack('n', " << (*f_iter)->get_key() << ");" << endl; + } else { + indent(out) << "$xfer += $output->writeFieldBegin(" + << "'" << (*f_iter)->get_name() << "', " << type_to_enum((*f_iter)->get_type()) + << ", " << (*f_iter)->get_key() << ");" << endl; + } + + // Write field contents + generate_serialize_field(out, *f_iter, "this->"); + + // Write field closer + if (!binary_inline_) { + indent(out) << "$xfer += $output->writeFieldEnd();" << endl; + } + + indent_down(); + indent(out) << "}" << endl; + } + + if (binary_inline_) { + out << indent() << "$output .= pack('c', " + << "TType::STOP);" << endl; + } else { + out << indent() << "$xfer += $output->writeFieldStop();" << endl << indent() + << "$xfer += $output->writeStructEnd();" << endl; + } + + out << indent() << "return $xfer;" << endl; + + indent_down(); + out << indent() << "}" << endl; +} + +void t_php_generator::generate_php_struct_read_validator(ostream& out, t_struct* tstruct) { + generate_php_struct_required_validator(out, tstruct, "_validateForRead", false); +} + +void t_php_generator::generate_php_struct_write_validator(ostream& out, t_struct* tstruct) { + generate_php_struct_required_validator(out, tstruct, "_validateForWrite", true); +} + +void t_php_generator::generate_php_struct_required_validator(ostream& out, + t_struct* tstruct, + std::string method_name, + bool write_mode) { + indent(out) << "private function " << method_name << "() {" << endl; + indent_up(); + + const vector<t_field*>& fields = tstruct->get_members(); + + if (fields.size() > 0) { + vector<t_field*>::const_iterator f_iter; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = (*f_iter); + if (field->get_req() == t_field::T_REQUIRED + || (field->get_req() == t_field::T_OPT_IN_REQ_OUT && write_mode)) { + indent(out) << "if ($this->" << field->get_name() << " === null) {" << endl; + indent_up(); + indent(out) << "throw new TProtocolException('Required field " << tstruct->get_name() << "." + << field->get_name() << " is unset!');" << endl; + indent_down(); + indent(out) << "}" << endl; + } + } + } + + indent_down(); + indent(out) << "}" << endl; +} + +void t_php_generator::generate_php_struct_json_serialize(ostream& out, + t_struct* tstruct, + bool is_result) { + indent(out) << "public function jsonSerialize() {" << endl; + indent_up(); + + if (needs_php_write_validator(tstruct, is_result)) { + indent(out) << "$this->_validateForWrite();" << endl; + } + + indent(out) << "$json = new stdClass;" << endl; + + const vector<t_field*>& fields = tstruct->get_members(); + + if (fields.size() > 0) { + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = (*f_iter); + t_type* type = field->get_type(); + const string& name = field->get_name(); + if (type->is_map()) { + t_type* key_type = ((t_map*)type)->get_key_type(); + if (!(key_type->is_base_type() || key_type->is_enum())) { + // JSON object keys must be strings. PHP's json_encode() + // function will convert any scalar key to strings, but + // we skip thrift maps with non-scalar keys. + continue; + } + } + indent(out) << "if ($this->" << name << " !== null) {" << endl; + indent_up(); + indent(out) << "$json->" << name << " = "; + if (type->is_map()) { + out << "(object)"; + } else { + out << type_to_cast(type); + } + out << "$this->" << name << ";" << endl; + indent_down(); + indent(out) << "}" << endl; + } + } + + indent(out) << "return $json;" << endl; + indent_down(); + + indent(out) << "}" << endl; +} + +int t_php_generator::get_php_num_required_fields(const vector<t_field*>& fields, bool write_mode) { + int num_req = 0; + + if (fields.size() > 0) { + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_req() == t_field::T_REQUIRED + || ((*f_iter)->get_req() == t_field::T_OPT_IN_REQ_OUT && write_mode)) { + ++num_req; + } + } + } + return num_req; +} + +bool t_php_generator::needs_php_write_validator(t_struct* tstruct, bool is_result) { + return (validate_ && !is_result && !tstruct->is_union() + && get_php_num_required_fields(tstruct->get_members(), true) > 0); +} + +bool t_php_generator::needs_php_read_validator(t_struct* tstruct, bool is_result) { + return (validate_ && !is_result + && (get_php_num_required_fields(tstruct->get_members(), false) > 0)); +} + +/** + * Generates a thrift service. + * + * @param tservice The service definition + */ +void t_php_generator::generate_service(t_service* tservice) { + if(classmap_) { + string f_service_name = package_dir_ + service_name_ + ".php"; + f_service_.open(f_service_name.c_str()); + generate_service_header(tservice, f_service_); + } + + // Generate the three main parts of the service (well, two for now in PHP) + generate_service_interface(tservice); + if (rest_) { + generate_service_rest(tservice); + } + generate_service_client(tservice); + generate_service_helpers(tservice); + if (phps_) { + generate_service_processor(tservice); + } + + if(classmap_) { + // Close service file + f_service_ << endl; + f_service_.close(); + } +} + +/** + * Generates a service server definition. + * + * @param tservice The service to generate a server for. + */ +void t_php_generator::generate_service_processor(t_service* tservice) { + ofstream_with_content_based_conditional_update& f_service_processor = f_service_; + if (!classmap_) { + string f_service_processor_name = package_dir_ + service_name_ + "Processor.php"; + f_service_processor.open(f_service_processor_name.c_str()); + generate_service_header(tservice, f_service_processor); + } + + // Generate the dispatch methods + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + string extends = ""; + string extends_processor = ""; + if (tservice->get_extends() != NULL) { + extends = tservice->get_extends()->get_name(); + extends_processor = " extends " + php_namespace(tservice->get_extends()->get_program()) + + extends + "Processor"; + } + + // Generate the header portion + f_service_processor << "class " << service_name_ << "Processor" << extends_processor << endl + << "{" << endl; + indent_up(); + + if (extends.empty()) { + f_service_processor << indent() << "protected $handler_ = null;" << endl; + } + + f_service_processor << indent() << "public function __construct($handler)"<< endl + << indent() << "{" << endl; + + indent_up(); + if (extends.empty()) { + f_service_processor << indent() << "$this->handler_ = $handler;" << endl; + } else { + f_service_processor << indent() << "parent::__construct($handler);" << endl; + } + + indent_down(); + f_service_processor << indent() << "}" << endl << endl; + + // Generate the server implementation + f_service_processor << indent() << "public function process($input, $output)" << endl + << indent() << "{" << endl; + indent_up(); + + f_service_processor << indent() << "$rseqid = 0;" << endl << indent() << "$fname = null;" << endl + << indent() << "$mtype = 0;" << endl << endl; + + if (binary_inline_) { + t_field ffname(g_type_string, "fname"); + t_field fmtype(g_type_i8, "mtype"); + t_field fseqid(g_type_i32, "rseqid"); + generate_deserialize_field(f_service_processor, &ffname, "", true); + generate_deserialize_field(f_service_processor, &fmtype, "", true); + generate_deserialize_field(f_service_processor, &fseqid, "", true); + } else { + f_service_processor << indent() << "$input->readMessageBegin($fname, $mtype, $rseqid);" << endl; + } + + // HOT: check for method implementation + f_service_processor << indent() << "$methodname = 'process_'.$fname;" << endl + << indent() << "if (!method_exists($this, $methodname)) {" << endl; + + indent_up(); + if (binary_inline_) { + f_service_processor << indent() << "throw new \\Exception('Function '.$fname.' not implemented.');" << endl; + } else { + f_service_processor << indent() << " $input->skip(" + << "TType::STRUCT);" << endl << indent() << " $input->readMessageEnd();" << endl + << indent() << " $x = new " + << "TApplicationException('Function '.$fname.' not implemented.', " + << "TApplicationException::UNKNOWN_METHOD);" << endl << indent() + << " $output->writeMessageBegin($fname, " + << "TMessageType::EXCEPTION, $rseqid);" << endl << indent() + << " $x->write($output);" << endl << indent() << " $output->writeMessageEnd();" + << endl << indent() << " $output->getTransport()->flush();" << endl << indent() + << " return;" << endl; + } + + indent_down(); + f_service_processor << indent() << "}" << endl + << indent() << "$this->$methodname($rseqid, $input, $output);" << endl + << indent() << "return true;" << endl; + + indent_down(); + f_service_processor << indent() << "}" << endl << endl; + + // Generate the process subfunctions + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_process_function(f_service_processor, tservice, *f_iter); + } + + indent_down(); + f_service_processor << "}" << endl; + + if (!classmap_) { + f_service_processor.close(); + } +} + +/** + * Generates a process function definition. + * + * @param tfunction The function to write a dispatcher for + */ +void t_php_generator::generate_process_function(std::ostream& out, t_service* tservice, t_function* tfunction) { + // Open function + out << indent() << "protected function process_" << tfunction->get_name() << "($seqid, $input, $output)" << endl + << indent() << "{" << endl; + indent_up(); + + string argsname = php_namespace(tservice->get_program()) + service_name_ + "_" + + tfunction->get_name() + "_args"; + string resultname = php_namespace(tservice->get_program()) + service_name_ + "_" + + tfunction->get_name() + "_result"; + + out << indent() << "$bin_accel = ($input instanceof " + << "TBinaryProtocolAccelerated) && function_exists('thrift_protocol_read_binary_after_message_begin');" + << endl; + out << indent() << "if ($bin_accel) {" << endl; + indent_up(); + + out << indent() << "$args = thrift_protocol_read_binary_after_message_begin(" <<endl; + + indent_up(); + out << indent() << "$input,"<<endl + << indent() << "'" << argsname << "'," << endl + << indent() << "$input->isStrictRead()" <<endl; + + indent_down(); + out << indent() <<");" << endl; + + indent_down(); + out << indent() << "} else {" << endl; + + indent_up(); + out << indent() << "$args = new " << argsname << "();" << endl + << indent() << "$args->read($input);" << endl; + + indent_down(); + out << indent() << "}" << endl; + + if (!binary_inline_) { + out << indent() << "$input->readMessageEnd();" << endl; + } + + t_struct* xs = tfunction->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + + // Declare result for non oneway function + if (!tfunction->is_oneway()) { + out << indent() << "$result = new " << resultname << "();" << endl; + } + + // Try block for a function with exceptions + if (xceptions.size() > 0) { + out << indent() << "try {" << endl; + indent_up(); + } + + // Generate the function call + t_struct* arg_struct = tfunction->get_arglist(); + const std::vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + + out << indent(); + if (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void()) { + out << "$result->success = "; + } + out << "$this->handler_->" << tfunction->get_name() << "("; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + out << ", "; + } + out << "$args->" << (*f_iter)->get_name(); + } + out << ");" << endl; + + if (!tfunction->is_oneway() && xceptions.size() > 0) { + indent_down(); + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + out << indent() << "} catch (" + << php_namespace(get_true_type((*x_iter)->get_type())->get_program()) + << (*x_iter)->get_type()->get_name() << " $" << (*x_iter)->get_name() << ") {" + << endl; + if (!tfunction->is_oneway()) { + indent_up(); + out << indent() << "$result->" << (*x_iter)->get_name() << " = $" + << (*x_iter)->get_name() << ";" << endl; + indent_down(); + out << indent(); + } + } + out << "}" << endl; + } + + // Shortcut out here for oneway functions + if (tfunction->is_oneway()) { + out << indent() << "return;" << endl; + indent_down(); + out << indent() << "}" << endl; + return; + } + + out << indent() << "$bin_accel = ($output instanceof " + << "TBinaryProtocolAccelerated) && function_exists('thrift_protocol_write_binary');" + << endl; + + out << indent() << "if ($bin_accel) {" << endl; + indent_up(); + + out << indent() << "thrift_protocol_write_binary(" << endl; + + indent_up(); + out << indent() << "$output,"<<endl + << indent() << "'" << tfunction->get_name()<< "'," <<endl + << indent() << "TMessageType::REPLY,"<< endl + << indent() << "$result," << endl + << indent() << "$seqid," << endl + << indent() << "$output->isStrictWrite()"<<endl; + + indent_down(); + out << indent() << ");" << endl; + + indent_down(); + out << indent() << "} else {" << endl; + indent_up(); + + // Serialize the request header + if (binary_inline_) { + out << indent() << "$buff = pack('N', (0x80010000 | " + << "TMessageType::REPLY)); " << endl << indent() << "$buff .= pack('N', strlen('" + << tfunction->get_name() << "'));" << endl << indent() << "$buff .= '" + << tfunction->get_name() << "';" << endl << indent() << "$buff .= pack('N', $seqid);" + << endl << indent() << "$result->write($buff);" << endl << indent() + << "$output->write($buff);" << endl << indent() << "$output->flush();" << endl; + } else { + out << indent() << "$output->writeMessageBegin('" << tfunction->get_name() << "', " + << "TMessageType::REPLY, $seqid);" << endl << indent() << "$result->write($output);" + << endl << indent() << "$output->writeMessageEnd();" << endl << indent() + << "$output->getTransport()->flush();" << endl; + } + + scope_down(out); + + // Close function + indent_down(); + out << indent() << "}" << endl; +} + +/** + * Generates helper functions for a service. + * + * @param tservice The service to generate a header definition for + */ +void t_php_generator::generate_service_helpers(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + ofstream_with_content_based_conditional_update& f_struct_definition = f_service_; + if (classmap_) { + f_struct_definition << "// HELPER FUNCTIONS AND STRUCTURES" << endl << endl; + } + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* ts = (*f_iter)->get_arglist(); + string name = ts->get_name(); + ts->set_name(service_name_ + "_" + name); + + if (!classmap_) { + string f_struct_definition_name = package_dir_ + service_name_ + "_" + name + ".php"; + f_struct_definition.open(f_struct_definition_name.c_str()); + generate_service_header(tservice, f_struct_definition); + } + + generate_php_struct_definition(f_struct_definition, ts); + if (!classmap_) { + f_struct_definition.close(); + } + + generate_php_function_helpers(tservice, *f_iter); + ts->set_name(name); + } +} + +/** + * Generates a struct and helpers for a function. + * + * @param tfunction The function + */ +void t_php_generator::generate_php_function_helpers(t_service* tservice, t_function* tfunction) { + if (!tfunction->is_oneway()) { + t_struct result(program_, service_name_ + "_" + tfunction->get_name() + "_result"); + t_field success(tfunction->get_returntype(), "success", 0); + if (!tfunction->get_returntype()->is_void()) { + result.append(&success); + } + + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& fields = xs->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + result.append(*f_iter); + } + + ofstream_with_content_based_conditional_update& f_struct_helper = f_service_; + if (!classmap_) { + string f_struct_helper_name = package_dir_ + result.get_name() + ".php"; + f_struct_helper.open(f_struct_helper_name.c_str()); + generate_service_header(tservice, f_struct_helper); + } + generate_php_struct_definition(f_struct_helper, &result, false, true); + if (!classmap_) { + f_struct_helper.close(); + } + } +} + +/** + * Generates a service interface definition. + * + * @param tservice The service to generate a header definition for + */ +void t_php_generator::generate_service_interface(t_service* tservice) { + ofstream_with_content_based_conditional_update& f_service_interface = f_service_; + if (!classmap_) { + string f_service_interface_name = package_dir_ + service_name_ + "If.php"; + f_service_interface.open(f_service_interface_name.c_str()); + generate_service_header(tservice, f_service_interface); + } + + string extends = ""; + string extends_if = ""; + if (tservice->get_extends() != NULL) { + extends = " extends " + php_namespace(tservice->get_extends()->get_program()) + + tservice->get_extends()->get_name(); + extends_if = " extends " + php_namespace(tservice->get_extends()->get_program()) + + tservice->get_extends()->get_name() + "If"; + } + generate_php_doc(f_service_interface, tservice); + f_service_interface << "interface " << php_namespace_declaration(tservice) << "If" << extends_if << endl + << "{" << endl; + + indent_up(); + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_php_doc(f_service_interface, *f_iter); + indent(f_service_interface) << "public function " << function_signature(*f_iter) << ";" << endl; + } + indent_down(); + f_service_interface << "}" << endl; + + // Close service interface file + if (!classmap_) { + f_service_interface.close(); + } +} + +/** + * Generates a REST interface + */ +void t_php_generator::generate_service_rest(t_service* tservice) { + ofstream_with_content_based_conditional_update& f_service_rest = f_service_; + if (!classmap_) { + string f_service_rest_name = package_dir_ + service_name_ + "Rest.php"; + f_service_rest.open(f_service_rest_name.c_str()); + generate_service_header(tservice, f_service_rest); + } + + string extends = ""; + string extends_if = ""; + if (tservice->get_extends() != NULL) { + extends = " extends " + php_namespace(tservice->get_extends()->get_program()) + + tservice->get_extends()->get_name(); + extends_if = " extends " + php_namespace(tservice->get_extends()->get_program()) + + tservice->get_extends()->get_name() + "Rest"; + } + f_service_rest << "class " << service_name_ << "Rest" << extends_if << endl + << "{" << endl; + indent_up(); + + if (extends.empty()) { + f_service_rest << indent() << "protected $impl_;" << endl << endl; + } + + f_service_rest << indent() << "public function __construct($impl) {" << endl << indent() + << " $this->impl_ = $impl;" << endl << indent() << "}" << endl << endl; + + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + indent(f_service_rest) << "public function " << (*f_iter)->get_name() << "($request) {" << endl; + indent_up(); + const vector<t_field*>& args = (*f_iter)->get_arglist()->get_members(); + vector<t_field*>::const_iterator a_iter; + for (a_iter = args.begin(); a_iter != args.end(); ++a_iter) { + t_type* atype = get_true_type((*a_iter)->get_type()); + string cast = type_to_cast(atype); + string req = "$request['" + (*a_iter)->get_name() + "']"; + if (atype->is_bool()) { + f_service_rest << indent() << "$" << (*a_iter)->get_name() << " = " << cast << "(!empty(" << req + << ") && (" << req << " !== 'false'));" << endl; + } else { + f_service_rest << indent() << "$" << (*a_iter)->get_name() << " = isset(" << req << ") ? " + << cast << req << " : null;" << endl; + } + if (atype->is_string() && ((t_base_type*)atype)->is_string_list()) { + f_service_rest << indent() << "$" << (*a_iter)->get_name() << " = explode(',', $" + << (*a_iter)->get_name() << ");" << endl; + } else if (atype->is_map() || atype->is_list()) { + f_service_rest << indent() << "$" << (*a_iter)->get_name() << " = json_decode($" + << (*a_iter)->get_name() << ", true);" << endl; + } else if (atype->is_set()) { + f_service_rest << indent() << "$" << (*a_iter)->get_name() << " = array_fill_keys(json_decode($" + << (*a_iter)->get_name() << ", true), 1);" << endl; + } else if (atype->is_struct() || atype->is_xception()) { + f_service_rest << indent() << "if ($" << (*a_iter)->get_name() << " !== null) {" << endl + << indent() << " $" << (*a_iter)->get_name() << " = new " + << php_namespace(atype->get_program()) << atype->get_name() << "(json_decode($" + << (*a_iter)->get_name() << ", true));" << endl << indent() << "}" << endl; + } + } + f_service_rest << indent() << "return $this->impl_->" << (*f_iter)->get_name() << "(" + << argument_list((*f_iter)->get_arglist(), false) << ");" << endl; + indent_down(); + indent(f_service_rest) << "}" << endl << endl; + } + indent_down(); + f_service_rest << "}" << endl << endl; + + // Close service rest file + f_service_rest << endl; + if (!classmap_) { + f_service_rest.close(); + } +} + +/** + * Generates a service client definition. + * + * @param tservice The service to generate a server for. + */ +void t_php_generator::generate_service_client(t_service* tservice) { + ofstream_with_content_based_conditional_update& f_service_client = f_service_; + if (!classmap_) { + string f_service_client_name = package_dir_ + service_name_ + "Client.php"; + f_service_client.open(f_service_client_name.c_str()); + generate_service_header(tservice, f_service_client); + } + + string extends = ""; + string extends_client = ""; + if (tservice->get_extends() != NULL) { + extends = tservice->get_extends()->get_name(); + extends_client = " extends " + php_namespace(tservice->get_extends()->get_program()) + extends + + "Client"; + } + + f_service_client << "class " << php_namespace_declaration(tservice) << "Client" << extends_client + << " implements " << php_namespace(tservice->get_program()) << service_name_ << "If" << endl + <<"{"<< endl; + indent_up(); + + // Private members + if (extends.empty()) { + f_service_client << indent() << "protected $input_ = null;" << endl << indent() + << "protected $output_ = null;" << endl << endl; + f_service_client << indent() << "protected $seqid_ = 0;" << endl << endl; + } + + // Constructor function + f_service_client << indent() << "public function __construct($input, $output = null)" << endl + << indent() << "{" << endl; + + indent_up(); + if (!extends.empty()) { + f_service_client << indent() << "parent::__construct($input, $output);" << endl; + } else { + f_service_client << indent() << "$this->input_ = $input;" << endl + << indent() << "$this->output_ = $output ? $output : $input;" << endl; + } + + indent_down(); + f_service_client << indent() << "}" << endl << endl; + + // Generate client method implementations + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* arg_struct = (*f_iter)->get_arglist(); + const vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator fld_iter; + string funname = (*f_iter)->get_name(); + + f_service_client << endl; + + // Open function + indent(f_service_client) << "public function " << function_signature(*f_iter) << endl; + scope_up(f_service_client); + indent(f_service_client) << "$this->send_" << funname << "("; + + bool first = true; + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + if (first) { + first = false; + } else { + f_service_client << ", "; + } + f_service_client << "$" << (*fld_iter)->get_name(); + } + f_service_client << ");" << endl; + + if (!(*f_iter)->is_oneway()) { + f_service_client << indent(); + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_client << "return "; + } + f_service_client << "$this->recv_" << funname << "();" << endl; + } + scope_down(f_service_client); + f_service_client << endl; + + indent(f_service_client) << "public function send_" << function_signature(*f_iter) << endl; + scope_up(f_service_client); + + std::string argsname = php_namespace(tservice->get_program()) + service_name_ + "_" + + (*f_iter)->get_name() + "_args"; + + f_service_client << indent() << "$args = new " << argsname << "();" << endl; + + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + f_service_client << indent() << "$args->" << (*fld_iter)->get_name() << " = $" + << (*fld_iter)->get_name() << ";" << endl; + } + + f_service_client << indent() << "$bin_accel = ($this->output_ instanceof " + << "TBinaryProtocolAccelerated) && function_exists('thrift_protocol_write_binary');" + << endl; + + f_service_client << indent() << "if ($bin_accel) {" << endl; + indent_up(); + + string messageType = (*f_iter)->is_oneway() ? "TMessageType::ONEWAY" : "TMessageType::CALL"; + + f_service_client << indent() << "thrift_protocol_write_binary(" << endl; + + indent_up(); + f_service_client << indent() << "$this->output_," << endl + << indent() << "'" << (*f_iter)->get_name() << "'," << endl + << indent() << messageType << "," << endl + << indent() << "$args," << endl + << indent() << "$this->seqid_," << endl + << indent() << "$this->output_->isStrictWrite()" << endl; + + indent_down(); + f_service_client << indent() << ");" << endl; + + indent_down(); + f_service_client << indent() << "} else {" << endl; + indent_up(); + + // Serialize the request header + if (binary_inline_) { + f_service_client << indent() << "$buff = pack('N', (0x80010000 | " << messageType << "));" << endl + << indent() << "$buff .= pack('N', strlen('" << funname << "'));" << endl + << indent() << "$buff .= '" << funname << "';" << endl << indent() + << "$buff .= pack('N', $this->seqid_);" << endl; + } else { + f_service_client << indent() << "$this->output_->writeMessageBegin('" << (*f_iter)->get_name() + << "', " << messageType << ", $this->seqid_);" << endl; + } + + // Write to the stream + if (binary_inline_) { + f_service_client << indent() << "$args->write($buff);" << endl << indent() + << "$this->output_->write($buff);" << endl << indent() + << "$this->output_->flush();" << endl; + } else { + f_service_client << indent() << "$args->write($this->output_);" << endl << indent() + << "$this->output_->writeMessageEnd();" << endl << indent() + << "$this->output_->getTransport()->flush();" << endl; + } + + scope_down(f_service_client); + + scope_down(f_service_client); + + if (!(*f_iter)->is_oneway()) { + std::string resultname = php_namespace(tservice->get_program()) + service_name_ + "_" + + (*f_iter)->get_name() + "_result"; + t_struct noargs(program_); + + t_function recv_function((*f_iter)->get_returntype(), + string("recv_") + (*f_iter)->get_name(), + &noargs); + // Open function + f_service_client << endl << indent() << "public function " << function_signature(&recv_function) + << endl; + scope_up(f_service_client); + + f_service_client << indent() << "$bin_accel = ($this->input_ instanceof " + << "TBinaryProtocolAccelerated)" + << " && function_exists('thrift_protocol_read_binary');" << endl; + + f_service_client << indent() << "if ($bin_accel) {" << endl; + + indent_up(); + f_service_client << indent() << "$result = thrift_protocol_read_binary(" << endl; + + indent_up(); + f_service_client << indent() << "$this->input_," << endl + << indent() << "'" << resultname << "'," << endl + << indent() << "$this->input_->isStrictRead()" << endl; + + indent_down(); + f_service_client << indent() << ");" << endl; + + indent_down(); + f_service_client << indent() << "} else {" << endl; + + indent_up(); + f_service_client << indent() << "$rseqid = 0;" << endl + << indent() << "$fname = null;" << endl + << indent() << "$mtype = 0;" << endl << endl; + + if (binary_inline_) { + t_field ffname(g_type_string, "fname"); + t_field fseqid(g_type_i32, "rseqid"); + f_service_client << indent() << "$ver = unpack('N', $this->input_->readAll(4));" << endl + << indent() << "$ver = $ver[1];" << endl << indent() << "$mtype = $ver & 0xff;" + << endl << indent() << "$ver = $ver & 0xffff0000;" << endl << indent() + << "if ($ver != 0x80010000) throw new " + << "TProtocolException('Bad version identifier: '.$ver, " + << "TProtocolException::BAD_VERSION);" << endl; + generate_deserialize_field(f_service_client, &ffname, "", true); + generate_deserialize_field(f_service_client, &fseqid, "", true); + } else { + f_service_client << indent() << "$this->input_->readMessageBegin($fname, $mtype, $rseqid);" << endl + << indent() << "if ($mtype == TMessageType::EXCEPTION) {" << endl; + + indent_up(); + f_service_client << indent() << "$x = new TApplicationException();" << endl + << indent() << "$x->read($this->input_);" << endl + << indent() << "$this->input_->readMessageEnd();" << endl + << indent() << "throw $x;" << endl; + indent_down(); + f_service_client << indent() << "}" << endl; + } + + f_service_client << indent() << "$result = new " << resultname << "();" << endl + << indent() << "$result->read($this->input_);" << endl; + + if (!binary_inline_) { + f_service_client << indent() << "$this->input_->readMessageEnd();" << endl; + } + + scope_down(f_service_client); + + // Careful, only return result if not a void function + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_client << indent() << "if ($result->success !== null) {" << endl; + + indent_up(); + f_service_client << indent() << "return $result->success;" << endl; + + indent_down(); + f_service_client << indent() << "}" << endl; + } + + t_struct* xs = (*f_iter)->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + f_service_client << indent() << "if ($result->" << (*x_iter)->get_name() << " !== null) {" << endl; + + indent_up(); + f_service_client << indent() << "throw $result->" << (*x_iter)->get_name() << ";" << endl; + + indent_down(); + f_service_client << indent() << "}" << endl; + } + + // Careful, only return _result if not a void function + if ((*f_iter)->get_returntype()->is_void()) { + indent(f_service_client) << "return;" << endl; + } else { + f_service_client << indent() << "throw new \\Exception(\"" << (*f_iter)->get_name() + << " failed: unknown result\");" << endl; + } + + // Close function + scope_down(f_service_client); + } + } + + indent_down(); + f_service_client << "}" << endl; + + // Close service client file + if (!classmap_) { + f_service_client.close(); + } +} + +/** + * Deserializes a field of any type. + */ +void t_php_generator::generate_deserialize_field(ostream& out, + t_field* tfield, + string prefix, + bool inclass) { + t_type* type = get_true_type(tfield->get_type()); + + if (type->is_void()) { + throw "CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name(); + } + + string name = prefix + tfield->get_name(); + + if (type->is_struct() || type->is_xception()) { + generate_deserialize_struct(out, (t_struct*)type, name); + } else { + + if (type->is_container()) { + generate_deserialize_container(out, type, name); + } else if (type->is_base_type() || type->is_enum()) { + + if (binary_inline_) { + std::string itrans = (inclass ? "$this->input_" : "$input"); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + out << indent() << "$len = unpack('N', " << itrans << "->readAll(4));" << endl + << indent() << "$len = $len[1];" << endl << indent() << "if ($len > 0x7fffffff) {" + << endl << indent() << " $len = 0 - (($len - 1) ^ 0xffffffff);" << endl << indent() + << "}" << endl << indent() << "$" << name << " = " << itrans << "->readAll($len);" + << endl; + break; + case t_base_type::TYPE_BOOL: + out << indent() << "$" << name << " = unpack('c', " << itrans << "->readAll(1));" + << endl << indent() << "$" << name << " = (bool)$" << name << "[1];" << endl; + break; + case t_base_type::TYPE_I8: + out << indent() << "$" << name << " = unpack('c', " << itrans << "->readAll(1));" + << endl << indent() << "$" << name << " = $" << name << "[1];" << endl; + break; + case t_base_type::TYPE_I16: + out << indent() << "$val = unpack('n', " << itrans << "->readAll(2));" << endl + << indent() << "$val = $val[1];" << endl << indent() << "if ($val > 0x7fff) {" + << endl << indent() << " $val = 0 - (($val - 1) ^ 0xffff);" << endl << indent() + << "}" << endl << indent() << "$" << name << " = $val;" << endl; + break; + case t_base_type::TYPE_I32: + out << indent() << "$val = unpack('N', " << itrans << "->readAll(4));" << endl + << indent() << "$val = $val[1];" << endl << indent() << "if ($val > 0x7fffffff) {" + << endl << indent() << " $val = 0 - (($val - 1) ^ 0xffffffff);" << endl << indent() + << "}" << endl << indent() << "$" << name << " = $val;" << endl; + break; + case t_base_type::TYPE_I64: + out << indent() << "$arr = unpack('N2', " << itrans << "->readAll(8));" << endl + << indent() << "if ($arr[1] & 0x80000000) {" << endl << indent() + << " $arr[1] = $arr[1] ^ 0xFFFFFFFF;" << endl << indent() + << " $arr[2] = $arr[2] ^ 0xFFFFFFFF;" << endl << indent() << " $" << name + << " = 0 - $arr[1]*4294967296 - $arr[2] - 1;" << endl << indent() << "} else {" + << endl << indent() << " $" << name << " = $arr[1]*4294967296 + $arr[2];" << endl + << indent() << "}" << endl; + break; + case t_base_type::TYPE_DOUBLE: + out << indent() << "$arr = unpack('d', strrev(" << itrans << "->readAll(8)));" << endl + << indent() << "$" << name << " = $arr[1];" << endl; + break; + default: + throw "compiler error: no PHP name for base type " + t_base_type::t_base_name(tbase) + + tfield->get_name(); + } + } else if (type->is_enum()) { + out << indent() << "$val = unpack('N', " << itrans << "->readAll(4));" << endl << indent() + << "$val = $val[1];" << endl << indent() << "if ($val > 0x7fffffff) {" << endl + << indent() << " $val = 0 - (($val - 1) ^ 0xffffffff);" << endl << indent() << "}" + << endl << indent() << "$" << name << " = $val;" << endl; + } + } else { + + indent(out) << "$xfer += $input->"; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + out << "readString($" << name << ");"; + break; + case t_base_type::TYPE_BOOL: + out << "readBool($" << name << ");"; + break; + case t_base_type::TYPE_I8: + out << "readByte($" << name << ");"; + break; + case t_base_type::TYPE_I16: + out << "readI16($" << name << ");"; + break; + case t_base_type::TYPE_I32: + out << "readI32($" << name << ");"; + break; + case t_base_type::TYPE_I64: + out << "readI64($" << name << ");"; + break; + case t_base_type::TYPE_DOUBLE: + out << "readDouble($" << name << ");"; + break; + default: + throw "compiler error: no PHP name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "readI32($" << name << ");"; + } + out << endl; + } + } else { + printf("DO NOT KNOW HOW TO DESERIALIZE FIELD '%s' TYPE '%s'\n", + tfield->get_name().c_str(), + type->get_name().c_str()); + } + } +} + +/** + * Generates an unserializer for a variable. This makes two key assumptions, + * first that there is a const char* variable named data that points to the + * buffer for deserialization, and that there is a variable protocol which + * is a reference to a TProtocol serialization object. + */ +void t_php_generator::generate_deserialize_struct(ostream& out, t_struct* tstruct, string prefix) { + out << indent() << "$" << prefix << " = new " << php_namespace(tstruct->get_program()) + << tstruct->get_name() << "();" << endl << indent() << "$xfer += $" << prefix + << "->read($input);" << endl; +} + +void t_php_generator::generate_deserialize_container(ostream& out, t_type* ttype, string prefix) { + string size = tmp("_size"); + string ktype = tmp("_ktype"); + string vtype = tmp("_vtype"); + string etype = tmp("_etype"); + + t_field fsize(g_type_i32, size); + t_field fktype(g_type_i8, ktype); + t_field fvtype(g_type_i8, vtype); + t_field fetype(g_type_i8, etype); + + out << indent() << "$" << prefix << " = array();" << endl << indent() << "$" << size << " = 0;" + << endl; + + // Declare variables, read header + if (ttype->is_map()) { + out << indent() << "$" << ktype << " = 0;" << endl << indent() << "$" << vtype << " = 0;" + << endl; + if (binary_inline_) { + generate_deserialize_field(out, &fktype); + generate_deserialize_field(out, &fvtype); + generate_deserialize_field(out, &fsize); + } else { + out << indent() << "$xfer += $input->readMapBegin(" + << "$" << ktype << ", $" << vtype << ", $" << size << ");" << endl; + } + } else if (ttype->is_set()) { + if (binary_inline_) { + generate_deserialize_field(out, &fetype); + generate_deserialize_field(out, &fsize); + } else { + out << indent() << "$" << etype << " = 0;" << endl << indent() + << "$xfer += $input->readSetBegin(" + << "$" << etype << ", $" << size << ");" << endl; + } + } else if (ttype->is_list()) { + if (binary_inline_) { + generate_deserialize_field(out, &fetype); + generate_deserialize_field(out, &fsize); + } else { + out << indent() << "$" << etype << " = 0;" << endl << indent() + << "$xfer += $input->readListBegin(" + << "$" << etype << ", $" << size << ");" << endl; + } + } + + // For loop iterates over elements + string i = tmp("_i"); + indent(out) << "for ($" << i << " = 0; $" << i << " < $" << size << "; ++$" << i << ") {" << endl; + + indent_up(); + + if (ttype->is_map()) { + generate_deserialize_map_element(out, (t_map*)ttype, prefix); + } else if (ttype->is_set()) { + generate_deserialize_set_element(out, (t_set*)ttype, prefix); + } else if (ttype->is_list()) { + generate_deserialize_list_element(out, (t_list*)ttype, prefix); + } + + scope_down(out); + + if (!binary_inline_) { + // Read container end + if (ttype->is_map()) { + indent(out) << "$xfer += $input->readMapEnd();" << endl; + } else if (ttype->is_set()) { + indent(out) << "$xfer += $input->readSetEnd();" << endl; + } else if (ttype->is_list()) { + indent(out) << "$xfer += $input->readListEnd();" << endl; + } + } +} + +/** + * Generates code to deserialize a map + */ +void t_php_generator::generate_deserialize_map_element(ostream& out, t_map* tmap, string prefix) { + string key = tmp("key"); + string val = tmp("val"); + t_field fkey(tmap->get_key_type(), key); + t_field fval(tmap->get_val_type(), val); + + indent(out) << declare_field(&fkey, true, true) << endl; + indent(out) << declare_field(&fval, true, true) << endl; + + generate_deserialize_field(out, &fkey); + generate_deserialize_field(out, &fval); + + indent(out) << "$" << prefix << "[$" << key << "] = $" << val << ";" << endl; +} + +void t_php_generator::generate_deserialize_set_element(ostream& out, t_set* tset, string prefix) { + string elem = tmp("elem"); + t_field felem(tset->get_elem_type(), elem); + + indent(out) << "$" << elem << " = null;" << endl; + + generate_deserialize_field(out, &felem); + + t_type* elem_type = tset->get_elem_type(); + if(php_is_scalar(elem_type)) { + indent(out) << "$" << prefix << "[$" << elem << "] = true;" << endl; + } else { + indent(out) << "$" << prefix << "[] = $" << elem << ";" << endl; + } +} + +void t_php_generator::generate_deserialize_list_element(ostream& out, + t_list* tlist, + string prefix) { + string elem = tmp("elem"); + t_field felem(tlist->get_elem_type(), elem); + + indent(out) << "$" << elem << " = null;" << endl; + + generate_deserialize_field(out, &felem); + + indent(out) << "$" << prefix << " []= $" << elem << ";" << endl; +} + +/** + * Serializes a field of any type. + * + * @param tfield The field to serialize + * @param prefix Name to prepend to field name + */ +void t_php_generator::generate_serialize_field(ostream& out, t_field* tfield, string prefix) { + t_type* type = get_true_type(tfield->get_type()); + + // Do nothing for void types + if (type->is_void()) { + throw "CANNOT GENERATE SERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name(); + } + + if (type->is_struct() || type->is_xception()) { + generate_serialize_struct(out, (t_struct*)type, prefix + tfield->get_name()); + } else if (type->is_container()) { + generate_serialize_container(out, type, prefix + tfield->get_name()); + } else if (type->is_base_type() || type->is_enum()) { + + string name = prefix + tfield->get_name(); + + if (binary_inline_) { + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + out << indent() << "$output .= pack('N', strlen($" << name << "));" << endl << indent() + << "$output .= $" << name << ";" << endl; + break; + case t_base_type::TYPE_BOOL: + out << indent() << "$output .= pack('c', $" << name << " ? 1 : 0);" << endl; + break; + case t_base_type::TYPE_I8: + out << indent() << "$output .= pack('c', $" << name << ");" << endl; + break; + case t_base_type::TYPE_I16: + out << indent() << "$output .= pack('n', $" << name << ");" << endl; + break; + case t_base_type::TYPE_I32: + out << indent() << "$output .= pack('N', $" << name << ");" << endl; + break; + case t_base_type::TYPE_I64: + out << indent() << "$output .= pack('N2', $" << name << " >> 32, $" << name + << " & 0xFFFFFFFF);" << endl; + break; + case t_base_type::TYPE_DOUBLE: + out << indent() << "$output .= strrev(pack('d', $" << name << "));" << endl; + break; + default: + throw "compiler error: no PHP name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << indent() << "$output .= pack('N', $" << name << ");" << endl; + } + } else { + + indent(out) << "$xfer += $output->"; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + out << "writeString($" << name << ");"; + break; + case t_base_type::TYPE_BOOL: + out << "writeBool($" << name << ");"; + break; + case t_base_type::TYPE_I8: + out << "writeByte($" << name << ");"; + break; + case t_base_type::TYPE_I16: + out << "writeI16($" << name << ");"; + break; + case t_base_type::TYPE_I32: + out << "writeI32($" << name << ");"; + break; + case t_base_type::TYPE_I64: + out << "writeI64($" << name << ");"; + break; + case t_base_type::TYPE_DOUBLE: + out << "writeDouble($" << name << ");"; + break; + default: + throw "compiler error: no PHP name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "writeI32($" << name << ");"; + } + out << endl; + } + } else { + printf("DO NOT KNOW HOW TO SERIALIZE FIELD '%s%s' TYPE '%s'\n", + prefix.c_str(), + tfield->get_name().c_str(), + type->get_name().c_str()); + } +} + +/** + * Serializes all the members of a struct. + * + * @param tstruct The struct to serialize + * @param prefix String prefix to attach to all fields + */ +void t_php_generator::generate_serialize_struct(ostream& out, t_struct* tstruct, string prefix) { + (void)tstruct; + indent(out) << "$xfer += $" << prefix << "->write($output);" << endl; +} + +/** + * Writes out a container + */ +void t_php_generator::generate_serialize_container(ostream& out, t_type* ttype, string prefix) { + if (ttype->is_map()) { + if (binary_inline_) { + out << indent() << "$output .= pack('c', " << type_to_enum(((t_map*)ttype)->get_key_type()) + << ");" << endl << indent() << "$output .= pack('c', " + << type_to_enum(((t_map*)ttype)->get_val_type()) << ");" << endl << indent() + << "$output .= strrev(pack('l', count($" << prefix << ")));" << endl; + } else { + indent(out) << "$output->writeMapBegin(" << type_to_enum(((t_map*)ttype)->get_key_type()) + << ", " << type_to_enum(((t_map*)ttype)->get_val_type()) << ", " + << "count($" << prefix << "));" << endl; + } + } else if (ttype->is_set()) { + if (binary_inline_) { + out << indent() << "$output .= pack('c', " << type_to_enum(((t_set*)ttype)->get_elem_type()) + << ");" << endl << indent() << "$output .= strrev(pack('l', count($" << prefix << ")));" + << endl; + + } else { + indent(out) << "$output->writeSetBegin(" << type_to_enum(((t_set*)ttype)->get_elem_type()) + << ", " + << "count($" << prefix << "));" << endl; + } + } else if (ttype->is_list()) { + if (binary_inline_) { + out << indent() << "$output .= pack('c', " << type_to_enum(((t_list*)ttype)->get_elem_type()) + << ");" << endl << indent() << "$output .= strrev(pack('l', count($" << prefix << ")));" + << endl; + + } else { + indent(out) << "$output->writeListBegin(" << type_to_enum(((t_list*)ttype)->get_elem_type()) + << ", " + << "count($" << prefix << "));" << endl; + } + } + + if (ttype->is_map()) { + string kiter = tmp("kiter"); + string viter = tmp("viter"); + indent(out) << "foreach ($" << prefix << " as " + << "$" << kiter << " => $" << viter << ") {" << endl; + indent_up(); + generate_serialize_map_element(out, (t_map*)ttype, kiter, viter); + scope_down(out); + } else if (ttype->is_set()) { + string iter = tmp("iter"); + string iter_val = tmp("iter"); + indent(out) << "foreach ($" << prefix << " as $" << iter << " => $" << iter_val << ") {" << endl; + indent_up(); + + t_type* elem_type = ((t_set*)ttype)->get_elem_type(); + if(php_is_scalar(elem_type)) { + generate_serialize_set_element(out, (t_set*)ttype, iter); + } else { + generate_serialize_set_element(out, (t_set*)ttype, iter_val); + } + scope_down(out); + } else if (ttype->is_list()) { + string iter = tmp("iter"); + indent(out) << "foreach ($" << prefix << " as $" << iter << ") {" << endl; + indent_up(); + generate_serialize_list_element(out, (t_list*)ttype, iter); + scope_down(out); + } + + if (!binary_inline_) { + if (ttype->is_map()) { + indent(out) << "$output->writeMapEnd();" << endl; + } else if (ttype->is_set()) { + indent(out) << "$output->writeSetEnd();" << endl; + } else if (ttype->is_list()) { + indent(out) << "$output->writeListEnd();" << endl; + } + } +} + +/** + * Serializes the members of a map. + * + */ +void t_php_generator::generate_serialize_map_element(ostream& out, + t_map* tmap, + string kiter, + string viter) { + t_field kfield(tmap->get_key_type(), kiter); + generate_serialize_field(out, &kfield, ""); + + t_field vfield(tmap->get_val_type(), viter); + generate_serialize_field(out, &vfield, ""); +} + +/** + * Serializes the members of a set. + */ +void t_php_generator::generate_serialize_set_element(ostream& out, t_set* tset, string iter) { + t_field efield(tset->get_elem_type(), iter); + generate_serialize_field(out, &efield, ""); +} + +/** + * Serializes the members of a list. + */ +void t_php_generator::generate_serialize_list_element(ostream& out, t_list* tlist, string iter) { + t_field efield(tlist->get_elem_type(), iter); + generate_serialize_field(out, &efield, ""); +} + +/** + * Emits a PHPDoc comment for the given contents + */ +void t_php_generator::generate_php_docstring_comment(ostream& out, string contents) { + generate_docstring_comment(out, "/**\n", " * ", contents, " */\n"); +} + +/** + * Emits a PHPDoc comment if the provided object has a doc in Thrift + */ +void t_php_generator::generate_php_doc(ostream& out, t_doc* tdoc) { + if (tdoc->has_doc()) { + generate_php_docstring_comment(out, tdoc->get_doc()); + } +} + +/** + * Emits a PHPDoc comment for a field + */ +void t_php_generator::generate_php_doc(ostream& out, t_field* field) { + stringstream ss; + + // prepend free-style doc if available + if (field->has_doc()) { + ss << field->get_doc() << endl; + } + + // append @var tag + t_type* type = get_true_type(field->get_type()); + ss << "@var " << type_to_phpdoc(type) << endl; + + generate_php_docstring_comment(out, ss.str()); +} + +/** + * Emits a PHPDoc comment for a function + */ +void t_php_generator::generate_php_doc(ostream& out, t_function* function) { + stringstream ss; + if (function->has_doc()) { + ss << function->get_doc() << endl; + } + + // generate parameter types doc + const vector<t_field*>& args = function->get_arglist()->get_members(); + vector<t_field*>::const_iterator a_iter; + for (a_iter = args.begin(); a_iter != args.end(); ++a_iter) { + t_field* arg = *a_iter; + ss << "@param " << type_to_phpdoc(arg->get_type()) << " $" << arg->get_name(); + if (arg->has_doc()) { + ss << " " << arg->get_doc(); + } + ss << endl; + } + + // generate return type doc + t_type* ret_type = function->get_returntype(); + if (!ret_type->is_void() || ret_type->has_doc()) { + ss << "@return " << type_to_phpdoc(ret_type); + if (ret_type->has_doc()) { + ss << " " << ret_type->get_doc(); + } + ss << endl; + } + + // generate exceptions doc + const vector<t_field*>& excs = function->get_xceptions()->get_members(); + vector<t_field*>::const_iterator e_iter; + for (e_iter = excs.begin(); e_iter != excs.end(); ++e_iter) { + t_field* exc = *e_iter; + ss << "@throws " << type_to_phpdoc(exc->get_type()); + if (exc->has_doc()) { + ss << " " << exc->get_doc(); + } + ss << endl; + } + + generate_docstring_comment(out, "/**\n", " * ", ss.str(), " */\n"); +} + +/** + * Declares a field, which may include initialization as necessary. + * + * @param ttype The type + */ +string t_php_generator::declare_field(t_field* tfield, bool init, bool obj) { + string result = "$" + tfield->get_name(); + if (init) { + t_type* type = get_true_type(tfield->get_type()); + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + break; + case t_base_type::TYPE_STRING: + result += " = ''"; + break; + case t_base_type::TYPE_BOOL: + result += " = false"; + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + result += " = 0"; + break; + case t_base_type::TYPE_DOUBLE: + result += " = 0.0"; + break; + default: + throw "compiler error: no PHP initializer for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + result += " = 0"; + } else if (type->is_container()) { + result += " = array()"; + } else if (type->is_struct() || type->is_xception()) { + if (obj) { + result += " = new " + php_namespace(type->get_program()) + type->get_name() + "()"; + } else { + result += " = null"; + } + } + } + return result + ";"; +} + +/** + * Renders a function signature of the form 'type name(args)' + * + * @param tfunction Function definition + * @return String of rendered function definition + */ +string t_php_generator::function_signature(t_function* tfunction, string prefix) { + return prefix + tfunction->get_name() + "(" + argument_list(tfunction->get_arglist()) + ")"; +} + +/** + * Renders a field list + */ +string t_php_generator::argument_list(t_struct* tstruct, bool addTypeHints) { + string result = ""; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + result += ", "; + } + + t_type* type = (*f_iter)->get_type(); + + // Set type name + if (addTypeHints) { + if (type->is_struct()) { + string className = php_namespace(type->get_program()) + + php_namespace_directory("Definition", false) + + classify(type->get_name()); + + result += className + " "; + } else if (type->is_container()) { + result += "array "; + } + } + + result += "$" + (*f_iter)->get_name(); + } + return result; +} + +/** + * Gets a typecast string for a particular type. + */ +string t_php_generator::type_to_cast(t_type* type) { + if (type->is_base_type()) { + t_base_type* btype = (t_base_type*)type; + switch (btype->get_base()) { + case t_base_type::TYPE_BOOL: + return "(bool)"; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + return "(int)"; + case t_base_type::TYPE_DOUBLE: + return "(double)"; + case t_base_type::TYPE_STRING: + return "(string)"; + default: + return ""; + } + } else if (type->is_enum()) { + return "(int)"; + } + return ""; +} + +/** + * Converts the parse type to a C++ enum string for the given type. + */ +string t_php_generator::type_to_enum(t_type* type) { + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + return "TType::STRING"; + case t_base_type::TYPE_BOOL: + return "TType::BOOL"; + case t_base_type::TYPE_I8: + return "TType::BYTE"; + case t_base_type::TYPE_I16: + return "TType::I16"; + case t_base_type::TYPE_I32: + return "TType::I32"; + case t_base_type::TYPE_I64: + return "TType::I64"; + case t_base_type::TYPE_DOUBLE: + return "TType::DOUBLE"; + } + } else if (type->is_enum()) { + return "TType::I32"; + } else if (type->is_struct() || type->is_xception()) { + return "TType::STRUCT"; + } else if (type->is_map()) { + return "TType::MAP"; + } else if (type->is_set()) { + return "TType::SET"; + } else if (type->is_list()) { + return "TType::LST"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +/** + * Converts the parse type to a PHPDoc string for the given type. + */ +string t_php_generator::type_to_phpdoc(t_type* type) { + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + return "void"; + case t_base_type::TYPE_STRING: + return "string"; + case t_base_type::TYPE_BOOL: + return "bool"; + case t_base_type::TYPE_I8: + return "int"; + case t_base_type::TYPE_I16: + return "int"; + case t_base_type::TYPE_I32: + return "int"; + case t_base_type::TYPE_I64: + return "int"; + case t_base_type::TYPE_DOUBLE: + return "double"; + } + } else if (type->is_enum()) { + return "int"; + } else if (type->is_struct() || type->is_xception()) { + return php_namespace(type->get_program()) + type->get_name(); + } else if (type->is_map()) { + return "array"; + } else if (type->is_set()) { + t_set* tset = static_cast<t_set*>(type); + t_type* t_elem = tset->get_elem_type(); + if (t_elem->is_container()) { + return "(" + type_to_phpdoc(t_elem) + ")[]"; + } else { + return type_to_phpdoc(t_elem) + "[]"; + } + } else if (type->is_list()) { + t_list* tlist = static_cast<t_list*>(type); + t_type* t_elem = tlist->get_elem_type(); + if (t_elem->is_container()) { + return "(" + type_to_phpdoc(t_elem) + ")[]"; + } else { + return type_to_phpdoc(t_elem) + "[]"; + } + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +THRIFT_REGISTER_GENERATOR( + php, + "PHP", + " inlined: Generate PHP inlined files\n" + " server: Generate PHP server stubs\n" + " oop: Generate PHP with object oriented subclasses\n" + " classmap: Generate old-style PHP files (use classmap autoloading)\n" + " rest: Generate PHP REST processors\n" + " nsglobal=NAME: Set global namespace\n" + " validate: Generate PHP validator methods\n" + " json: Generate JsonSerializable classes (requires PHP >= 5.4)\n") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_py_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_py_generator.cc new file mode 100644 index 000000000..982bca145 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_py_generator.cc @@ -0,0 +1,2781 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <string> +#include <fstream> +#include <iomanip> +#include <iostream> +#include <limits> +#include <vector> + +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sstream> +#include <algorithm> +#include "thrift/platform.h" +#include "thrift/version.h" +#include "thrift/generate/t_generator.h" + +using std::map; +using std::ostream; +using std::ostringstream; +using std::string; +using std::stringstream; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes + +/** + * Python code generator. + * + */ +class t_py_generator : public t_generator { +public: + t_py_generator(t_program* program, + const std::map<std::string, std::string>& parsed_options, + const std::string& option_string) + : t_generator (program) { + std::map<std::string, std::string>::const_iterator iter; + + gen_newstyle_ = true; + gen_utf8strings_ = true; + gen_dynbase_ = false; + gen_slots_ = false; + gen_tornado_ = false; + gen_zope_interface_ = false; + gen_twisted_ = false; + gen_dynamic_ = false; + coding_ = ""; + gen_dynbaseclass_ = ""; + gen_dynbaseclass_exc_ = ""; + gen_dynbaseclass_frozen_ = ""; + import_dynbase_ = ""; + package_prefix_ = ""; + for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + if( iter->first.compare("new_style") == 0) { + pwarning(0, "new_style is enabled by default, so the option will be removed in the near future.\n"); + } else if( iter->first.compare("old_style") == 0) { + gen_newstyle_ = false; + pwarning(0, "old_style is deprecated and may be removed in the future.\n"); + } else if( iter->first.compare("utf8strings") == 0) { + pwarning(0, "utf8strings is enabled by default, so the option will be removed in the near future.\n"); + } else if( iter->first.compare("no_utf8strings") == 0) { + gen_utf8strings_ = false; + } else if( iter->first.compare("slots") == 0) { + gen_slots_ = true; + } else if( iter->first.compare("package_prefix") == 0) { + package_prefix_ = iter->second; + } else if( iter->first.compare("dynamic") == 0) { + gen_dynamic_ = true; + gen_newstyle_ = false; // dynamic is newstyle + if( gen_dynbaseclass_.empty()) { + gen_dynbaseclass_ = "TBase"; + } + if( gen_dynbaseclass_frozen_.empty()) { + gen_dynbaseclass_frozen_ = "TFrozenBase"; + } + if( gen_dynbaseclass_exc_.empty()) { + gen_dynbaseclass_exc_ = "TExceptionBase"; + } + if( import_dynbase_.empty()) { + import_dynbase_ = "from thrift.protocol.TBase import TBase, TFrozenBase, TExceptionBase, TTransport\n"; + } + } else if( iter->first.compare("dynbase") == 0) { + gen_dynbase_ = true; + gen_dynbaseclass_ = (iter->second); + } else if( iter->first.compare("dynfrozen") == 0) { + gen_dynbaseclass_frozen_ = (iter->second); + } else if( iter->first.compare("dynexc") == 0) { + gen_dynbaseclass_exc_ = (iter->second); + } else if( iter->first.compare("dynimport") == 0) { + gen_dynbase_ = true; + import_dynbase_ = (iter->second); + } else if( iter->first.compare("zope.interface") == 0) { + gen_zope_interface_ = true; + } else if( iter->first.compare("twisted") == 0) { + gen_twisted_ = true; + gen_zope_interface_ = true; + } else if( iter->first.compare("tornado") == 0) { + gen_tornado_ = true; + } else if( iter->first.compare("coding") == 0) { + coding_ = iter->second; + } else { + throw "unknown option py:" + iter->first; + } + } + + if (gen_twisted_ && gen_tornado_) { + throw "at most one of 'twisted' and 'tornado' are allowed"; + } + + copy_options_ = option_string; + + if (gen_twisted_) { + out_dir_base_ = "gen-py.twisted"; + } else if (gen_tornado_) { + out_dir_base_ = "gen-py.tornado"; + } else { + out_dir_base_ = "gen-py"; + } + } + + std::string indent_str() const override { + return " "; + } + + /** + * Init and close methods + */ + + void init_generator() override; + void close_generator() override; + + /** + * Program-level generation functions + */ + + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_const(t_const* tconst) override; + void generate_struct(t_struct* tstruct) override; + void generate_forward_declaration(t_struct* tstruct) override; + void generate_xception(t_struct* txception) override; + void generate_service(t_service* tservice) override; + + std::string render_const_value(t_type* type, t_const_value* value); + + /** + * Struct generation code + */ + + void generate_py_struct(t_struct* tstruct, bool is_exception); + void generate_py_thrift_spec(std::ostream& out, t_struct* tstruct, bool is_exception); + void generate_py_struct_definition(std::ostream& out, + t_struct* tstruct, + bool is_xception = false); + void generate_py_struct_reader(std::ostream& out, t_struct* tstruct); + void generate_py_struct_writer(std::ostream& out, t_struct* tstruct); + void generate_py_struct_required_validator(std::ostream& out, t_struct* tstruct); + void generate_py_function_helpers(t_function* tfunction); + + /** + * Service-level generation functions + */ + + void generate_service_helpers(t_service* tservice); + void generate_service_interface(t_service* tservice); + void generate_service_client(t_service* tservice); + void generate_service_remote(t_service* tservice); + void generate_service_server(t_service* tservice); + void generate_process_function(t_service* tservice, t_function* tfunction); + + /** + * Serialization constructs + */ + + void generate_deserialize_field(std::ostream& out, + t_field* tfield, + std::string prefix = ""); + + void generate_deserialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); + + void generate_deserialize_container(std::ostream& out, t_type* ttype, std::string prefix = ""); + + void generate_deserialize_set_element(std::ostream& out, t_set* tset, std::string prefix = ""); + + void generate_deserialize_map_element(std::ostream& out, t_map* tmap, std::string prefix = ""); + + void generate_deserialize_list_element(std::ostream& out, + t_list* tlist, + std::string prefix = ""); + + void generate_serialize_field(std::ostream& out, t_field* tfield, std::string prefix = ""); + + void generate_serialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); + + void generate_serialize_container(std::ostream& out, t_type* ttype, std::string prefix = ""); + + void generate_serialize_map_element(std::ostream& out, + t_map* tmap, + std::string kiter, + std::string viter); + + void generate_serialize_set_element(std::ostream& out, t_set* tmap, std::string iter); + + void generate_serialize_list_element(std::ostream& out, t_list* tlist, std::string iter); + + void generate_python_docstring(std::ostream& out, t_struct* tstruct); + + void generate_python_docstring(std::ostream& out, t_function* tfunction); + + void generate_python_docstring(std::ostream& out, + t_doc* tdoc, + t_struct* tstruct, + const char* subheader); + + void generate_python_docstring(std::ostream& out, t_doc* tdoc); + + /** + * Helper rendering functions + */ + + std::string py_autogen_comment(); + std::string py_imports(); + std::string render_includes(); + std::string declare_argument(t_field* tfield); + std::string render_field_default_value(t_field* tfield); + std::string type_name(t_type* ttype); + std::string function_signature(t_function* tfunction, bool interface = false); + std::string argument_list(t_struct* tstruct, + std::vector<std::string>* pre = NULL, + std::vector<std::string>* post = NULL); + std::string type_to_enum(t_type* ttype); + std::string type_to_spec_args(t_type* ttype); + + static bool is_valid_namespace(const std::string& sub_namespace) { + return sub_namespace == "twisted"; + } + + static std::string get_real_py_module(const t_program* program, bool gen_twisted, std::string package_dir="") { + if (gen_twisted) { + std::string twisted_module = program->get_namespace("py.twisted"); + if (!twisted_module.empty()) { + return twisted_module; + } + } + + std::string real_module = program->get_namespace("py"); + if (real_module.empty()) { + return program->get_name(); + } + return package_dir + real_module; + } + + static bool is_immutable(t_type* ttype) { + return ttype->annotations_.find("python.immutable") != ttype->annotations_.end(); + } + +private: + + /** + * True if we should generate new-style classes. + */ + bool gen_newstyle_; + + /** + * True if we should generate dynamic style classes. + */ + bool gen_dynamic_; + + bool gen_dynbase_; + std::string gen_dynbaseclass_; + std::string gen_dynbaseclass_frozen_; + std::string gen_dynbaseclass_exc_; + + std::string import_dynbase_; + + bool gen_slots_; + + std::string copy_options_; + + /** + * True if we should generate code for use with zope.interface. + */ + bool gen_zope_interface_; + + /** + * True if we should generate Twisted-friendly RPC services. + */ + bool gen_twisted_; + + /** + * True if we should generate code for use with Tornado + */ + bool gen_tornado_; + + /** + * True if strings should be encoded using utf-8. + */ + bool gen_utf8strings_; + + /** + * specify generated file encoding + * eg. # -*- coding: utf-8 -*- + */ + string coding_; + + string package_prefix_; + + /** + * File streams + */ + + ofstream_with_content_based_conditional_update f_types_; + ofstream_with_content_based_conditional_update f_consts_; + ofstream_with_content_based_conditional_update f_service_; + + std::string package_dir_; + std::string module_; + +protected: + std::set<std::string> lang_keywords() const override { + std::string keywords[] = { "False", "None", "True", "and", "as", "assert", "break", "class", + "continue", "def", "del", "elif", "else", "except", "exec", "finally", "for", "from", + "global", "if", "import", "in", "is", "lambda", "nonlocal", "not", "or", "pass", "print", + "raise", "return", "try", "while", "with", "yield" }; + return std::set<std::string>(keywords, keywords + sizeof(keywords)/sizeof(keywords[0]) ); + } +}; + +/** + * Prepares for file generation by opening up the necessary file output + * streams. + * + * @param tprogram The program to generate + */ +void t_py_generator::init_generator() { + // Make output directory + string module = get_real_py_module(program_, gen_twisted_); + package_dir_ = get_out_dir(); + module_ = module; + while (true) { + // TODO: Do better error checking here. + MKDIR(package_dir_.c_str()); + std::ofstream init_py((package_dir_ + "/__init__.py").c_str(), std::ios_base::app); + init_py.close(); + if (module.empty()) { + break; + } + string::size_type pos = module.find('.'); + if (pos == string::npos) { + package_dir_ += "/"; + package_dir_ += module; + module.clear(); + } else { + package_dir_ += "/"; + package_dir_ += module.substr(0, pos); + module.erase(0, pos + 1); + } + } + + // Make output file + string f_types_name = package_dir_ + "/" + "ttypes.py"; + f_types_.open(f_types_name.c_str()); + + string f_consts_name = package_dir_ + "/" + "constants.py"; + f_consts_.open(f_consts_name.c_str()); + + string f_init_name = package_dir_ + "/__init__.py"; + ofstream_with_content_based_conditional_update f_init; + f_init.open(f_init_name.c_str()); + f_init << "__all__ = ['ttypes', 'constants'"; + vector<t_service*> services = program_->get_services(); + vector<t_service*>::iterator sv_iter; + for (sv_iter = services.begin(); sv_iter != services.end(); ++sv_iter) { + f_init << ", '" << (*sv_iter)->get_name() << "'"; + } + f_init << "]" << endl; + f_init.close(); + + // Print header + f_types_ << py_autogen_comment() << endl + << py_imports() << endl + << render_includes() << endl + << "from thrift.transport import TTransport" << endl + << import_dynbase_; + + f_types_ << "all_structs = []" << endl; + + f_consts_ << + py_autogen_comment() << endl << + py_imports() << endl << + "from .ttypes import *" << endl; +} + +/** + * Renders all the imports necessary for including another Thrift program + */ +string t_py_generator::render_includes() { + const vector<t_program*>& includes = program_->get_includes(); + string result = ""; + for (auto include : includes) { + result += "import " + get_real_py_module(include, gen_twisted_, package_prefix_) + ".ttypes\n"; + } + return result; +} + +/** + * Autogen'd comment + */ +string t_py_generator::py_autogen_comment() { + string coding; + if (!coding_.empty()) { + coding = "# -*- coding: " + coding_ + " -*-\n"; + } + return coding + std::string("#\n") + "# Autogenerated by Thrift Compiler (" + THRIFT_VERSION + ")\n" + + "#\n" + "# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n" + "#\n" + + "# options string: " + copy_options_ + "\n" + "#\n"; +} + +/** + * Prints standard thrift imports + */ +string t_py_generator::py_imports() { + ostringstream ss; + ss << "from thrift.Thrift import TType, TMessageType, TFrozenDict, TException, " + "TApplicationException" + << endl + << "from thrift.protocol.TProtocol import TProtocolException" + << endl + << "from thrift.TRecursive import fix_spec" + << endl; + + if (gen_utf8strings_) { + ss << endl << "import sys"; + } + return ss.str(); +} + +/** + * Closes the type files + */ +void t_py_generator::close_generator() { + + // Fix thrift_spec definitions for recursive structs. + f_types_ << "fix_spec(all_structs)" << endl; + f_types_ << "del all_structs" << endl; + + // Close types file + f_types_.close(); + f_consts_.close(); +} + +/** + * Generates a typedef. This is not done in Python, types are all implicit. + * + * @param ttypedef The type definition + */ +void t_py_generator::generate_typedef(t_typedef* ttypedef) { + (void)ttypedef; +} + +/** + * Generates code for an enumerated type. Done using a class to scope + * the values. + * + * @param tenum The enumeration + */ +void t_py_generator::generate_enum(t_enum* tenum) { + std::ostringstream to_string_mapping, from_string_mapping; + + f_types_ << endl << endl << "class " << tenum->get_name() << (gen_newstyle_ ? "(object)" : "") + << (gen_dynamic_ ? "(" + gen_dynbaseclass_ + ")" : "") << ":" << endl; + indent_up(); + generate_python_docstring(f_types_, tenum); + + to_string_mapping << indent() << "_VALUES_TO_NAMES = {" << endl; + from_string_mapping << indent() << "_NAMES_TO_VALUES = {" << endl; + + vector<t_enum_value*> constants = tenum->get_constants(); + vector<t_enum_value*>::iterator c_iter; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + int value = (*c_iter)->get_value(); + indent(f_types_) << (*c_iter)->get_name() << " = " << value << endl; + + // Dictionaries to/from string names of enums + to_string_mapping << indent() << indent() << value << ": \"" + << escape_string((*c_iter)->get_name()) << "\"," << endl; + from_string_mapping << indent() << indent() << '"' << escape_string((*c_iter)->get_name()) + << "\": " << value << ',' << endl; + } + to_string_mapping << indent() << "}" << endl; + from_string_mapping << indent() << "}" << endl; + + indent_down(); + f_types_ << endl; + f_types_ << to_string_mapping.str() << endl << from_string_mapping.str(); +} + +/** + * Generate a constant value + */ +void t_py_generator::generate_const(t_const* tconst) { + t_type* type = tconst->get_type(); + string name = tconst->get_name(); + t_const_value* value = tconst->get_value(); + + indent(f_consts_) << name << " = " << render_const_value(type, value); + f_consts_ << endl; +} + +/** + * Prints the value of a constant with the given type. Note that type checking + * is NOT performed in this function as it is always run beforehand using the + * validate_types method in main.cc + */ +string t_py_generator::render_const_value(t_type* type, t_const_value* value) { + type = get_true_type(type); + std::ostringstream out; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + if (((t_base_type*)type)->is_binary()) { + out << 'b'; + } + out << '"' << get_escaped_string(value) << '"'; + break; + case t_base_type::TYPE_BOOL: + out << (value->get_integer() > 0 ? "True" : "False"); + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + out << value->get_integer(); + break; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) { + out << "float(" << value->get_integer() << ")"; + } else { + out << emit_double_as_string(value->get_double()); + } + break; + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << value->get_integer(); + } else if (type->is_struct() || type->is_xception()) { + out << type_name(type) << "(**{" << endl; + indent_up(); + const vector<t_field*>& fields = ((t_struct*)type)->get_members(); + vector<t_field*>::const_iterator f_iter; + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + t_type* field_type = NULL; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field_type = (*f_iter)->get_type(); + } + } + if (field_type == NULL) { + throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string(); + } + indent(out) << render_const_value(g_type_string, v_iter->first) << ": " + << render_const_value(field_type, v_iter->second) << "," << endl; + } + indent_down(); + indent(out) << "})"; + } else if (type->is_map()) { + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + if (is_immutable(type)) { + out << "TFrozenDict("; + } + out << "{" << endl; + indent_up(); + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + indent(out) << render_const_value(ktype, v_iter->first) << ": " + << render_const_value(vtype, v_iter->second) << "," << endl; + } + indent_down(); + indent(out) << "}"; + if (is_immutable(type)) { + out << ")"; + } + } else if (type->is_list() || type->is_set()) { + t_type* etype; + if (type->is_list()) { + etype = ((t_list*)type)->get_elem_type(); + } else { + etype = ((t_set*)type)->get_elem_type(); + } + if (type->is_set()) { + if (is_immutable(type)) { + out << "frozen"; + } + out << "set("; + } + if (is_immutable(type) || type->is_set()) { + out << "(" << endl; + } else { + out << "[" << endl; + } + indent_up(); + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + indent(out) << render_const_value(etype, *v_iter) << "," << endl; + } + indent_down(); + if (is_immutable(type) || type->is_set()) { + indent(out) << ")"; + } else { + indent(out) << "]"; + } + if (type->is_set()) { + out << ")"; + } + } else { + throw "CANNOT GENERATE CONSTANT FOR TYPE: " + type->get_name(); + } + + return out.str(); +} + +/** + * Generates the "forward declarations" for python structs. + * These are actually full class definitions so that calls to generate_struct + * can add the thrift_spec field. This is needed so that all thrift_spec + * definitions are grouped at the end of the file to enable co-recursive structs. + */ +void t_py_generator::generate_forward_declaration(t_struct* tstruct) { + generate_py_struct(tstruct, tstruct->is_xception()); +} + +/** + * Generates a python struct + */ +void t_py_generator::generate_struct(t_struct* tstruct) { + generate_py_thrift_spec(f_types_, tstruct, false); +} + +/** + * Generates a struct definition for a thrift exception. Basically the same + * as a struct but extends the Exception class. + * + * @param txception The struct definition + */ +void t_py_generator::generate_xception(t_struct* txception) { + generate_py_thrift_spec(f_types_, txception, true); +} + +/** + * Generates a python struct + */ +void t_py_generator::generate_py_struct(t_struct* tstruct, bool is_exception) { + generate_py_struct_definition(f_types_, tstruct, is_exception); +} + + +/** + * Generate the thrift_spec for a struct + * For example, + * all_structs.append(Recursive) + * Recursive.thrift_spec = ( + * None, # 0 + * (1, TType.LIST, 'Children', (TType.STRUCT, (Recursive, None), False), None, ), # 1 + * ) + */ +void t_py_generator::generate_py_thrift_spec(ostream& out, + t_struct* tstruct, + bool /*is_exception*/) { + const vector<t_field*>& sorted_members = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator m_iter; + + // Add struct definition to list so thrift_spec can be fixed for recursive structures. + indent(out) << "all_structs.append(" << tstruct->get_name() << ")" << endl; + + if (sorted_members.empty() || (sorted_members[0]->get_key() >= 0)) { + indent(out) << tstruct->get_name() << ".thrift_spec = (" << endl; + indent_up(); + + int sorted_keys_pos = 0; + for (m_iter = sorted_members.begin(); m_iter != sorted_members.end(); ++m_iter) { + + for (; sorted_keys_pos != (*m_iter)->get_key(); sorted_keys_pos++) { + indent(out) << "None, # " << sorted_keys_pos << endl; + } + + indent(out) << "(" << (*m_iter)->get_key() << ", " << type_to_enum((*m_iter)->get_type()) + << ", " + << "'" << (*m_iter)->get_name() << "'" + << ", " << type_to_spec_args((*m_iter)->get_type()) << ", " + << render_field_default_value(*m_iter) << ", " + << ")," + << " # " << sorted_keys_pos << endl; + + sorted_keys_pos++; + } + + indent_down(); + indent(out) << ")" << endl; + } else { + indent(out) << tstruct->get_name() << ".thrift_spec = ()" << endl; + } +} + +/** + * Generates a struct definition for a thrift data type. + * + * @param tstruct The struct definition + */ +void t_py_generator::generate_py_struct_definition(ostream& out, + t_struct* tstruct, + bool is_exception) { + const vector<t_field*>& members = tstruct->get_members(); + const vector<t_field*>& sorted_members = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator m_iter; + + out << endl << endl << "class " << tstruct->get_name(); + if (is_exception) { + if (gen_dynamic_) { + out << "(" << gen_dynbaseclass_exc_ << ")"; + } else { + out << "(TException)"; + } + } else if (gen_dynamic_) { + if (is_immutable(tstruct)) { + out << "(" << gen_dynbaseclass_frozen_ << ")"; + } else { + out << "(" << gen_dynbaseclass_ << ")"; + } + } else if (gen_newstyle_) { + out << "(object)"; + } + out << ":" << endl; + indent_up(); + generate_python_docstring(out, tstruct); + + out << endl; + + /* + Here we generate the structure specification for the fastbinary codec. + These specifications have the following structure: + thrift_spec -> tuple of item_spec + item_spec -> None | (tag, type_enum, name, spec_args, default) + tag -> integer + type_enum -> TType.I32 | TType.STRING | TType.STRUCT | ... + name -> string_literal + default -> None # Handled by __init__ + spec_args -> None # For simple types + | (type_enum, spec_args) # Value type for list/set + | (type_enum, spec_args, type_enum, spec_args) + # Key and value for map + | (class_name, spec_args_ptr) # For struct/exception + class_name -> identifier # Basically a pointer to the class + spec_args_ptr -> expression # just class_name.spec_args + + TODO(dreiss): Consider making this work for structs with negative tags. + */ + + if (gen_slots_) { + indent(out) << "__slots__ = (" << endl; + indent_up(); + for (m_iter = sorted_members.begin(); m_iter != sorted_members.end(); ++m_iter) { + indent(out) << "'" << (*m_iter)->get_name() << "'," << endl; + } + indent_down(); + indent(out) << ")" << endl << endl; + } + + // TODO(dreiss): Look into generating an empty tuple instead of None + // for structures with no members. + // TODO(dreiss): Test encoding of structs where some inner structs + // don't have thrift_spec. + + if (members.size() > 0) { + out << endl; + out << indent() << "def __init__(self,"; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + out << " " << declare_argument(*m_iter) << ","; + } + out << "):" << endl; + + indent_up(); + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + // Initialize fields + t_type* type = (*m_iter)->get_type(); + if (!type->is_base_type() && !type->is_enum() && (*m_iter)->get_value() != NULL) { + indent(out) << "if " << (*m_iter)->get_name() << " is " + << "self.thrift_spec[" << (*m_iter)->get_key() << "][4]:" << endl; + indent_up(); + indent(out) << (*m_iter)->get_name() << " = " << render_field_default_value(*m_iter) + << endl; + indent_down(); + } + + if (is_immutable(tstruct)) { + if (gen_newstyle_ || gen_dynamic_) { + indent(out) << "super(" << tstruct->get_name() << ", self).__setattr__('" + << (*m_iter)->get_name() << "', " << (*m_iter)->get_name() << ")" << endl; + } else { + indent(out) << "self.__dict__['" << (*m_iter)->get_name() + << "'] = " << (*m_iter)->get_name() << endl; + } + } else { + indent(out) << "self." << (*m_iter)->get_name() << " = " << (*m_iter)->get_name() << endl; + } + } + + indent_down(); + } + + if (is_immutable(tstruct)) { + out << endl; + out << indent() << "def __setattr__(self, *args):" << endl + << indent() << indent_str() << "raise TypeError(\"can't modify immutable instance\")" << endl + << endl; + out << indent() << "def __delattr__(self, *args):" << endl + << indent() << indent_str() << "raise TypeError(\"can't modify immutable instance\")" << endl + << endl; + + // Hash all of the members in order, and also hash in the class + // to avoid collisions for stuff like single-field structures. + out << indent() << "def __hash__(self):" << endl + << indent() << indent_str() << "return hash(self.__class__) ^ hash(("; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + out << "self." << (*m_iter)->get_name() << ", "; + } + + out << "))" << endl; + } + + if (!gen_dynamic_) { + out << endl; + generate_py_struct_reader(out, tstruct); + generate_py_struct_writer(out, tstruct); + } + + // For exceptions only, generate a __str__ method. This is + // because when raised exceptions are printed to the console, __repr__ + // isn't used. See python bug #5882 + if (is_exception) { + out << endl; + out << indent() << "def __str__(self):" << endl + << indent() << indent_str() << "return repr(self)" << endl; + } + + if (!gen_slots_) { + out << endl; + // Printing utilities so that on the command line thrift + // structs look pretty like dictionaries + indent(out) << "def __repr__(self):" << endl; + indent_up(); + out << indent() << "L = ['%s=%r' % (key, value)" << endl + << indent() << " for key, value in self.__dict__.items()]" << endl + << indent() << "return '%s(%s)' % (self.__class__.__name__, ', '.join(L))" << endl + << endl; + indent_down(); + + // Equality and inequality methods that compare by value + out << indent() << "def __eq__(self, other):" << endl; + indent_up(); + out << indent() << "return isinstance(other, self.__class__) and " + "self.__dict__ == other.__dict__" << endl; + indent_down(); + out << endl; + + out << indent() << "def __ne__(self, other):" << endl; + indent_up(); + + out << indent() << "return not (self == other)" << endl; + indent_down(); + } else if (!gen_dynamic_) { + out << endl; + // no base class available to implement __eq__ and __repr__ and __ne__ for us + // so we must provide one that uses __slots__ + indent(out) << "def __repr__(self):" << endl; + indent_up(); + out << indent() << "L = ['%s=%r' % (key, getattr(self, key))" << endl + << indent() << " for key in self.__slots__]" << endl + << indent() << "return '%s(%s)' % (self.__class__.__name__, ', '.join(L))" << endl + << endl; + indent_down(); + + // Equality method that compares each attribute by value and type, walking __slots__ + out << indent() << "def __eq__(self, other):" << endl; + indent_up(); + out << indent() << "if not isinstance(other, self.__class__):" << endl + << indent() << indent_str() << "return False" << endl + << indent() << "for attr in self.__slots__:" << endl + << indent() << indent_str() << "my_val = getattr(self, attr)" << endl + << indent() << indent_str() << "other_val = getattr(other, attr)" << endl + << indent() << indent_str() << "if my_val != other_val:" << endl + << indent() << indent_str() << indent_str() << "return False" << endl + << indent() << "return True" << endl + << endl; + indent_down(); + + out << indent() << "def __ne__(self, other):" << endl + << indent() << indent_str() << "return not (self == other)" << endl; + } + indent_down(); +} + +/** + * Generates the read method for a struct + */ +void t_py_generator::generate_py_struct_reader(ostream& out, t_struct* tstruct) { + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + if (is_immutable(tstruct)) { + out << indent() << "@classmethod" << endl << indent() << "def read(cls, iprot):" << endl; + } else { + indent(out) << "def read(self, iprot):" << endl; + } + indent_up(); + + const char* id = is_immutable(tstruct) ? "cls" : "self"; + + indent(out) << "if iprot._fast_decode is not None " + "and isinstance(iprot.trans, TTransport.CReadableTransport) " + "and " + << id << ".thrift_spec is not None:" << endl; + indent_up(); + + if (is_immutable(tstruct)) { + indent(out) << "return iprot._fast_decode(None, iprot, [cls, cls.thrift_spec])" << endl; + } else { + indent(out) << "iprot._fast_decode(self, iprot, [self.__class__, self.thrift_spec])" << endl; + indent(out) << "return" << endl; + } + indent_down(); + + indent(out) << "iprot.readStructBegin()" << endl; + + if (is_immutable(tstruct)) { + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* tfield = *f_iter; + std::ostringstream result; + result << tfield->get_name() << " = "; + if (tfield->get_value() != NULL) { + result << render_field_default_value(tfield); + } else { + result << "None"; + } + indent(out) << result.str() << endl; + } + } + + // Loop over reading in fields + indent(out) << "while True:" << endl; + indent_up(); + + // Read beginning field marker + indent(out) << "(fname, ftype, fid) = iprot.readFieldBegin()" << endl; + + // Check for field STOP marker and break + indent(out) << "if ftype == TType.STOP:" << endl; + indent_up(); + indent(out) << "break" << endl; + indent_down(); + + // Switch statement on the field we are reading + bool first = true; + + // Generate deserialization code for known cases + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + out << indent() << "if "; + } else { + out << indent() << "elif "; + } + out << "fid == " << (*f_iter)->get_key() << ":" << endl; + indent_up(); + indent(out) << "if ftype == " << type_to_enum((*f_iter)->get_type()) << ":" << endl; + indent_up(); + if (is_immutable(tstruct)) { + generate_deserialize_field(out, *f_iter); + } else { + generate_deserialize_field(out, *f_iter, "self."); + } + indent_down(); + out << indent() << "else:" << endl << indent() << indent_str() << "iprot.skip(ftype)" << endl; + indent_down(); + } + + // In the default case we skip the field + out << indent() << "else:" << endl << indent() << indent_str() << "iprot.skip(ftype)" << endl; + + // Read field end marker + indent(out) << "iprot.readFieldEnd()" << endl; + + indent_down(); + + indent(out) << "iprot.readStructEnd()" << endl; + + if (is_immutable(tstruct)) { + indent(out) << "return cls(" << endl; + indent_up(); + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + indent(out) << (*f_iter)->get_name() << "=" << (*f_iter)->get_name() << "," << endl; + } + indent_down(); + indent(out) << ")" << endl; + } + + indent_down(); + out << endl; +} + +void t_py_generator::generate_py_struct_writer(ostream& out, t_struct* tstruct) { + string name = tstruct->get_name(); + const vector<t_field*>& fields = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator f_iter; + + indent(out) << "def write(self, oprot):" << endl; + indent_up(); + + indent(out) << "if oprot._fast_encode is not None and self.thrift_spec is not None:" << endl; + indent_up(); + + indent(out) + << "oprot.trans.write(oprot._fast_encode(self, [self.__class__, self.thrift_spec]))" + << endl; + indent(out) << "return" << endl; + indent_down(); + + indent(out) << "oprot.writeStructBegin('" << name << "')" << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + // Write field header + indent(out) << "if self." << (*f_iter)->get_name() << " is not None:" << endl; + indent_up(); + indent(out) << "oprot.writeFieldBegin(" + << "'" << (*f_iter)->get_name() << "', " << type_to_enum((*f_iter)->get_type()) + << ", " << (*f_iter)->get_key() << ")" << endl; + + // Write field contents + generate_serialize_field(out, *f_iter, "self."); + + // Write field closer + indent(out) << "oprot.writeFieldEnd()" << endl; + + indent_down(); + } + + // Write the struct map + out << indent() << "oprot.writeFieldStop()" << endl << indent() << "oprot.writeStructEnd()" + << endl; + + out << endl; + + indent_down(); + generate_py_struct_required_validator(out, tstruct); +} + +void t_py_generator::generate_py_struct_required_validator(ostream& out, t_struct* tstruct) { + indent(out) << "def validate(self):" << endl; + indent_up(); + + const vector<t_field*>& fields = tstruct->get_members(); + + if (fields.size() > 0) { + vector<t_field*>::const_iterator f_iter; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = (*f_iter); + if (field->get_req() == t_field::T_REQUIRED) { + indent(out) << "if self." << field->get_name() << " is None:" << endl; + indent(out) << indent_str() << "raise TProtocolException(message='Required field " + << field->get_name() << " is unset!')" << endl; + } + } + } + + indent(out) << "return" << endl; + indent_down(); +} + +/** + * Generates a thrift service. + * + * @param tservice The service definition + */ +void t_py_generator::generate_service(t_service* tservice) { + string f_service_name = package_dir_ + "/" + service_name_ + ".py"; + f_service_.open(f_service_name.c_str()); + + f_service_ << py_autogen_comment() << endl << py_imports() << endl; + + if (tservice->get_extends() != NULL) { + f_service_ << "import " + << get_real_py_module(tservice->get_extends()->get_program(), gen_twisted_, package_prefix_) << "." + << tservice->get_extends()->get_name() << endl; + } + + f_service_ << "import logging" << endl + << "from .ttypes import *" << endl + << "from thrift.Thrift import TProcessor" << endl + << "from thrift.transport import TTransport" << endl + << import_dynbase_; + if (gen_zope_interface_) { + f_service_ << "from zope.interface import Interface, implementer" << endl; + } + + if (gen_twisted_) { + f_service_ << "from twisted.internet import defer" << endl + << "from thrift.transport import TTwisted" << endl; + } else if (gen_tornado_) { + f_service_ << "from tornado import gen" << endl; + f_service_ << "from tornado import concurrent" << endl; + } + + f_service_ << "all_structs = []" << endl; + + // Generate the three main parts of the service + generate_service_interface(tservice); + generate_service_client(tservice); + generate_service_server(tservice); + generate_service_helpers(tservice); + generate_service_remote(tservice); + + // Close service file + f_service_ << "fix_spec(all_structs)" << endl + << "del all_structs" << endl << endl; + f_service_.close(); +} + +/** + * Generates helper functions for a service. + * + * @param tservice The service to generate a header definition for + */ +void t_py_generator::generate_service_helpers(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + f_service_ << endl << "# HELPER FUNCTIONS AND STRUCTURES" << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* ts = (*f_iter)->get_arglist(); + generate_py_struct_definition(f_service_, ts, false); + generate_py_thrift_spec(f_service_, ts, false); + generate_py_function_helpers(*f_iter); + } +} + +/** + * Generates a struct and helpers for a function. + * + * @param tfunction The function + */ +void t_py_generator::generate_py_function_helpers(t_function* tfunction) { + if (!tfunction->is_oneway()) { + t_struct result(program_, tfunction->get_name() + "_result"); + t_field success(tfunction->get_returntype(), "success", 0); + if (!tfunction->get_returntype()->is_void()) { + result.append(&success); + } + + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& fields = xs->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + result.append(*f_iter); + } + generate_py_struct_definition(f_service_, &result, false); + generate_py_thrift_spec(f_service_, &result, false); + } +} + +/** + * Generates a service interface definition. + * + * @param tservice The service to generate a header definition for + */ +void t_py_generator::generate_service_interface(t_service* tservice) { + string extends = ""; + string extends_if = ""; + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + extends_if = "(" + extends + ".Iface)"; + } else { + if (gen_zope_interface_) { + extends_if = "(Interface)"; + } else if (gen_newstyle_ || gen_dynamic_ || gen_tornado_) { + extends_if = "(object)"; + } + } + + f_service_ << endl << endl << "class Iface" << extends_if << ":" << endl; + indent_up(); + generate_python_docstring(f_service_, tservice); + vector<t_function*> functions = tservice->get_functions(); + if (functions.empty()) { + f_service_ << indent() << "pass" << endl; + } else { + vector<t_function*>::iterator f_iter; + bool first = true; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + if (first) { + first = false; + } else { + f_service_ << endl; + } + f_service_ << indent() << "def " << function_signature(*f_iter, true) << ":" << endl; + indent_up(); + generate_python_docstring(f_service_, (*f_iter)); + f_service_ << indent() << "pass" << endl; + indent_down(); + } + } + + indent_down(); +} + +/** + * Generates a service client definition. + * + * @param tservice The service to generate a server for. + */ +void t_py_generator::generate_service_client(t_service* tservice) { + string extends = ""; + string extends_client = ""; + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + if (gen_zope_interface_) { + extends_client = "(" + extends + ".Client)"; + } else { + extends_client = extends + ".Client, "; + } + } else { + if (gen_zope_interface_ && (gen_newstyle_ || gen_dynamic_)) { + extends_client = "(object)"; + } + } + + f_service_ << endl << endl; + + if (gen_zope_interface_) { + f_service_ << "@implementer(Iface)" << endl + << "class Client" << extends_client << ":" << endl + << endl; + } else { + f_service_ << "class Client(" << extends_client << "Iface):" << endl; + } + indent_up(); + generate_python_docstring(f_service_, tservice); + + // Constructor function + if (gen_twisted_) { + f_service_ << indent() << "def __init__(self, transport, oprot_factory):" << endl; + } else if (gen_tornado_) { + f_service_ << indent() + << "def __init__(self, transport, iprot_factory, oprot_factory=None):" << endl; + } else { + f_service_ << indent() << "def __init__(self, iprot, oprot=None):" << endl; + } + indent_up(); + if (extends.empty()) { + if (gen_twisted_) { + f_service_ << indent() << "self._transport = transport" << endl + << indent() << "self._oprot_factory = oprot_factory" << endl + << indent() << "self._seqid = 0" << endl + << indent() << "self._reqs = {}" << endl; + } else if (gen_tornado_) { + f_service_ << indent() << "self._transport = transport" << endl + << indent() << "self._iprot_factory = iprot_factory" << endl + << indent() << "self._oprot_factory = (oprot_factory if oprot_factory is not None" + << endl + << indent() << " else iprot_factory)" << endl + << indent() << "self._seqid = 0" << endl + << indent() << "self._reqs = {}" << endl + << indent() << "self._transport.io_loop.spawn_callback(self._start_receiving)" + << endl; + } else { + f_service_ << indent() << "self._iprot = self._oprot = iprot" << endl + << indent() << "if oprot is not None:" << endl + << indent() << indent_str() << "self._oprot = oprot" << endl + << indent() << "self._seqid = 0" << endl; + } + } else { + if (gen_twisted_) { + f_service_ << indent() << extends + << ".Client.__init__(self, transport, oprot_factory)" << endl; + } else if (gen_tornado_) { + f_service_ << indent() << extends + << ".Client.__init__(self, transport, iprot_factory, oprot_factory)" << endl; + } else { + f_service_ << indent() << extends << ".Client.__init__(self, iprot, oprot)" << endl; + } + } + indent_down(); + + if (gen_tornado_ && extends.empty()) { + f_service_ << endl << + indent() << "@gen.engine" << endl << + indent() << "def _start_receiving(self):" << endl; + indent_up(); + indent(f_service_) << "while True:" << endl; + indent_up(); + f_service_ << indent() << "try:" << endl + << indent() << indent_str() << "frame = yield self._transport.readFrame()" << endl + << indent() << "except TTransport.TTransportException as e:" << endl + << indent() << indent_str() << "for future in self._reqs.values():" << endl + << indent() << indent_str() << indent_str() << "future.set_exception(e)" << endl + << indent() << indent_str() << "self._reqs = {}" << endl + << indent() << indent_str() << "return" << endl + << indent() << "tr = TTransport.TMemoryBuffer(frame)" << endl + << indent() << "iprot = self._iprot_factory.getProtocol(tr)" << endl + << indent() << "(fname, mtype, rseqid) = iprot.readMessageBegin()" << endl + << indent() << "method = getattr(self, 'recv_' + fname)" << endl + << indent() << "future = self._reqs.pop(rseqid, None)" << endl + << indent() << "if not future:" << endl + << indent() << indent_str() << "# future has already been discarded" << endl + << indent() << indent_str() << "continue" << endl + << indent() << "try:" << endl + << indent() << indent_str() << "result = method(iprot, mtype, rseqid)" << endl + << indent() << "except Exception as e:" << endl + << indent() << indent_str() << "future.set_exception(e)" << endl + << indent() << "else:" << endl + << indent() << indent_str() << "future.set_result(result)" << endl; + indent_down(); + indent_down(); + } + + // Generate client method implementations + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* arg_struct = (*f_iter)->get_arglist(); + const vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator fld_iter; + string funname = (*f_iter)->get_name(); + + f_service_ << endl; + // Open function + indent(f_service_) << "def " << function_signature(*f_iter, false) << ":" << endl; + indent_up(); + generate_python_docstring(f_service_, (*f_iter)); + if (gen_twisted_) { + indent(f_service_) << "seqid = self._seqid = self._seqid + 1" << endl; + indent(f_service_) << "self._reqs[seqid] = defer.Deferred()" << endl << endl; + indent(f_service_) << "d = defer.maybeDeferred(self.send_" << funname; + + } else if (gen_tornado_) { + indent(f_service_) << "self._seqid += 1" << endl; + if (!(*f_iter)->is_oneway()) { + indent(f_service_) << "future = self._reqs[self._seqid] = concurrent.Future()" << endl; + } + indent(f_service_) << "self.send_" << funname << "("; + + } else { + indent(f_service_) << "self.send_" << funname << "("; + } + + bool first = true; + if (gen_twisted_) { + // we need a leading comma if there are args, since it's called as maybeDeferred(funcname, + // arg) + first = false; + } + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << (*fld_iter)->get_name(); + } + + f_service_ << ")" << endl; + + if (!(*f_iter)->is_oneway()) { + if (gen_twisted_) { + // nothing. See the next block. + } else if (gen_tornado_) { + indent(f_service_) << "return future" << endl; + } else { + f_service_ << indent(); + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_ << "return "; + } + f_service_ << "self.recv_" << funname << "()" << endl; + } + } + indent_down(); + + if (gen_twisted_) { + // This block injects the body of the send_<> method for twisted (and a cb/eb pair) + indent_up(); + indent(f_service_) << "d.addCallbacks(" << endl; + + indent_up(); + f_service_ << indent() << "callback=self.cb_send_" << funname << "," << endl << indent() + << "callbackArgs=(seqid,)," << endl << indent() << "errback=self.eb_send_" + << funname << "," << endl << indent() << "errbackArgs=(seqid,))" << endl; + indent_down(); + + indent(f_service_) << "return d" << endl; + indent_down(); + f_service_ << endl; + + indent(f_service_) << "def cb_send_" << funname << "(self, _, seqid):" << endl; + indent_up(); + if ((*f_iter)->is_oneway()) { + // if one-way, fire the deferred & remove it from _reqs + f_service_ << indent() << "d = self._reqs.pop(seqid)" << endl << indent() + << "d.callback(None)" << endl << indent() << "return d" << endl; + } else { + f_service_ << indent() << "return self._reqs[seqid]" << endl; + } + indent_down(); + f_service_ << endl; + + // add an errback to fail the request if the call to send_<> raised an exception + indent(f_service_) << "def eb_send_" << funname << "(self, f, seqid):" << endl; + indent_up(); + f_service_ << indent() << "d = self._reqs.pop(seqid)" << endl << indent() << "d.errback(f)" + << endl << indent() << "return d" << endl; + indent_down(); + } + + f_service_ << endl; + indent(f_service_) << "def send_" << function_signature(*f_iter, false) << ":" << endl; + indent_up(); + + std::string argsname = (*f_iter)->get_name() + "_args"; + std::string messageType = (*f_iter)->is_oneway() ? "TMessageType.ONEWAY" : "TMessageType.CALL"; + + // Serialize the request header + if (gen_twisted_ || gen_tornado_) { + f_service_ << indent() << "oprot = self._oprot_factory.getProtocol(self._transport)" << endl + << indent() << "oprot.writeMessageBegin('" << (*f_iter)->get_name() << "', " + << messageType << ", self._seqid)" << endl; + } else { + f_service_ << indent() << "self._oprot.writeMessageBegin('" << (*f_iter)->get_name() << "', " + << messageType << ", self._seqid)" << endl; + } + + f_service_ << indent() << "args = " << argsname << "()" << endl; + + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + f_service_ << indent() << "args." << (*fld_iter)->get_name() << " = " + << (*fld_iter)->get_name() << endl; + } + + // Write to the stream + if (gen_twisted_ || gen_tornado_) { + f_service_ << indent() << "args.write(oprot)" << endl << indent() << "oprot.writeMessageEnd()" + << endl << indent() << "oprot.trans.flush()" << endl; + } else { + f_service_ << indent() << "args.write(self._oprot)" << endl << indent() + << "self._oprot.writeMessageEnd()" << endl << indent() + << "self._oprot.trans.flush()" << endl; + } + + indent_down(); + + if (!(*f_iter)->is_oneway()) { + std::string resultname = (*f_iter)->get_name() + "_result"; + // Open function + f_service_ << endl; + if (gen_twisted_ || gen_tornado_) { + f_service_ << indent() << "def recv_" << (*f_iter)->get_name() + << "(self, iprot, mtype, rseqid):" << endl; + } else { + t_struct noargs(program_); + t_function recv_function((*f_iter)->get_returntype(), + string("recv_") + (*f_iter)->get_name(), + &noargs); + f_service_ << indent() << "def " << function_signature(&recv_function) << ":" << endl; + } + indent_up(); + + // TODO(mcslee): Validate message reply here, seq ids etc. + + if (gen_twisted_) { + f_service_ << indent() << "d = self._reqs.pop(rseqid)" << endl; + } else if (gen_tornado_) { + } else { + f_service_ << indent() << "iprot = self._iprot" << endl << indent() + << "(fname, mtype, rseqid) = iprot.readMessageBegin()" << endl; + } + + f_service_ << indent() << "if mtype == TMessageType.EXCEPTION:" << endl + << indent() << indent_str() << "x = TApplicationException()" << endl; + + if (gen_twisted_) { + f_service_ << indent() << indent_str() << "x.read(iprot)" << endl << indent() + << indent_str() << "iprot.readMessageEnd()" << endl << indent() << indent_str() << "return d.errback(x)" + << endl << indent() << "result = " << resultname << "()" << endl << indent() + << "result.read(iprot)" << endl << indent() << "iprot.readMessageEnd()" << endl; + } else { + f_service_ << indent() << indent_str() << "x.read(iprot)" << endl << indent() + << indent_str() << "iprot.readMessageEnd()" << endl << indent() << indent_str() << "raise x" << endl + << indent() << "result = " << resultname << "()" << endl << indent() + << "result.read(iprot)" << endl << indent() << "iprot.readMessageEnd()" << endl; + } + + // Careful, only return _result if not a void function + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_ << indent() << "if result.success is not None:" << endl; + if (gen_twisted_) { + f_service_ << indent() << indent_str() << "return d.callback(result.success)" << endl; + } else { + f_service_ << indent() << indent_str() << "return result.success" << endl; + } + } + + t_struct* xs = (*f_iter)->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + const string& xname = (*x_iter)->get_name(); + f_service_ << indent() << "if result." << xname << " is not None:" << endl; + if (gen_twisted_) { + f_service_ << indent() << indent_str() << "return d.errback(result." << xname << ")" + << endl; + } else { + f_service_ << indent() << indent_str() << "raise result." << xname << "" << endl; + } + } + + // Careful, only return _result if not a void function + if ((*f_iter)->get_returntype()->is_void()) { + if (gen_twisted_) { + f_service_ << indent() << "return d.callback(None)" << endl; + } else { + f_service_ << indent() << "return" << endl; + } + } else { + if (gen_twisted_) { + f_service_ + << indent() + << "return d.errback(TApplicationException(TApplicationException.MISSING_RESULT, \"" + << (*f_iter)->get_name() << " failed: unknown result\"))" << endl; + } else { + f_service_ << indent() + << "raise TApplicationException(TApplicationException.MISSING_RESULT, \"" + << (*f_iter)->get_name() << " failed: unknown result\")" << endl; + } + } + + // Close function + indent_down(); + } + } + + indent_down(); +} + +/** + * Generates a command line tool for making remote requests + * + * @param tservice The service to generate a remote for. + */ +void t_py_generator::generate_service_remote(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + // Get all function from parents + t_service* parent = tservice->get_extends(); + while (parent != NULL) { + vector<t_function*> p_functions = parent->get_functions(); + functions.insert(functions.end(), p_functions.begin(), p_functions.end()); + parent = parent->get_extends(); + } + vector<t_function*>::iterator f_iter; + + string f_remote_name = package_dir_ + "/" + service_name_ + "-remote"; + ofstream_with_content_based_conditional_update f_remote; + f_remote.open(f_remote_name.c_str()); + + f_remote << + "#!/usr/bin/env python" << endl << + py_autogen_comment() << endl << + "import sys" << endl << + "import pprint" << endl << + "if sys.version_info[0] > 2:" << endl << + indent_str() << "from urllib.parse import urlparse" << endl << + "else:" << endl << + indent_str() << "from urlparse import urlparse" << endl << + "from thrift.transport import TTransport, TSocket, TSSLSocket, THttpClient" << endl << + "from thrift.protocol.TBinaryProtocol import TBinaryProtocol" << endl << + endl; + + f_remote << + "from " << module_ << " import " << service_name_ << endl << + "from " << module_ << ".ttypes import *" << endl << + endl; + + f_remote << + "if len(sys.argv) <= 1 or sys.argv[1] == '--help':" << endl << + indent_str() << "print('')" << endl << + indent_str() << "print('Usage: ' + sys.argv[0] + ' [-h host[:port]] [-u url] [-f[ramed]] [-s[sl]] [-novalidate] [-ca_certs certs] [-keyfile keyfile] [-certfile certfile] function [arg1 [arg2...]]')" << endl << + indent_str() << "print('')" << endl << + indent_str() << "print('Functions:')" << endl; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + f_remote << indent_str() << "print(' " << (*f_iter)->get_returntype()->get_name() << " " + << (*f_iter)->get_name() << "("; + t_struct* arg_struct = (*f_iter)->get_arglist(); + const std::vector<t_field*>& args = arg_struct->get_members(); + std::vector<t_field*>::size_type num_args = args.size(); + bool first = true; + for (std::vector<t_field*>::size_type i = 0; i < num_args; ++i) { + if (first) { + first = false; + } else { + f_remote << ", "; + } + f_remote << args[i]->get_type()->get_name() << " " << args[i]->get_name(); + } + f_remote << ")')" << endl; + } + f_remote << indent_str() << "print('')" << endl << indent_str() << "sys.exit(0)" << endl << endl; + + f_remote << "pp = pprint.PrettyPrinter(indent=2)" << endl + << "host = 'localhost'" << endl + << "port = 9090" << endl + << "uri = ''" << endl + << "framed = False" << endl + << "ssl = False" << endl + << "validate = True" << endl + << "ca_certs = None" << endl + << "keyfile = None" << endl + << "certfile = None" << endl + << "http = False" << endl + << "argi = 1" << endl + << endl + << "if sys.argv[argi] == '-h':" << endl + << indent_str() << "parts = sys.argv[argi + 1].split(':')" << endl + << indent_str() << "host = parts[0]" << endl + << indent_str() << "if len(parts) > 1:" << endl + << indent_str() << indent_str() << "port = int(parts[1])" << endl + << indent_str() << "argi += 2" << endl + << endl + << "if sys.argv[argi] == '-u':" << endl + << indent_str() << "url = urlparse(sys.argv[argi + 1])" << endl + << indent_str() << "parts = url[1].split(':')" << endl + << indent_str() << "host = parts[0]" << endl + << indent_str() << "if len(parts) > 1:" << endl + << indent_str() << indent_str() << "port = int(parts[1])" << endl + << indent_str() << "else:" << endl + << indent_str() << indent_str() << "port = 80" << endl + << indent_str() << "uri = url[2]" << endl + << indent_str() << "if url[4]:" << endl + << indent_str() << indent_str() << "uri += '?%s' % url[4]" << endl + << indent_str() << "http = True" << endl + << indent_str() << "argi += 2" << endl + << endl + << "if sys.argv[argi] == '-f' or sys.argv[argi] == '-framed':" << endl + << indent_str() << "framed = True" << endl + << indent_str() << "argi += 1" << endl + << endl + << "if sys.argv[argi] == '-s' or sys.argv[argi] == '-ssl':" << endl + << indent_str() << "ssl = True" << endl + << indent_str() << "argi += 1" << endl + << endl + << "if sys.argv[argi] == '-novalidate':" << endl + << indent_str() << "validate = False" << endl + << indent_str() << "argi += 1" << endl + << endl + << "if sys.argv[argi] == '-ca_certs':" << endl + << indent_str() << "ca_certs = sys.argv[argi+1]" << endl + << indent_str() << "argi += 2" << endl + << endl + << "if sys.argv[argi] == '-keyfile':" << endl + << indent_str() << "keyfile = sys.argv[argi+1]" << endl + << indent_str() << "argi += 2" << endl + << endl + << "if sys.argv[argi] == '-certfile':" << endl + << indent_str() << "certfile = sys.argv[argi+1]" << endl + << indent_str() << "argi += 2" << endl + << endl + << "cmd = sys.argv[argi]" << endl + << "args = sys.argv[argi + 1:]" << endl + << endl + << "if http:" << endl + << indent_str() << "transport = THttpClient.THttpClient(host, port, uri)" << endl + << "else:" << endl + << indent_str() << "if ssl:" << endl + << indent_str() << indent_str() << "socket = TSSLSocket.TSSLSocket(host, port, " + "validate=validate, ca_certs=ca_certs, keyfile=keyfile, certfile=certfile)" + << endl + << indent_str() << "else:" << endl + << indent_str() << indent_str() << "socket = TSocket.TSocket(host, port)" << endl + << indent_str() << "if framed:" << endl + << indent_str() << indent_str() << "transport = TTransport.TFramedTransport(socket)" << endl + << indent_str() << "else:" << endl + << indent_str() << indent_str() << "transport = TTransport.TBufferedTransport(socket)" << endl + << "protocol = TBinaryProtocol(transport)" << endl + << "client = " << service_name_ << ".Client(protocol)" << endl + << "transport.open()" << endl + << endl; + + // Generate the dispatch methods + bool first = true; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + if (first) { + first = false; + } else { + f_remote << "el"; + } + + t_struct* arg_struct = (*f_iter)->get_arglist(); + const std::vector<t_field*>& args = arg_struct->get_members(); + std::vector<t_field*>::size_type num_args = args.size(); + + f_remote << "if cmd == '" << (*f_iter)->get_name() << "':" << endl; + indent_up(); + f_remote << indent() << "if len(args) != " << num_args << ":" << endl + << indent() << indent_str() << "print('" << (*f_iter)->get_name() << " requires " << num_args + << " args')" << endl + << indent() << indent_str() << "sys.exit(1)" << endl + << indent() << "pp.pprint(client." << (*f_iter)->get_name() << "("; + indent_down(); + bool first_arg = true; + for (std::vector<t_field*>::size_type i = 0; i < num_args; ++i) { + if (first_arg) + first_arg = false; + else + f_remote << " "; + if (args[i]->get_type()->is_string()) { + f_remote << "args[" << i << "],"; + } else { + f_remote << "eval(args[" << i << "]),"; + } + } + f_remote << "))" << endl; + + f_remote << endl; + } + + if (functions.size() > 0) { + f_remote << "else:" << endl; + f_remote << indent_str() << "print('Unrecognized method %s' % cmd)" << endl; + f_remote << indent_str() << "sys.exit(1)" << endl; + f_remote << endl; + } + + f_remote << "transport.close()" << endl; + + // Close service file + f_remote.close(); + +#ifndef _MSC_VER + + // Make file executable, love that bitwise OR action + chmod(f_remote_name.c_str(), + S_IRUSR | S_IWUSR | S_IXUSR +#ifndef _WIN32 + | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH +#endif + ); + +#endif // _MSC_VER +} + +/** + * Generates a service server definition. + * + * @param tservice The service to generate a server for. + */ +void t_py_generator::generate_service_server(t_service* tservice) { + // Generate the dispatch methods + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + string extends = ""; + string extends_processor = ""; + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + extends_processor = extends + ".Processor, "; + } + + f_service_ << endl << endl; + + // Generate the header portion + if (gen_zope_interface_) { + f_service_ << "@implementer(Iface)" << endl + << "class Processor(" << extends_processor << "TProcessor):" << endl; + } else { + f_service_ << "class Processor(" << extends_processor << "Iface, TProcessor):" << endl; + } + + indent_up(); + + indent(f_service_) << "def __init__(self, handler):" << endl; + indent_up(); + if (extends.empty()) { + if (gen_zope_interface_) { + f_service_ << indent() << "self._handler = Iface(handler)" << endl; + } else { + f_service_ << indent() << "self._handler = handler" << endl; + } + + f_service_ << indent() << "self._processMap = {}" << endl; + } else { + if (gen_zope_interface_) { + f_service_ << indent() << extends << ".Processor.__init__(self, Iface(handler))" << endl; + } else { + f_service_ << indent() << extends << ".Processor.__init__(self, handler)" << endl; + } + } + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + f_service_ << indent() << "self._processMap[\"" << (*f_iter)->get_name() + << "\"] = Processor.process_" << (*f_iter)->get_name() << endl; + } + f_service_ << indent() << "self._on_message_begin = None" << endl; + indent_down(); + f_service_ << endl; + + f_service_ << indent() << "def on_message_begin(self, func):" << endl; + indent_up(); + f_service_ << indent() << "self._on_message_begin = func" << endl; + indent_down(); + f_service_ << endl; + + // Generate the server implementation + f_service_ << indent() << "def process(self, iprot, oprot):" << endl; + indent_up(); + + f_service_ << indent() << "(name, type, seqid) = iprot.readMessageBegin()" << endl; + f_service_ << indent() << "if self._on_message_begin:" << endl; + indent_up(); + f_service_ << indent() << "self._on_message_begin(name, type, seqid)" << endl; + indent_down(); + + // TODO(mcslee): validate message + + // HOT: dictionary function lookup + f_service_ << indent() << "if name not in self._processMap:" << endl; + indent_up(); + f_service_ << indent() << "iprot.skip(TType.STRUCT)" << endl + << indent() << "iprot.readMessageEnd()" << endl + << indent() + << "x = TApplicationException(TApplicationException.UNKNOWN_METHOD, 'Unknown " + "function %s' % (name))" + << endl + << indent() << "oprot.writeMessageBegin(name, TMessageType.EXCEPTION, seqid)" << endl + << indent() << "x.write(oprot)" << endl + << indent() << "oprot.writeMessageEnd()" << endl + << indent() << "oprot.trans.flush()" << endl; + + if (gen_twisted_) { + f_service_ << indent() << "return defer.succeed(None)" << endl; + } else { + f_service_ << indent() << "return" << endl; + } + indent_down(); + + f_service_ << indent() << "else:" << endl; + + if (gen_twisted_ || gen_tornado_) { + f_service_ << indent() << indent_str() + << "return self._processMap[name](self, seqid, iprot, oprot)" << endl; + } else { + f_service_ << indent() << indent_str() << "self._processMap[name](self, seqid, iprot, oprot)" + << endl; + + // Read end of args field, the T_STOP, and the struct close + f_service_ << indent() << "return True" << endl; + } + + indent_down(); + + // Generate the process subfunctions + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + f_service_ << endl; + generate_process_function(tservice, *f_iter); + } + + indent_down(); +} + +/** + * Generates a process function definition. + * + * @param tfunction The function to write a dispatcher for + */ +void t_py_generator::generate_process_function(t_service* tservice, t_function* tfunction) { + (void)tservice; + // Open function + if (gen_tornado_) { + f_service_ << indent() << "@gen.coroutine" << endl << indent() << "def process_" + << tfunction->get_name() << "(self, seqid, iprot, oprot):" << endl; + } else { + f_service_ << indent() << "def process_" << tfunction->get_name() + << "(self, seqid, iprot, oprot):" << endl; + } + + indent_up(); + + string argsname = tfunction->get_name() + "_args"; + string resultname = tfunction->get_name() + "_result"; + + f_service_ << indent() << "args = " << argsname << "()" << endl << indent() << "args.read(iprot)" + << endl << indent() << "iprot.readMessageEnd()" << endl; + + t_struct* xs = tfunction->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + + // Declare result for non oneway function + if (!tfunction->is_oneway()) { + f_service_ << indent() << "result = " << resultname << "()" << endl; + } + + if (gen_twisted_) { + // Generate the function call + t_struct* arg_struct = tfunction->get_arglist(); + const std::vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + + f_service_ << indent() << "d = defer.maybeDeferred(self._handler." << tfunction->get_name() + << ", "; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << "args." << (*f_iter)->get_name(); + } + f_service_ << ")" << endl; + + if (tfunction->is_oneway()) { + f_service_ << indent() << "d.addErrback(self.handle_exception_" << tfunction->get_name() + << ", seqid)" << endl; + } else { + f_service_ << indent() << "d.addCallback(self.write_results_success_" << tfunction->get_name() + << ", result, seqid, oprot)" << endl + << indent() << "d.addErrback(self.write_results_exception_" + << tfunction->get_name() << ", result, seqid, oprot)" << endl; + } + f_service_ << indent() << "return d" << endl << endl; + + indent_down(); + + if (tfunction->is_oneway()) { + indent(f_service_) << "def handle_exception_" << tfunction->get_name() + << "(self, error, seqid):" << endl; + } else { + indent(f_service_) << "def write_results_success_" << tfunction->get_name() + << "(self, success, result, seqid, oprot):" << endl; + indent_up(); + if (!tfunction->get_returntype()->is_void()) { + f_service_ << indent() << "result.success = success" << endl; + } + f_service_ << indent() << "oprot.writeMessageBegin(\"" << tfunction->get_name() + << "\", TMessageType.REPLY, seqid)" << endl + << indent() << "result.write(oprot)" << endl + << indent() << "oprot.writeMessageEnd()" << endl + << indent() << "oprot.trans.flush()" << endl + << endl; + indent_down(); + + indent(f_service_) << "def write_results_exception_" << tfunction->get_name() + << "(self, error, result, seqid, oprot):" << endl; + } + indent_up(); + if (!tfunction->is_oneway()) { + f_service_ << indent() << "msg_type = TMessageType.REPLY" << endl; + } + f_service_ << indent() << "try:" << endl; + + // Kinda absurd + f_service_ << indent() << indent_str() << "error.raiseException()" << endl; + if (!tfunction->is_oneway()) { + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + const string& xname = (*x_iter)->get_name(); + f_service_ << indent() << "except " << type_name((*x_iter)->get_type()) << " as " << xname + << ":" << endl; + indent_up(); + f_service_ << indent() << "result." << xname << " = " << xname << endl; + indent_down(); + } + } + f_service_ << indent() << "except TTransport.TTransportException:" << endl + << indent() << indent_str() << "raise" << endl; + if (!tfunction->is_oneway()) { + f_service_ << indent() << "except TApplicationException as ex:" << endl + << indent() << indent_str() + << "logging.exception('TApplication exception in handler')" << endl + << indent() << indent_str() << "msg_type = TMessageType.EXCEPTION" << endl + << indent() << indent_str() << "result = ex" << endl + << indent() << "except Exception:" << endl + << indent() << indent_str() + << "logging.exception('Unexpected exception in handler')" << endl + << indent() << indent_str() << "msg_type = TMessageType.EXCEPTION" << endl + << indent() << indent_str() + << "result = TApplicationException(TApplicationException.INTERNAL_ERROR, " + "'Internal error')" + << endl + << indent() << "oprot.writeMessageBegin(\"" << tfunction->get_name() + << "\", msg_type, seqid)" << endl + << indent() << "result.write(oprot)" << endl + << indent() << "oprot.writeMessageEnd()" << endl + << indent() << "oprot.trans.flush()" << endl; + } else { + f_service_ << indent() << "except Exception:" << endl + << indent() << indent_str() + << "logging.exception('Exception in oneway handler')" << endl; + } + indent_down(); + + } else if (gen_tornado_) { + // Generate the function call + t_struct* arg_struct = tfunction->get_arglist(); + const std::vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + + if (!tfunction->is_oneway()) { + indent(f_service_) << "msg_type = TMessageType.REPLY" << endl; + } + f_service_ << indent() << "try:" << endl; + indent_up(); + f_service_ << indent(); + if (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void()) { + f_service_ << "result.success = "; + } + f_service_ << "yield gen.maybe_future(self._handler." << tfunction->get_name() << "("; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << "args." << (*f_iter)->get_name(); + } + f_service_ << "))" << endl; + + indent_down(); + if (!tfunction->is_oneway()) { + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + const string& xname = (*x_iter)->get_name(); + f_service_ << indent() << "except " << type_name((*x_iter)->get_type()) << " as " << xname + << ":" << endl + << indent() << indent_str() << "result." << xname << " = " << xname << endl; + } + } + f_service_ << indent() << "except TTransport.TTransportException:" << endl + << indent() << indent_str() << "raise" << endl; + if (!tfunction->is_oneway()) { + f_service_ << indent() << "except TApplicationException as ex:" << endl + << indent() << indent_str() + << "logging.exception('TApplication exception in handler')" << endl + << indent() << indent_str() << "msg_type = TMessageType.EXCEPTION" << endl + << indent() << indent_str() << "result = ex" << endl + << indent() << "except Exception:" << endl + << indent() << indent_str() + << "logging.exception('Unexpected exception in handler')" << endl + << indent() << indent_str() << "msg_type = TMessageType.EXCEPTION" << endl + << indent() << indent_str() + << "result = TApplicationException(TApplicationException.INTERNAL_ERROR, " + "'Internal error')" + << endl; + } else { + f_service_ << indent() << "except Exception:" << endl + << indent() << indent_str() + << "logging.exception('Exception in oneway handler')" << endl; + } + + if (!tfunction->is_oneway()) { + f_service_ << indent() << "oprot.writeMessageBegin(\"" << tfunction->get_name() + << "\", msg_type, seqid)" << endl + << indent() << "result.write(oprot)" << endl + << indent() << "oprot.writeMessageEnd()" << endl + << indent() << "oprot.trans.flush()" << endl; + } + + // Close function + indent_down(); + + } else { // py + // Try block for a function with exceptions + // It also catches arbitrary exceptions raised by handler method to propagate them to the client + f_service_ << indent() << "try:" << endl; + indent_up(); + + // Generate the function call + t_struct* arg_struct = tfunction->get_arglist(); + const std::vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + + f_service_ << indent(); + if (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void()) { + f_service_ << "result.success = "; + } + f_service_ << "self._handler." << tfunction->get_name() << "("; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << "args." << (*f_iter)->get_name(); + } + f_service_ << ")" << endl; + if (!tfunction->is_oneway()) { + f_service_ << indent() << "msg_type = TMessageType.REPLY" << endl; + } + + indent_down(); + f_service_ << indent() + << "except TTransport.TTransportException:" << endl + << indent() << indent_str() << "raise" << endl; + + if (!tfunction->is_oneway()) { + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + const string& xname = (*x_iter)->get_name(); + f_service_ << indent() << "except " << type_name((*x_iter)->get_type()) << " as " << xname + << ":" << endl; + indent_up(); + f_service_ << indent() << "msg_type = TMessageType.REPLY" << endl; + f_service_ << indent() << "result." << xname << " = " << xname << endl; + indent_down(); + } + + f_service_ << indent() << "except TApplicationException as ex:" << endl + << indent() << indent_str() + << "logging.exception('TApplication exception in handler')" << endl + << indent() << indent_str() << "msg_type = TMessageType.EXCEPTION" << endl + << indent() << indent_str() << "result = ex" << endl + << indent() << "except Exception:" << endl + << indent() << indent_str() + << "logging.exception('Unexpected exception in handler')" << endl + << indent() << indent_str() << "msg_type = TMessageType.EXCEPTION" << endl + << indent() << indent_str() + << "result = TApplicationException(TApplicationException.INTERNAL_ERROR, " + "'Internal error')" + << endl + << indent() << "oprot.writeMessageBegin(\"" << tfunction->get_name() + << "\", msg_type, seqid)" << endl + << indent() << "result.write(oprot)" << endl + << indent() << "oprot.writeMessageEnd()" << endl + << indent() << "oprot.trans.flush()" << endl; + } else { + f_service_ << indent() << "except Exception:" << endl + << indent() << indent_str() << "logging.exception('Exception in oneway handler')" << endl; + } + + // Close function + indent_down(); + } +} + +/** + * Deserializes a field of any type. + */ +void t_py_generator::generate_deserialize_field(ostream& out, + t_field* tfield, + string prefix) { + t_type* type = get_true_type(tfield->get_type()); + + if (type->is_void()) { + throw "CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name(); + } + + string name = prefix + tfield->get_name(); + + if (type->is_struct() || type->is_xception()) { + generate_deserialize_struct(out, (t_struct*)type, name); + } else if (type->is_container()) { + generate_deserialize_container(out, type, name); + } else if (type->is_base_type() || type->is_enum()) { + indent(out) << name << " = iprot."; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + out << "readBinary()"; + } else if(!gen_utf8strings_) { + out << "readString()"; + } else { + out << "readString().decode('utf-8') if sys.version_info[0] == 2 else iprot.readString()"; + } + break; + case t_base_type::TYPE_BOOL: + out << "readBool()"; + break; + case t_base_type::TYPE_I8: + out << "readByte()"; + break; + case t_base_type::TYPE_I16: + out << "readI16()"; + break; + case t_base_type::TYPE_I32: + out << "readI32()"; + break; + case t_base_type::TYPE_I64: + out << "readI64()"; + break; + case t_base_type::TYPE_DOUBLE: + out << "readDouble()"; + break; + default: + throw "compiler error: no Python name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "readI32()"; + } + out << endl; + + } else { + printf("DO NOT KNOW HOW TO DESERIALIZE FIELD '%s' TYPE '%s'\n", + tfield->get_name().c_str(), + type->get_name().c_str()); + } +} + +/** + * Generates an unserializer for a struct, calling read() + */ +void t_py_generator::generate_deserialize_struct(ostream& out, t_struct* tstruct, string prefix) { + if (is_immutable(tstruct)) { + out << indent() << prefix << " = " << type_name(tstruct) << ".read(iprot)" << endl; + } else { + out << indent() << prefix << " = " << type_name(tstruct) << "()" << endl + << indent() << prefix << ".read(iprot)" << endl; + } +} + +/** + * Serialize a container by writing out the header followed by + * data and then a footer. + */ +void t_py_generator::generate_deserialize_container(ostream& out, t_type* ttype, string prefix) { + string size = tmp("_size"); + string ktype = tmp("_ktype"); + string vtype = tmp("_vtype"); + string etype = tmp("_etype"); + + t_field fsize(g_type_i32, size); + t_field fktype(g_type_i8, ktype); + t_field fvtype(g_type_i8, vtype); + t_field fetype(g_type_i8, etype); + + // Declare variables, read header + if (ttype->is_map()) { + out << indent() << prefix << " = {}" << endl << indent() << "(" << ktype << ", " << vtype + << ", " << size << ") = iprot.readMapBegin()" << endl; + } else if (ttype->is_set()) { + out << indent() << prefix << " = set()" << endl << indent() << "(" << etype << ", " << size + << ") = iprot.readSetBegin()" << endl; + } else if (ttype->is_list()) { + out << indent() << prefix << " = []" << endl << indent() << "(" << etype << ", " << size + << ") = iprot.readListBegin()" << endl; + } + + // For loop iterates over elements + string i = tmp("_i"); + indent(out) << + "for " << i << " in range(" << size << "):" << endl; + + indent_up(); + + if (ttype->is_map()) { + generate_deserialize_map_element(out, (t_map*)ttype, prefix); + } else if (ttype->is_set()) { + generate_deserialize_set_element(out, (t_set*)ttype, prefix); + } else if (ttype->is_list()) { + generate_deserialize_list_element(out, (t_list*)ttype, prefix); + } + + indent_down(); + + // Read container end + if (ttype->is_map()) { + indent(out) << "iprot.readMapEnd()" << endl; + if (is_immutable(ttype)) { + indent(out) << prefix << " = TFrozenDict(" << prefix << ")" << endl; + } + } else if (ttype->is_set()) { + indent(out) << "iprot.readSetEnd()" << endl; + if (is_immutable(ttype)) { + indent(out) << prefix << " = frozenset(" << prefix << ")" << endl; + } + } else if (ttype->is_list()) { + if (is_immutable(ttype)) { + indent(out) << prefix << " = tuple(" << prefix << ")" << endl; + } + indent(out) << "iprot.readListEnd()" << endl; + } +} + +/** + * Generates code to deserialize a map + */ +void t_py_generator::generate_deserialize_map_element(ostream& out, t_map* tmap, string prefix) { + string key = tmp("_key"); + string val = tmp("_val"); + t_field fkey(tmap->get_key_type(), key); + t_field fval(tmap->get_val_type(), val); + + generate_deserialize_field(out, &fkey); + generate_deserialize_field(out, &fval); + + indent(out) << prefix << "[" << key << "] = " << val << endl; +} + +/** + * Write a set element + */ +void t_py_generator::generate_deserialize_set_element(ostream& out, t_set* tset, string prefix) { + string elem = tmp("_elem"); + t_field felem(tset->get_elem_type(), elem); + + generate_deserialize_field(out, &felem); + + indent(out) << prefix << ".add(" << elem << ")" << endl; +} + +/** + * Write a list element + */ +void t_py_generator::generate_deserialize_list_element(ostream& out, + t_list* tlist, + string prefix) { + string elem = tmp("_elem"); + t_field felem(tlist->get_elem_type(), elem); + + generate_deserialize_field(out, &felem); + + indent(out) << prefix << ".append(" << elem << ")" << endl; +} + +/** + * Serializes a field of any type. + * + * @param tfield The field to serialize + * @param prefix Name to prepend to field name + */ +void t_py_generator::generate_serialize_field(ostream& out, t_field* tfield, string prefix) { + t_type* type = get_true_type(tfield->get_type()); + + // Do nothing for void types + if (type->is_void()) { + throw "CANNOT GENERATE SERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name(); + } + + if (type->is_struct() || type->is_xception()) { + generate_serialize_struct(out, (t_struct*)type, prefix + tfield->get_name()); + } else if (type->is_container()) { + generate_serialize_container(out, type, prefix + tfield->get_name()); + } else if (type->is_base_type() || type->is_enum()) { + + string name = prefix + tfield->get_name(); + + indent(out) << "oprot."; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + out << "writeBinary(" << name << ")"; + } else if (!gen_utf8strings_) { + out << "writeString(" << name << ")"; + } else { + out << "writeString(" << name << ".encode('utf-8') if sys.version_info[0] == 2 else " << name << ")"; + } + break; + case t_base_type::TYPE_BOOL: + out << "writeBool(" << name << ")"; + break; + case t_base_type::TYPE_I8: + out << "writeByte(" << name << ")"; + break; + case t_base_type::TYPE_I16: + out << "writeI16(" << name << ")"; + break; + case t_base_type::TYPE_I32: + out << "writeI32(" << name << ")"; + break; + case t_base_type::TYPE_I64: + out << "writeI64(" << name << ")"; + break; + case t_base_type::TYPE_DOUBLE: + out << "writeDouble(" << name << ")"; + break; + default: + throw "compiler error: no Python name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "writeI32(" << name << ")"; + } + out << endl; + } else { + printf("DO NOT KNOW HOW TO SERIALIZE FIELD '%s%s' TYPE '%s'\n", + prefix.c_str(), + tfield->get_name().c_str(), + type->get_name().c_str()); + } +} + +/** + * Serializes all the members of a struct. + * + * @param tstruct The struct to serialize + * @param prefix String prefix to attach to all fields + */ +void t_py_generator::generate_serialize_struct(ostream& out, t_struct* tstruct, string prefix) { + (void)tstruct; + indent(out) << prefix << ".write(oprot)" << endl; +} + +void t_py_generator::generate_serialize_container(ostream& out, t_type* ttype, string prefix) { + if (ttype->is_map()) { + indent(out) << "oprot.writeMapBegin(" << type_to_enum(((t_map*)ttype)->get_key_type()) << ", " + << type_to_enum(((t_map*)ttype)->get_val_type()) << ", " + << "len(" << prefix << "))" << endl; + } else if (ttype->is_set()) { + indent(out) << "oprot.writeSetBegin(" << type_to_enum(((t_set*)ttype)->get_elem_type()) << ", " + << "len(" << prefix << "))" << endl; + } else if (ttype->is_list()) { + indent(out) << "oprot.writeListBegin(" << type_to_enum(((t_list*)ttype)->get_elem_type()) + << ", " + << "len(" << prefix << "))" << endl; + } + + if (ttype->is_map()) { + string kiter = tmp("kiter"); + string viter = tmp("viter"); + indent(out) << "for " << kiter << ", " << viter << " in " << prefix << ".items():" << endl; + indent_up(); + generate_serialize_map_element(out, (t_map*)ttype, kiter, viter); + indent_down(); + } else if (ttype->is_set()) { + string iter = tmp("iter"); + indent(out) << "for " << iter << " in " << prefix << ":" << endl; + indent_up(); + generate_serialize_set_element(out, (t_set*)ttype, iter); + indent_down(); + } else if (ttype->is_list()) { + string iter = tmp("iter"); + indent(out) << "for " << iter << " in " << prefix << ":" << endl; + indent_up(); + generate_serialize_list_element(out, (t_list*)ttype, iter); + indent_down(); + } + + if (ttype->is_map()) { + indent(out) << "oprot.writeMapEnd()" << endl; + } else if (ttype->is_set()) { + indent(out) << "oprot.writeSetEnd()" << endl; + } else if (ttype->is_list()) { + indent(out) << "oprot.writeListEnd()" << endl; + } +} + +/** + * Serializes the members of a map. + * + */ +void t_py_generator::generate_serialize_map_element(ostream& out, + t_map* tmap, + string kiter, + string viter) { + t_field kfield(tmap->get_key_type(), kiter); + generate_serialize_field(out, &kfield, ""); + + t_field vfield(tmap->get_val_type(), viter); + generate_serialize_field(out, &vfield, ""); +} + +/** + * Serializes the members of a set. + */ +void t_py_generator::generate_serialize_set_element(ostream& out, t_set* tset, string iter) { + t_field efield(tset->get_elem_type(), iter); + generate_serialize_field(out, &efield, ""); +} + +/** + * Serializes the members of a list. + */ +void t_py_generator::generate_serialize_list_element(ostream& out, t_list* tlist, string iter) { + t_field efield(tlist->get_elem_type(), iter); + generate_serialize_field(out, &efield, ""); +} + +/** + * Generates the docstring for a given struct. + */ +void t_py_generator::generate_python_docstring(ostream& out, t_struct* tstruct) { + generate_python_docstring(out, tstruct, tstruct, "Attributes"); +} + +/** + * Generates the docstring for a given function. + */ +void t_py_generator::generate_python_docstring(ostream& out, t_function* tfunction) { + generate_python_docstring(out, tfunction, tfunction->get_arglist(), "Parameters"); +} + +/** + * Generates the docstring for a struct or function. + */ +void t_py_generator::generate_python_docstring(ostream& out, + t_doc* tdoc, + t_struct* tstruct, + const char* subheader) { + bool has_doc = false; + stringstream ss; + if (tdoc->has_doc()) { + has_doc = true; + ss << tdoc->get_doc(); + } + + const vector<t_field*>& fields = tstruct->get_members(); + if (fields.size() > 0) { + if (has_doc) { + ss << endl; + } + has_doc = true; + ss << subheader << ":\n"; + vector<t_field*>::const_iterator p_iter; + for (p_iter = fields.begin(); p_iter != fields.end(); ++p_iter) { + t_field* p = *p_iter; + ss << " - " << p->get_name(); + if (p->has_doc()) { + ss << ": " << p->get_doc(); + } else { + ss << endl; + } + } + } + + if (has_doc) { + generate_docstring_comment(out, "\"\"\"\n", "", ss.str(), "\"\"\"\n"); + } +} + +/** + * Generates the docstring for a generic object. + */ +void t_py_generator::generate_python_docstring(ostream& out, t_doc* tdoc) { + if (tdoc->has_doc()) { + generate_docstring_comment(out, "\"\"\"\n", "", tdoc->get_doc(), "\"\"\"\n"); + } +} + +/** + * Declares an argument, which may include initialization as necessary. + * + * @param tfield The field + */ +string t_py_generator::declare_argument(t_field* tfield) { + std::ostringstream result; + result << tfield->get_name() << "="; + if (tfield->get_value() != NULL) { + result << render_field_default_value(tfield); + } else { + result << "None"; + } + return result.str(); +} + +/** + * Renders a field default value, returns None otherwise. + * + * @param tfield The field + */ +string t_py_generator::render_field_default_value(t_field* tfield) { + t_type* type = get_true_type(tfield->get_type()); + if (tfield->get_value() != NULL) { + return render_const_value(type, tfield->get_value()); + } else { + return "None"; + } +} + +/** + * Renders a function signature of the form 'type name(args)' + * + * @param tfunction Function definition + * @return String of rendered function definition + */ +string t_py_generator::function_signature(t_function* tfunction, bool interface) { + vector<string> pre; + vector<string> post; + string signature = tfunction->get_name() + "("; + + if (!(gen_zope_interface_ && interface)) { + pre.emplace_back("self"); + } + + signature += argument_list(tfunction->get_arglist(), &pre, &post) + ")"; + return signature; +} + +/** + * Renders a field list + */ +string t_py_generator::argument_list(t_struct* tstruct, vector<string>* pre, vector<string>* post) { + string result = ""; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + vector<string>::const_iterator s_iter; + bool first = true; + if (pre) { + for (s_iter = pre->begin(); s_iter != pre->end(); ++s_iter) { + if (first) { + first = false; + } else { + result += ", "; + } + result += *s_iter; + } + } + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + result += ", "; + } + result += (*f_iter)->get_name(); + } + if (post) { + for (s_iter = post->begin(); s_iter != post->end(); ++s_iter) { + if (first) { + first = false; + } else { + result += ", "; + } + result += *s_iter; + } + } + return result; +} + +string t_py_generator::type_name(t_type* ttype) { + while (ttype->is_typedef()) { + ttype = ((t_typedef*)ttype)->get_type(); + } + + t_program* program = ttype->get_program(); + if (ttype->is_service()) { + return get_real_py_module(program, gen_twisted_, package_prefix_) + "." + ttype->get_name(); + } + if (program != NULL && program != program_) { + return get_real_py_module(program, gen_twisted_, package_prefix_) + ".ttypes." + ttype->get_name(); + } + return ttype->get_name(); +} + +/** + * Converts the parse type to a Python tyoe + */ +string t_py_generator::type_to_enum(t_type* type) { + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + return "TType.STRING"; + case t_base_type::TYPE_BOOL: + return "TType.BOOL"; + case t_base_type::TYPE_I8: + return "TType.BYTE"; + case t_base_type::TYPE_I16: + return "TType.I16"; + case t_base_type::TYPE_I32: + return "TType.I32"; + case t_base_type::TYPE_I64: + return "TType.I64"; + case t_base_type::TYPE_DOUBLE: + return "TType.DOUBLE"; + } + } else if (type->is_enum()) { + return "TType.I32"; + } else if (type->is_struct() || type->is_xception()) { + return "TType.STRUCT"; + } else if (type->is_map()) { + return "TType.MAP"; + } else if (type->is_set()) { + return "TType.SET"; + } else if (type->is_list()) { + return "TType.LIST"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +/** See the comment inside generate_py_struct_definition for what this is. */ +string t_py_generator::type_to_spec_args(t_type* ttype) { + while (ttype->is_typedef()) { + ttype = ((t_typedef*)ttype)->get_type(); + } + + if (ttype->is_binary()) { + return "'BINARY'"; + } else if (gen_utf8strings_ && ttype->is_base_type() + && reinterpret_cast<t_base_type*>(ttype)->is_string()) { + return "'UTF8'"; + } else if (ttype->is_base_type() || ttype->is_enum()) { + return "None"; + } else if (ttype->is_struct() || ttype->is_xception()) { + return "[" + type_name(ttype) + ", None]"; + } else if (ttype->is_map()) { + return "(" + type_to_enum(((t_map*)ttype)->get_key_type()) + ", " + + type_to_spec_args(((t_map*)ttype)->get_key_type()) + ", " + + type_to_enum(((t_map*)ttype)->get_val_type()) + ", " + + type_to_spec_args(((t_map*)ttype)->get_val_type()) + ", " + + (is_immutable(ttype) ? "True" : "False") + ")"; + + } else if (ttype->is_set()) { + return "(" + type_to_enum(((t_set*)ttype)->get_elem_type()) + ", " + + type_to_spec_args(((t_set*)ttype)->get_elem_type()) + ", " + + (is_immutable(ttype) ? "True" : "False") + ")"; + + } else if (ttype->is_list()) { + return "(" + type_to_enum(((t_list*)ttype)->get_elem_type()) + ", " + + type_to_spec_args(((t_list*)ttype)->get_elem_type()) + ", " + + (is_immutable(ttype) ? "True" : "False") + ")"; + } + + throw "INVALID TYPE IN type_to_spec_args: " + ttype->get_name(); +} + +THRIFT_REGISTER_GENERATOR( + py, + "Python", + " zope.interface: Generate code for use with zope.interface.\n" + " twisted: Generate Twisted-friendly RPC services.\n" + " tornado: Generate code for use with Tornado.\n" + " no_utf8strings: Do not Encode/decode strings using utf8 in the generated code. Basically no effect for Python 3.\n" + " coding=CODING: Add file encoding declare in generated file.\n" + " slots: Generate code using slots for instance members.\n" + " dynamic: Generate dynamic code, less code generated but slower.\n" + " dynbase=CLS Derive generated classes from class CLS instead of TBase.\n" + " dynfrozen=CLS Derive generated immutable classes from class CLS instead of TFrozenBase.\n" + " dynexc=CLS Derive generated exceptions from CLS instead of TExceptionBase.\n" + " dynimport='from foo.bar import CLS'\n" + " Add an import line to generated code to find the dynbase class.\n" + " package_prefix='top.package.'\n" + " Package prefix for generated files.\n" + " old_style: Deprecated. Generate old-style classes.\n") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_rb_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_rb_generator.cc new file mode 100644 index 000000000..61c34811d --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_rb_generator.cc @@ -0,0 +1,1288 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +#include <string> +#include <fstream> +#include <iostream> +#include <vector> +#include <algorithm> + +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sstream> + +#include "thrift/platform.h" +#include "thrift/version.h" +#include "thrift/generate/t_oop_generator.h" + +using std::map; +using std::ofstream; +using std::ostringstream; +using std::string; +using std::stringstream; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes + +/** + * A subclass of std::ofstream that includes indenting functionality. + */ +class t_rb_ofstream : public std::ofstream { +private: + int indent_; + +public: + t_rb_ofstream() : std::ofstream(), indent_(0) {} + explicit t_rb_ofstream(const char* filename, + ios_base::openmode mode = ios_base::out, + int indent = 0) + : std::ofstream(filename, mode), indent_(indent) {} + + t_rb_ofstream& indent() { + for (int i = 0; i < indent_; ++i) { + *this << " "; + } + return *this; + } + + void indent_up() { indent_++; } + void indent_down() { indent_--; } +}; + +/** + * Ruby code generator. + * + */ +class t_rb_generator : public t_oop_generator { +public: + t_rb_generator(t_program* program, + const std::map<std::string, std::string>& parsed_options, + const std::string& option_string) + : t_oop_generator(program) { + (void)option_string; + std::map<std::string, std::string>::const_iterator iter; + + require_rubygems_ = false; + namespaced_ = false; + for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + if( iter->first.compare("rubygems") == 0) { + require_rubygems_ = true; + } else if( iter->first.compare("namespaced") == 0) { + namespaced_ = true; + } else { + throw "unknown option ruby:" + iter->first; + } + } + + out_dir_base_ = "gen-rb"; + } + + /** + * Init and close methods + */ + + void init_generator() override; + void close_generator() override; + + /** + * Program-level generation functions + */ + + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_const(t_const* tconst) override; + void generate_struct(t_struct* tstruct) override; + void generate_forward_declaration(t_struct* tstruct) override; + void generate_union(t_struct* tunion); + void generate_xception(t_struct* txception) override; + void generate_service(t_service* tservice) override; + + t_rb_ofstream& render_const_value(t_rb_ofstream& out, t_type* type, t_const_value* value); + + /** + * Struct generation code + */ + + void generate_rb_struct_declaration(t_rb_ofstream& out, t_struct* tstruct, bool is_exception); + void generate_rb_struct(t_rb_ofstream& out, t_struct* tstruct, bool is_exception); + void generate_rb_struct_required_validator(t_rb_ofstream& out, t_struct* tstruct); + void generate_rb_union(t_rb_ofstream& out, t_struct* tstruct, bool is_exception); + void generate_rb_union_validator(t_rb_ofstream& out, t_struct* tstruct); + void generate_rb_function_helpers(t_function* tfunction); + void generate_rb_simple_constructor(t_rb_ofstream& out, t_struct* tstruct); + void generate_rb_simple_exception_constructor(t_rb_ofstream& out, t_struct* tstruct); + void generate_field_constants(t_rb_ofstream& out, t_struct* tstruct); + void generate_field_constructors(t_rb_ofstream& out, t_struct* tstruct); + void generate_field_defns(t_rb_ofstream& out, t_struct* tstruct); + void generate_field_data(t_rb_ofstream& out, + t_type* field_type, + const std::string& field_name, + t_const_value* field_value, + bool optional); + + /** + * Service-level generation functions + */ + + void generate_service_helpers(t_service* tservice); + void generate_service_interface(t_service* tservice); + void generate_service_client(t_service* tservice); + void generate_service_server(t_service* tservice); + void generate_process_function(t_service* tservice, t_function* tfunction); + + /** + * Serialization constructs + */ + + void generate_deserialize_field(t_rb_ofstream& out, + t_field* tfield, + std::string prefix = "", + bool inclass = false); + + void generate_deserialize_struct(t_rb_ofstream& out, t_struct* tstruct, std::string prefix = ""); + + void generate_deserialize_container(t_rb_ofstream& out, t_type* ttype, std::string prefix = ""); + + void generate_deserialize_set_element(t_rb_ofstream& out, t_set* tset, std::string prefix = ""); + + void generate_deserialize_map_element(t_rb_ofstream& out, t_map* tmap, std::string prefix = ""); + + void generate_deserialize_list_element(t_rb_ofstream& out, + t_list* tlist, + std::string prefix = ""); + + void generate_serialize_field(t_rb_ofstream& out, t_field* tfield, std::string prefix = ""); + + void generate_serialize_struct(t_rb_ofstream& out, t_struct* tstruct, std::string prefix = ""); + + void generate_serialize_container(t_rb_ofstream& out, t_type* ttype, std::string prefix = ""); + + void generate_serialize_map_element(t_rb_ofstream& out, + t_map* tmap, + std::string kiter, + std::string viter); + + void generate_serialize_set_element(t_rb_ofstream& out, t_set* tmap, std::string iter); + + void generate_serialize_list_element(t_rb_ofstream& out, t_list* tlist, std::string iter); + + void generate_rdoc(t_rb_ofstream& out, t_doc* tdoc); + + /** + * Helper rendering functions + */ + + std::string rb_autogen_comment(); + std::string render_require_thrift(); + std::string render_includes(); + std::string declare_field(t_field* tfield); + std::string type_name(const t_type* ttype); + std::string full_type_name(const t_type* ttype); + std::string function_signature(t_function* tfunction, std::string prefix = ""); + std::string argument_list(t_struct* tstruct); + std::string type_to_enum(t_type* ttype); + std::string rb_namespace_to_path_prefix(std::string rb_namespace); + + std::vector<std::string> ruby_modules(const t_program* p) { + std::string ns = p->get_namespace("rb"); + std::vector<std::string> modules; + if (ns.empty()) { + return modules; + } + + std::string::iterator pos = ns.begin(); + while (true) { + std::string::iterator delim = std::find(pos, ns.end(), '.'); + modules.push_back(capitalize(std::string(pos, delim))); + pos = delim; + if (pos == ns.end()) { + break; + } + ++pos; + } + + return modules; + } + + void begin_namespace(t_rb_ofstream&, std::vector<std::string>); + void end_namespace(t_rb_ofstream&, std::vector<std::string>); + +private: + /** + * File streams + */ + + t_rb_ofstream f_types_; + t_rb_ofstream f_consts_; + t_rb_ofstream f_service_; + + std::string namespace_dir_; + std::string require_prefix_; + + /** If true, add a "require 'rubygems'" line to the top of each gen-rb file. */ + bool require_rubygems_; + + /** If true, generate files in idiomatic namespaced directories. */ + bool namespaced_; +}; + +/** + * Prepares for file generation by opening up the necessary file output + * streams. + * + * @param tprogram The program to generate + */ +void t_rb_generator::init_generator() { + string subdir = get_out_dir(); + + // Make output directory + MKDIR(subdir.c_str()); + + if (namespaced_) { + require_prefix_ = rb_namespace_to_path_prefix(program_->get_namespace("rb")); + + string dir = require_prefix_; + string::size_type loc; + + while ((loc = dir.find("/")) != string::npos) { + subdir = subdir + dir.substr(0, loc) + "/"; + MKDIR(subdir.c_str()); + dir = dir.substr(loc + 1); + } + } + + namespace_dir_ = subdir; + + // Make output file + string f_types_name = namespace_dir_ + underscore(program_name_) + "_types.rb"; + f_types_.open(f_types_name.c_str()); + + string f_consts_name = namespace_dir_ + underscore(program_name_) + "_constants.rb"; + f_consts_.open(f_consts_name.c_str()); + + // Print header + f_types_ << rb_autogen_comment() << endl << render_require_thrift() << render_includes() << endl; + begin_namespace(f_types_, ruby_modules(program_)); + + f_consts_ << rb_autogen_comment() << endl << render_require_thrift() << "require '" + << require_prefix_ << underscore(program_name_) << "_types'" << endl << endl; + begin_namespace(f_consts_, ruby_modules(program_)); +} + +/** + * Renders the require of thrift itself, and possibly of the rubygems dependency. + */ +string t_rb_generator::render_require_thrift() { + if (require_rubygems_) { + return "require 'rubygems'\nrequire 'thrift'\n"; + } else { + return "require 'thrift'\n"; + } +} + +/** + * Renders all the imports necessary for including another Thrift program + */ +string t_rb_generator::render_includes() { + const vector<t_program*>& includes = program_->get_includes(); + string result = ""; + for (auto include : includes) { + if (namespaced_) { + t_program* included = include; + std::string included_require_prefix + = rb_namespace_to_path_prefix(included->get_namespace("rb")); + std::string included_name = included->get_name(); + result += "require '" + included_require_prefix + underscore(included_name) + "_types'\n"; + } else { + result += "require '" + underscore(include->get_name()) + "_types'\n"; + } + } + if (includes.size() > 0) { + result += "\n"; + } + return result; +} + +/** + * Autogen'd comment + */ +string t_rb_generator::rb_autogen_comment() { + return std::string("#\n") + "# Autogenerated by Thrift Compiler (" + THRIFT_VERSION + ")\n" + + "#\n" + "# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n" + "#\n"; +} + +/** + * Closes the type files + */ +void t_rb_generator::close_generator() { + // Close types file + end_namespace(f_types_, ruby_modules(program_)); + end_namespace(f_consts_, ruby_modules(program_)); + f_types_.close(); + f_consts_.close(); +} + +/** + * Generates a typedef. This is not done in Ruby, types are all implicit. + * + * @param ttypedef The type definition + */ +void t_rb_generator::generate_typedef(t_typedef* ttypedef) { + (void)ttypedef; +} + +/** + * Generates code for an enumerated type. Done using a class to scope + * the values. + * + * @param tenum The enumeration + */ +void t_rb_generator::generate_enum(t_enum* tenum) { + f_types_.indent() << "module " << capitalize(tenum->get_name()) << endl; + f_types_.indent_up(); + + vector<t_enum_value*> constants = tenum->get_constants(); + vector<t_enum_value*>::iterator c_iter; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + int value = (*c_iter)->get_value(); + + // Ruby class constants have to be capitalized... omg i am so on the fence + // about languages strictly enforcing capitalization why can't we just all + // agree and play nice. + string name = capitalize((*c_iter)->get_name()); + + generate_rdoc(f_types_, *c_iter); + f_types_.indent() << name << " = " << value << endl; + } + + // Create a hash mapping values back to their names (as strings) since ruby has no native enum + // type + f_types_.indent() << "VALUE_MAP = {"; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + // Populate the hash + int value = (*c_iter)->get_value(); + if (c_iter != constants.begin()) + f_types_ << ", "; + f_types_ << value << " => \"" << capitalize((*c_iter)->get_name()) << "\""; + } + f_types_ << "}" << endl; + + // Create a set with valid values for this enum + f_types_.indent() << "VALID_VALUES = Set.new(["; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + // Populate the set + if (c_iter != constants.begin()) + f_types_ << ", "; + f_types_ << capitalize((*c_iter)->get_name()); + } + f_types_ << "]).freeze" << endl; + + f_types_.indent_down(); + f_types_.indent() << "end" << endl << endl; +} + +/** + * Generate a constant value + */ +void t_rb_generator::generate_const(t_const* tconst) { + t_type* type = tconst->get_type(); + string name = tconst->get_name(); + t_const_value* value = tconst->get_value(); + + name[0] = toupper(name[0]); + + f_consts_.indent() << name << " = "; + render_const_value(f_consts_, type, value) << endl << endl; +} + +/** + * Prints the value of a constant with the given type. Note that type checking + * is NOT performed in this function as it is always run beforehand using the + * validate_types method in main.cc + */ +t_rb_ofstream& t_rb_generator::render_const_value(t_rb_ofstream& out, + t_type* type, + t_const_value* value) { + type = get_true_type(type); + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + out << "%q\"" << get_escaped_string(value) << '"'; + break; + case t_base_type::TYPE_BOOL: + out << (value->get_integer() > 0 ? "true" : "false"); + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + out << value->get_integer(); + break; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) { + out << value->get_integer(); + } else { + out << value->get_double(); + } + break; + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out.indent() << value->get_integer(); + } else if (type->is_struct() || type->is_xception()) { + out << full_type_name(type) << ".new({" << endl; + out.indent_up(); + const vector<t_field*>& fields = ((t_struct*)type)->get_members(); + vector<t_field*>::const_iterator f_iter; + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + t_type* field_type = NULL; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field_type = (*f_iter)->get_type(); + } + } + if (field_type == NULL) { + throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string(); + } + out.indent(); + render_const_value(out, g_type_string, v_iter->first) << " => "; + render_const_value(out, field_type, v_iter->second) << "," << endl; + } + out.indent_down(); + out.indent() << "})"; + } else if (type->is_map()) { + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + out << "{" << endl; + out.indent_up(); + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + out.indent(); + render_const_value(out, ktype, v_iter->first) << " => "; + render_const_value(out, vtype, v_iter->second) << "," << endl; + } + out.indent_down(); + out.indent() << "}"; + } else if (type->is_list() || type->is_set()) { + t_type* etype; + if (type->is_list()) { + etype = ((t_list*)type)->get_elem_type(); + } else { + etype = ((t_set*)type)->get_elem_type(); + } + if (type->is_set()) { + out << "Set.new([" << endl; + } else { + out << "[" << endl; + } + out.indent_up(); + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + out.indent(); + render_const_value(out, etype, *v_iter) << "," << endl; + } + out.indent_down(); + if (type->is_set()) { + out.indent() << "])"; + } else { + out.indent() << "]"; + } + } else { + throw "CANNOT GENERATE CONSTANT FOR TYPE: " + type->get_name(); + } + return out; +} + +/** + * Generates a ruby struct + */ +void t_rb_generator::generate_struct(t_struct* tstruct) { + if (tstruct->is_union()) { + generate_rb_union(f_types_, tstruct, false); + } else { + generate_rb_struct(f_types_, tstruct, false); + } +} + + +/** + * Generates the "forward declarations" for ruby structs. + * These are simply a declaration of each class with proper inheritance. + * The rest of the struct is still generated in generate_struct as has + * always been the case. These declarations allow thrift to generate valid + * ruby in cases where thrift structs rely on recursive definitions. + */ +void t_rb_generator::generate_forward_declaration(t_struct* tstruct) { + generate_rb_struct_declaration(f_types_, tstruct, tstruct->is_xception()); +} + +void t_rb_generator::generate_rb_struct_declaration(t_rb_ofstream& out, t_struct* tstruct, bool is_exception) { + out.indent() << "class " << type_name(tstruct); + if (tstruct->is_union()) { + out << " < ::Thrift::Union"; + } + if (is_exception) { + out << " < ::Thrift::Exception"; + } + out << "; end" << endl << endl; +} + +/** + * Generates a struct definition for a thrift exception. Basically the same + * as a struct but extends the Exception class. + * + * @param txception The struct definition + */ +void t_rb_generator::generate_xception(t_struct* txception) { + generate_rb_struct(f_types_, txception, true); +} + +/** + * Generates a ruby struct + */ +void t_rb_generator::generate_rb_struct(t_rb_ofstream& out, + t_struct* tstruct, + bool is_exception = false) { + generate_rdoc(out, tstruct); + out.indent() << "class " << type_name(tstruct); + if (is_exception) { + out << " < ::Thrift::Exception"; + } + out << endl; + + out.indent_up(); + out.indent() << "include ::Thrift::Struct, ::Thrift::Struct_Union" << endl; + + if (is_exception) { + generate_rb_simple_exception_constructor(out, tstruct); + } + + generate_field_constants(out, tstruct); + generate_field_defns(out, tstruct); + generate_rb_struct_required_validator(out, tstruct); + + out.indent() << "::Thrift::Struct.generate_accessors self" << endl; + + out.indent_down(); + out.indent() << "end" << endl << endl; +} + +/** + * Generates a ruby union + */ +void t_rb_generator::generate_rb_union(t_rb_ofstream& out, + t_struct* tstruct, + bool is_exception = false) { + (void)is_exception; + generate_rdoc(out, tstruct); + out.indent() << "class " << type_name(tstruct) << " < ::Thrift::Union" << endl; + + out.indent_up(); + out.indent() << "include ::Thrift::Struct_Union" << endl; + + generate_field_constructors(out, tstruct); + + generate_field_constants(out, tstruct); + generate_field_defns(out, tstruct); + generate_rb_union_validator(out, tstruct); + + out.indent() << "::Thrift::Union.generate_accessors self" << endl; + + out.indent_down(); + out.indent() << "end" << endl << endl; +} + +void t_rb_generator::generate_field_constructors(t_rb_ofstream& out, t_struct* tstruct) { + + out.indent() << "class << self" << endl; + out.indent_up(); + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (f_iter != fields.begin()) { + out << endl; + } + std::string field_name = (*f_iter)->get_name(); + + out.indent() << "def " << field_name << "(val)" << endl; + out.indent() << " " << tstruct->get_name() << ".new(:" << field_name << ", val)" << endl; + out.indent() << "end" << endl; + } + + out.indent_down(); + out.indent() << "end" << endl; + + out << endl; +} + +void t_rb_generator::generate_rb_simple_exception_constructor(t_rb_ofstream& out, + t_struct* tstruct) { + const vector<t_field*>& members = tstruct->get_members(); + + if (members.size() == 1) { + vector<t_field*>::const_iterator m_iter = members.begin(); + + if ((*m_iter)->get_type()->is_string()) { + string name = (*m_iter)->get_name(); + + out.indent() << "def initialize(message=nil)" << endl; + out.indent_up(); + out.indent() << "super()" << endl; + out.indent() << "self." << name << " = message" << endl; + out.indent_down(); + out.indent() << "end" << endl << endl; + + if (name != "message") { + out.indent() << "def message; " << name << " end" << endl << endl; + } + } + } +} + +void t_rb_generator::generate_field_constants(t_rb_ofstream& out, t_struct* tstruct) { + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + std::string field_name = (*f_iter)->get_name(); + std::string cap_field_name = upcase_string(field_name); + + out.indent() << cap_field_name << " = " << (*f_iter)->get_key() << endl; + } + out << endl; +} + +void t_rb_generator::generate_field_defns(t_rb_ofstream& out, t_struct* tstruct) { + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + out.indent() << "FIELDS = {" << endl; + out.indent_up(); + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (f_iter != fields.begin()) { + out << "," << endl; + } + + // generate the field docstrings within the FIELDS constant. no real better place... + generate_rdoc(out, *f_iter); + + out.indent() << upcase_string((*f_iter)->get_name()) << " => "; + + generate_field_data(out, + (*f_iter)->get_type(), + (*f_iter)->get_name(), + (*f_iter)->get_value(), + (*f_iter)->get_req() == t_field::T_OPTIONAL); + } + out.indent_down(); + out << endl; + out.indent() << "}" << endl << endl; + + out.indent() << "def struct_fields; FIELDS; end" << endl << endl; +} + +void t_rb_generator::generate_field_data(t_rb_ofstream& out, + t_type* field_type, + const std::string& field_name = "", + t_const_value* field_value = NULL, + bool optional = false) { + field_type = get_true_type(field_type); + + // Begin this field's defn + out << "{:type => " << type_to_enum(field_type); + + if (!field_name.empty()) { + out << ", :name => '" << field_name << "'"; + } + + if (field_value != NULL) { + out << ", :default => "; + render_const_value(out, field_type, field_value); + } + + if (!field_type->is_base_type()) { + if (field_type->is_struct() || field_type->is_xception()) { + out << ", :class => " << full_type_name((t_struct*)field_type); + } else if (field_type->is_list()) { + out << ", :element => "; + generate_field_data(out, ((t_list*)field_type)->get_elem_type()); + } else if (field_type->is_map()) { + out << ", :key => "; + generate_field_data(out, ((t_map*)field_type)->get_key_type()); + out << ", :value => "; + generate_field_data(out, ((t_map*)field_type)->get_val_type()); + } else if (field_type->is_set()) { + out << ", :element => "; + generate_field_data(out, ((t_set*)field_type)->get_elem_type()); + } + } else { + if (((t_base_type*)field_type)->is_binary()) { + out << ", :binary => true"; + } + } + + if (optional) { + out << ", :optional => true"; + } + + if (field_type->is_enum()) { + out << ", :enum_class => " << full_type_name(field_type); + } + + // End of this field's defn + out << "}"; +} + +void t_rb_generator::begin_namespace(t_rb_ofstream& out, vector<std::string> modules) { + for (auto & module : modules) { + out.indent() << "module " << module << endl; + out.indent_up(); + } +} + +void t_rb_generator::end_namespace(t_rb_ofstream& out, vector<std::string> modules) { + for (vector<std::string>::reverse_iterator m_iter = modules.rbegin(); m_iter != modules.rend(); + ++m_iter) { + out.indent_down(); + out.indent() << "end" << endl; + } +} + +/** + * Generates a thrift service. + * + * @param tservice The service definition + */ +void t_rb_generator::generate_service(t_service* tservice) { + string f_service_name = namespace_dir_ + underscore(service_name_) + ".rb"; + f_service_.open(f_service_name.c_str()); + + f_service_ << rb_autogen_comment() << endl << render_require_thrift(); + + if (tservice->get_extends() != NULL) { + if (namespaced_) { + f_service_ << "require '" << rb_namespace_to_path_prefix( + tservice->get_extends()->get_program()->get_namespace("rb")) + << underscore(tservice->get_extends()->get_name()) << "'" << endl; + } else { + f_service_ << "require '" << require_prefix_ + << underscore(tservice->get_extends()->get_name()) << "'" << endl; + } + } + + f_service_ << "require '" << require_prefix_ << underscore(program_name_) << "_types'" << endl + << endl; + + begin_namespace(f_service_, ruby_modules(tservice->get_program())); + + f_service_.indent() << "module " << capitalize(tservice->get_name()) << endl; + f_service_.indent_up(); + + // Generate the three main parts of the service (well, two for now in PHP) + generate_service_client(tservice); + generate_service_server(tservice); + generate_service_helpers(tservice); + + f_service_.indent_down(); + f_service_.indent() << "end" << endl << endl; + + end_namespace(f_service_, ruby_modules(tservice->get_program())); + + // Close service file + f_service_.close(); +} + +/** + * Generates helper functions for a service. + * + * @param tservice The service to generate a header definition for + */ +void t_rb_generator::generate_service_helpers(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + f_service_.indent() << "# HELPER FUNCTIONS AND STRUCTURES" << endl << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* ts = (*f_iter)->get_arglist(); + generate_rb_struct(f_service_, ts); + generate_rb_function_helpers(*f_iter); + } +} + +/** + * Generates a struct and helpers for a function. + * + * @param tfunction The function + */ +void t_rb_generator::generate_rb_function_helpers(t_function* tfunction) { + t_struct result(program_, tfunction->get_name() + "_result"); + t_field success(tfunction->get_returntype(), "success", 0); + if (!tfunction->get_returntype()->is_void()) { + result.append(&success); + } + + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& fields = xs->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + result.append(*f_iter); + } + generate_rb_struct(f_service_, &result); +} + +/** + * Generates a service client definition. + * + * @param tservice The service to generate a server for. + */ +void t_rb_generator::generate_service_client(t_service* tservice) { + string extends = ""; + string extends_client = ""; + if (tservice->get_extends() != NULL) { + extends = full_type_name(tservice->get_extends()); + extends_client = " < " + extends + "::Client "; + } + + f_service_.indent() << "class Client" << extends_client << endl; + f_service_.indent_up(); + + f_service_.indent() << "include ::Thrift::Client" << endl << endl; + + // Generate client method implementations + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* arg_struct = (*f_iter)->get_arglist(); + const vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator fld_iter; + string funname = (*f_iter)->get_name(); + + // Open function + f_service_.indent() << "def " << function_signature(*f_iter) << endl; + f_service_.indent_up(); + f_service_.indent() << "send_" << funname << "("; + + bool first = true; + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << (*fld_iter)->get_name(); + } + f_service_ << ")" << endl; + + if (!(*f_iter)->is_oneway()) { + f_service_.indent(); + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_ << "return "; + } + f_service_ << "recv_" << funname << "()" << endl; + } + f_service_.indent_down(); + f_service_.indent() << "end" << endl; + f_service_ << endl; + + f_service_.indent() << "def send_" << function_signature(*f_iter) << endl; + f_service_.indent_up(); + + std::string argsname = capitalize((*f_iter)->get_name() + "_args"); + std::string messageSendProc = (*f_iter)->is_oneway() ? "send_oneway_message" : "send_message"; + + f_service_.indent() << messageSendProc << "('" << funname << "', " << argsname; + + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + f_service_ << ", :" << (*fld_iter)->get_name() << " => " << (*fld_iter)->get_name(); + } + + f_service_ << ")" << endl; + + f_service_.indent_down(); + f_service_.indent() << "end" << endl; + + if (!(*f_iter)->is_oneway()) { + std::string resultname = capitalize((*f_iter)->get_name() + "_result"); + t_struct noargs(program_); + + t_function recv_function((*f_iter)->get_returntype(), + string("recv_") + (*f_iter)->get_name(), + &noargs); + // Open function + f_service_ << endl; + f_service_.indent() << "def " << function_signature(&recv_function) << endl; + f_service_.indent_up(); + + // TODO(mcslee): Validate message reply here, seq ids etc. + + f_service_.indent() << "result = receive_message(" << resultname << ")" << endl; + + // Careful, only return _result if not a void function + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_.indent() << "return result.success unless result.success.nil?" << endl; + } + + t_struct* xs = (*f_iter)->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + f_service_.indent() << "raise result." << (*x_iter)->get_name() << " unless result." + << (*x_iter)->get_name() << ".nil?" << endl; + } + + // Careful, only return _result if not a void function + if ((*f_iter)->get_returntype()->is_void()) { + f_service_.indent() << "return" << endl; + } else { + f_service_.indent() << "raise " + "::Thrift::ApplicationException.new(::Thrift::ApplicationException::" + "MISSING_RESULT, '" << (*f_iter)->get_name() + << " failed: unknown result')" << endl; + } + + // Close function + f_service_.indent_down(); + f_service_.indent() << "end" << endl << endl; + } + } + + f_service_.indent_down(); + f_service_.indent() << "end" << endl << endl; +} + +/** + * Generates a service server definition. + * + * @param tservice The service to generate a server for. + */ +void t_rb_generator::generate_service_server(t_service* tservice) { + // Generate the dispatch methods + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + string extends = ""; + string extends_processor = ""; + if (tservice->get_extends() != NULL) { + extends = full_type_name(tservice->get_extends()); + extends_processor = " < " + extends + "::Processor "; + } + + // Generate the header portion + f_service_.indent() << "class Processor" << extends_processor << endl; + f_service_.indent_up(); + + f_service_.indent() << "include ::Thrift::Processor" << endl << endl; + + // Generate the process subfunctions + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_process_function(tservice, *f_iter); + } + + f_service_.indent_down(); + f_service_.indent() << "end" << endl << endl; +} + +/** + * Generates a process function definition. + * + * @param tfunction The function to write a dispatcher for + */ +void t_rb_generator::generate_process_function(t_service* tservice, t_function* tfunction) { + (void)tservice; + // Open function + f_service_.indent() << "def process_" << tfunction->get_name() << "(seqid, iprot, oprot)" << endl; + f_service_.indent_up(); + + string argsname = capitalize(tfunction->get_name()) + "_args"; + string resultname = capitalize(tfunction->get_name()) + "_result"; + + f_service_.indent() << "args = read_args(iprot, " << argsname << ")" << endl; + + t_struct* xs = tfunction->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + + // Declare result for non oneway function + if (!tfunction->is_oneway()) { + f_service_.indent() << "result = " << resultname << ".new()" << endl; + } + + // Try block for a function with exceptions + if (xceptions.size() > 0) { + f_service_.indent() << "begin" << endl; + f_service_.indent_up(); + } + + // Generate the function call + t_struct* arg_struct = tfunction->get_arglist(); + const std::vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + + f_service_.indent(); + if (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void()) { + f_service_ << "result.success = "; + } + f_service_ << "@handler." << tfunction->get_name() << "("; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << "args." << (*f_iter)->get_name(); + } + f_service_ << ")" << endl; + + if (!tfunction->is_oneway() && xceptions.size() > 0) { + f_service_.indent_down(); + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + f_service_.indent() << "rescue " << full_type_name((*x_iter)->get_type()) << " => " + << (*x_iter)->get_name() << endl; + if (!tfunction->is_oneway()) { + f_service_.indent_up(); + f_service_.indent() << "result." << (*x_iter)->get_name() << " = " << (*x_iter)->get_name() + << endl; + f_service_.indent_down(); + } + } + f_service_.indent() << "end" << endl; + } + + // Shortcut out here for oneway functions + if (tfunction->is_oneway()) { + f_service_.indent() << "return" << endl; + f_service_.indent_down(); + f_service_.indent() << "end" << endl << endl; + return; + } + + f_service_.indent() << "write_result(result, oprot, '" << tfunction->get_name() << "', seqid)" + << endl; + + // Close function + f_service_.indent_down(); + f_service_.indent() << "end" << endl << endl; +} + +/** + * Renders a function signature of the form 'type name(args)' + * + * @param tfunction Function definition + * @return String of rendered function definition + */ +string t_rb_generator::function_signature(t_function* tfunction, string prefix) { + // TODO(mcslee): Nitpicky, no ',' if argument_list is empty + return prefix + tfunction->get_name() + "(" + argument_list(tfunction->get_arglist()) + ")"; +} + +/** + * Renders a field list + */ +string t_rb_generator::argument_list(t_struct* tstruct) { + string result = ""; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + result += ", "; + } + result += (*f_iter)->get_name(); + } + return result; +} + +string t_rb_generator::type_name(const t_type* ttype) { + string prefix = ""; + + string name = ttype->get_name(); + if (ttype->is_struct() || ttype->is_xception() || ttype->is_enum()) { + name = capitalize(ttype->get_name()); + } + + return prefix + name; +} + +string t_rb_generator::full_type_name(const t_type* ttype) { + string prefix = "::"; + vector<std::string> modules = ruby_modules(ttype->get_program()); + for (auto & module : modules) { + prefix += module + "::"; + } + return prefix + type_name(ttype); +} + +/** + * Converts the parse type to a Ruby tyoe + */ +string t_rb_generator::type_to_enum(t_type* type) { + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + return "::Thrift::Types::STRING"; + case t_base_type::TYPE_BOOL: + return "::Thrift::Types::BOOL"; + case t_base_type::TYPE_I8: + return "::Thrift::Types::BYTE"; + case t_base_type::TYPE_I16: + return "::Thrift::Types::I16"; + case t_base_type::TYPE_I32: + return "::Thrift::Types::I32"; + case t_base_type::TYPE_I64: + return "::Thrift::Types::I64"; + case t_base_type::TYPE_DOUBLE: + return "::Thrift::Types::DOUBLE"; + } + } else if (type->is_enum()) { + return "::Thrift::Types::I32"; + } else if (type->is_struct() || type->is_xception()) { + return "::Thrift::Types::STRUCT"; + } else if (type->is_map()) { + return "::Thrift::Types::MAP"; + } else if (type->is_set()) { + return "::Thrift::Types::SET"; + } else if (type->is_list()) { + return "::Thrift::Types::LIST"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +string t_rb_generator::rb_namespace_to_path_prefix(string rb_namespace) { + string namespaces_left = rb_namespace; + string::size_type loc; + + string path_prefix = ""; + + while ((loc = namespaces_left.find(".")) != string::npos) { + path_prefix = path_prefix + underscore(namespaces_left.substr(0, loc)) + "/"; + namespaces_left = namespaces_left.substr(loc + 1); + } + if (namespaces_left.size() > 0) { + path_prefix = path_prefix + underscore(namespaces_left) + "/"; + } + return path_prefix; +} + +void t_rb_generator::generate_rdoc(t_rb_ofstream& out, t_doc* tdoc) { + if (tdoc->has_doc()) { + out.indent(); + generate_docstring_comment(out, "", "# ", tdoc->get_doc(), ""); + } +} + +void t_rb_generator::generate_rb_struct_required_validator(t_rb_ofstream& out, t_struct* tstruct) { + out.indent() << "def validate" << endl; + out.indent_up(); + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = (*f_iter); + if (field->get_req() == t_field::T_REQUIRED) { + out.indent() << "raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, " + "'Required field " << field->get_name() << " is unset!')"; + if (field->get_type()->is_bool()) { + out << " if @" << field->get_name() << ".nil?"; + } else { + out << " unless @" << field->get_name(); + } + out << endl; + } + } + + // if field is an enum, check that its value is valid + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field* field = (*f_iter); + + if (field->get_type()->is_enum()) { + out.indent() << "unless @" << field->get_name() << ".nil? || " + << full_type_name(field->get_type()) << "::VALID_VALUES.include?(@" + << field->get_name() << ")" << endl; + out.indent_up(); + out.indent() << "raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, " + "'Invalid value of field " << field->get_name() << "!')" << endl; + out.indent_down(); + out.indent() << "end" << endl; + } + } + + out.indent_down(); + out.indent() << "end" << endl << endl; +} + +void t_rb_generator::generate_rb_union_validator(t_rb_ofstream& out, t_struct* tstruct) { + out.indent() << "def validate" << endl; + out.indent_up(); + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + out.indent() + << "raise(StandardError, 'Union fields are not set.') if get_set_field.nil? || get_value.nil?" + << endl; + + // if field is an enum, check that its value is valid + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + const t_field* field = (*f_iter); + + if (field->get_type()->is_enum()) { + out.indent() << "if get_set_field == :" << field->get_name() << endl; + out.indent() << " raise " + "::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, " + "'Invalid value of field " << field->get_name() << "!') unless " + << full_type_name(field->get_type()) << "::VALID_VALUES.include?(get_value)" + << endl; + out.indent() << "end" << endl; + } + } + + out.indent_down(); + out.indent() << "end" << endl << endl; +} + +THRIFT_REGISTER_GENERATOR( + rb, + "Ruby", + " rubygems: Add a \"require 'rubygems'\" line to the top of each generated file.\n" + " namespaced: Generate files in idiomatic namespaced directories.\n") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_rs_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_rs_generator.cc new file mode 100644 index 000000000..70e18472c --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_rs_generator.cc @@ -0,0 +1,3351 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <string> +#include <fstream> +#include <iostream> + +#include "thrift/platform.h" +#include "thrift/generate/t_generator.h" + +using std::map; +using std::ofstream; +using std::ostringstream; +using std::string; +using std::vector; +using std::set; + +static const string endl("\n"); // avoid ostream << std::endl flushes +static const string SERVICE_RESULT_VARIABLE("result_value"); +static const string RESULT_STRUCT_SUFFIX("Result"); +static const string RUST_RESERVED_WORDS[] = { + "abstract", "alignof", "as", "become", + "box", "break", "const", "continue", + "crate", "do", "else", "enum", + "extern", "false", "final", "fn", + "for", "if", "impl", "in", + "let", "loop", "macro", "match", + "mod", "move", "mut", "offsetof", + "override", "priv", "proc", "pub", + "pure", "ref", "return", "Self", + "self", "sizeof", "static", "struct", + "super", "trait", "true", "type", + "typeof", "unsafe", "unsized", "use", + "virtual", "where", "while", "yield" +}; +const set<string> RUST_RESERVED_WORDS_SET( + RUST_RESERVED_WORDS, + RUST_RESERVED_WORDS + sizeof(RUST_RESERVED_WORDS)/sizeof(RUST_RESERVED_WORDS[0]) +); + +static const string SYNC_CLIENT_GENERIC_BOUND_VARS("<IP, OP>"); +static const string SYNC_CLIENT_GENERIC_BOUNDS("where IP: TInputProtocol, OP: TOutputProtocol"); + +// FIXME: extract common TMessageIdentifier function +// FIXME: have to_rust_type deal with Option + +class t_rs_generator : public t_generator { +public: + t_rs_generator( + t_program* program, + const std::map<std::string, std::string>&, + const std::string& + ) : t_generator(program) { + gen_dir_ = get_out_dir(); + } + + /** + * Init and close methods + */ + + void init_generator() override; + void close_generator() override; + + /** + * Program-level generation functions + */ + + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_const(t_const* tconst) override; + void generate_struct(t_struct* tstruct) override; + void generate_xception(t_struct* txception) override; + void generate_service(t_service* tservice) override; + +private: + // struct type + // T_REGULAR: user-defined struct in the IDL + // T_ARGS: struct used to hold all service-call parameters + // T_RESULT: struct used to hold all service-call returns and exceptions + // T_EXCEPTION: user-defined exception in the IDL + enum e_struct_type { T_REGULAR, T_ARGS, T_RESULT, T_EXCEPTION }; + + // Directory to which generated code is written. + string gen_dir_; + + // File to which generated code is written. + ofstream_with_content_based_conditional_update f_gen_; + + // Write the common compiler attributes and module includes to the top of the auto-generated file. + void render_attributes_and_includes(); + + // Create the closure of Rust modules referenced by this service. + void compute_service_referenced_modules(t_service *tservice, set<string> &referenced_modules); + + // Write the rust representation of an enum. + void render_enum_definition(t_enum* tenum, const string& enum_name); + + // Write the impl blocks associated with the traits necessary to convert an enum to/from an i32. + void render_enum_conversion(t_enum* tenum, const string& enum_name); + + // Write the impl block associated with the rust representation of an enum. This includes methods + // to write the enum to a protocol, read it from a protocol, etc. + void render_enum_impl(const string& enum_name); + + // Write a simple rust const value (ie. `pub const FOO: foo...`). + void render_const_value(const string& name, t_type* ttype, t_const_value* tvalue); + + // Write a constant list, set, map or struct. These constants require allocation and cannot be defined + // using a 'pub const'. As a result, I create a holder struct with a single `const_value` method that + // returns the initialized instance. + void render_const_value_holder(const string& name, t_type* ttype, t_const_value* tvalue); + + // Write the actual const value - the right side of a const definition. + void render_const_value(t_type* ttype, t_const_value* tvalue, bool is_owned = true); + + // Write a const struct (returned from `const_value` method). + void render_const_struct(t_type* ttype, t_const_value* tvalue); + + // Write a const list (returned from `const_value` method). + void render_const_list(t_type* ttype, t_const_value* tvalue); + + // Write a const set (returned from `const_value` method). + void render_const_set(t_type* ttype, t_const_value* tvalue); + + // Write a const map (returned from `const_value` method). + void render_const_map(t_type* ttype, t_const_value* tvalue); + + // Write the code to insert constant values into a rust vec or set. The + // `insert_function` is the rust function that we'll use to insert the elements. + void render_container_const_value( + const string& insert_function, + t_type* ttype, + t_const_value* tvalue + ); + + // Write the rust representation of a thrift struct to the generated file. Set `struct_type` to `T_ARGS` + // if rendering the struct used to pack arguments for a service call. When `struct_type` is `T_ARGS` the + // struct and its members have module visibility, and all fields are required. When `struct_type` is + // anything else the struct and its members have public visibility and fields have the visibility set + // in their definition. + void render_struct(const string& struct_name, t_struct* tstruct, t_rs_generator::e_struct_type struct_type); + + // Write the comment block preceding a type definition (and implementation). + void render_type_comment(const string& struct_name); + + // Write the rust representation of a thrift struct. Supports argument structs, result structs, + // user-defined structs and exception structs. The exact struct type to be generated is controlled + // by the `struct_type` parameter, which (among other things) modifies the visibility of the + // written struct and members, controls which trait implementations are generated. + void render_struct_definition( + const string& struct_name, + t_struct* tstruct, + t_rs_generator::e_struct_type struct_type + ); + + // Writes the impl block associated with the rust representation of a struct. At minimum this + // contains the methods to read from a protocol and write to a protocol. Additional methods may + // be generated depending on `struct_type`. + void render_struct_impl( + const string& struct_name, + t_struct* tstruct, + t_rs_generator::e_struct_type struct_type + ); + + // Generate a `fn new(...)` for a struct with name `struct_name` and type `t_struct`. The auto-generated + // code may include generic type parameters to make the constructor more ergonomic. `struct_type` controls + // the visibility of the generated constructor. + void render_struct_constructor( + const string& struct_name, + t_struct* tstruct, + t_rs_generator::e_struct_type struct_type + ); + + // Write the `ok_or` method added to all Thrift service call result structs. You can use this method + // to convert a struct into a `Result` and use it in a `try!` or combinator chain. + void render_result_struct_to_result_method(t_struct* tstruct); + + // Write the implementations for the `Error` and `Debug` traits. These traits are necessary for a + // user-defined exception to be properly handled as Rust errors. + void render_exception_struct_error_trait_impls(const string& struct_name, t_struct* tstruct); + + // Write the implementations for the `Default`. This trait allows you to specify only the fields you want + // and use `..Default::default()` to fill in the rest. + void render_struct_default_trait_impl(const string& struct_name, t_struct* tstruct); + + // Write the function that serializes a struct to its wire representation. If `struct_type` is `T_ARGS` + // then all fields are considered "required", if not, the default optionality is used. + void render_struct_sync_write(t_struct *tstruct, t_rs_generator::e_struct_type struct_type); + + // Helper function that serializes a single struct field to its wire representation. Unpacks the + // variable (since it may be optional) and serializes according to the optionality rules required by `req`. + // Variables in auto-generated code are passed by reference. Since this function may be called in + // contexts where the variable is *already* a reference you can set `field_var_is_ref` to `true` to avoid + // generating an extra, unnecessary `&` that the compiler will have to automatically dereference. + void render_struct_field_sync_write( + const string &field_var, + bool field_var_is_ref, + t_field *tfield, + t_field::e_req req); + + // Write the rust function that serializes a single type (i.e. a i32 etc.) to its wire representation. + // Variables in auto-generated code are passed by reference. Since this function may be called in + // contexts where the variable is *already* a reference you can set `type_var_is_ref` to `true` to avoid + // generating an extra, unnecessary `&` that the compiler will have to automatically dereference. + void render_type_sync_write(const string &type_var, bool type_var_is_ref, t_type *ttype); + + // Write a list to the output protocol. `list_variable` is the variable containing the list + // that will be written to the output protocol. + // Variables in auto-generated code are passed by reference. Since this function may be called in + // contexts where the variable is *already* a reference you can set `list_var_is_ref` to `true` to avoid + // generating an extra, unnecessary `&` that the compiler will have to automatically dereference. + void render_list_sync_write(const string &list_var, bool list_var_is_ref, t_list *tlist); + + // Write a set to the output protocol. `set_variable` is the variable containing the set that will + // be written to the output protocol. + // Variables in auto-generated code are passed by reference. Since this function may be called in + // contexts where the variable is *already* a reference you can set `set_var_is_ref` to `true` to avoid + // generating an extra, unnecessary `&` that the compiler will have to automatically dereference. + void render_set_sync_write(const string &set_var, bool set_var_is_ref, t_set *tset); + + // Write a map to the output protocol. `map_variable` is the variable containing the map that will + // be written to the output protocol. + // Variables in auto-generated code are passed by reference. Since this function may be called in + // contexts where the variable is *already* a reference you can set `map_var_is_ref` to `true` to avoid + // generating an extra, unnecessary `&` that the compiler will have to automatically dereference. + void render_map_sync_write(const string &map_var, bool map_var_is_ref, t_map *tset); + + // Return `true` if we need to dereference ths type when writing an element from a container. + // Iterations on rust containers are performed as follows: `for v in &values { ... }` + // where `v` has type `&RUST_TYPE` All defined functions take primitives by value, so, if the + // rendered code is calling such a function it has to dereference `v`. + bool needs_deref_on_container_write(t_type* ttype); + + // Return the variable (including all dereferences) required to write values from a rust container + // to the output protocol. For example, if you were iterating through a container and using the temp + // variable `v` to represent each element, then `ttype` is the type stored in the container and + // `base_var` is "v". The return value is the actual string you will have to use to properly reference + // the temp variable for writing to the output protocol. + string string_container_write_variable(t_type* ttype, const string& base_var); + + // Write the code to read bytes from the wire into the given `t_struct`. `struct_name` is the + // actual Rust name of the `t_struct`. If `struct_type` is `T_ARGS` then all struct fields are + // necessary. Otherwise, the field's default optionality is used. + void render_struct_sync_read(const string &struct_name, t_struct *tstruct, t_rs_generator::e_struct_type struct_type); + + // Write the rust function that deserializes a single type (i.e. i32 etc.) from its wire representation. + // Set `is_boxed` to `true` if the resulting value should be wrapped in a `Box::new(...)`. + void render_type_sync_read(const string &type_var, t_type *ttype, bool is_boxed = false); + + // Read the wire representation of a list and convert it to its corresponding rust implementation. + // The deserialized list is stored in `list_variable`. + void render_list_sync_read(t_list *tlist, const string &list_variable); + + // Read the wire representation of a set and convert it to its corresponding rust implementation. + // The deserialized set is stored in `set_variable`. + void render_set_sync_read(t_set *tset, const string &set_variable); + + // Read the wire representation of a map and convert it to its corresponding rust implementation. + // The deserialized map is stored in `map_variable`. + void render_map_sync_read(t_map *tmap, const string &map_variable); + + // Return a temporary variable used to store values when deserializing nested containers. + string struct_field_read_temp_variable(t_field* tfield); + + // Top-level function that calls the various render functions necessary to write the rust representation + // of a thrift union (i.e. an enum). + void render_union(t_struct* tstruct); + + // Write the enum corresponding to the Thrift union. + void render_union_definition(const string& union_name, t_struct* tstruct); + + // Write the enum impl (with read/write functions) for the Thrift union. + void render_union_impl(const string& union_name, t_struct* tstruct); + + // Write the `ENUM::write_to_out_protocol` function. + void render_union_sync_write(const string &union_name, t_struct *tstruct); + + // Write the `ENUM::read_from_in_protocol` function. + void render_union_sync_read(const string &union_name, t_struct *tstruct); + + // Top-level function that calls the various render functions necessary to write the rust representation + // of a Thrift client. + void render_sync_client(t_service* tservice); + + // Write the trait with the service-call methods for `tservice`. + void render_sync_client_trait(t_service *tservice); + + // Write the trait to be implemented by the client impl if end users can use it to make service calls. + void render_sync_client_marker_trait(t_service *tservice); + + // Write the code to create the Thrift service sync client struct and its matching 'impl' block. + void render_sync_client_definition_and_impl(const string& client_impl_name); + + // Write the code to create the `SyncClient::new` functions as well as any other functions + // callers would like to use on the Thrift service sync client. + void render_sync_client_lifecycle_functions(const string& client_struct); + + // Write the code to create the impl block for the `TThriftClient` trait. Since generated + // Rust Thrift clients perform all their operations using methods defined in this trait, we + // have to implement it for the client structs. + void render_sync_client_tthriftclient_impl(const string &client_impl_name); + + // Write the marker traits for any service(s) being extended, including the one for the current + // service itself (i.e. `tservice`) + void render_sync_client_marker_trait_impls(t_service *tservice, const string &impl_struct_name); + + // Generate a list of all the traits this Thrift client struct extends. + string sync_client_marker_traits_for_extension(t_service *tservice); + + // Top-level function that writes the code to make the Thrift service calls. + void render_sync_client_process_impl(t_service* tservice); + + // Write the actual function that calls out to the remote service and processes its response. + void render_sync_send_recv_wrapper(t_function* tfunc); + + // Write the `send` functionality for a Thrift service call represented by a `t_service->t_function`. + void render_sync_send(t_function* tfunc); + + // Write the `recv` functionality for a Thrift service call represented by a `t_service->t_function`. + // This method is only rendered if the function is *not* oneway. + void render_sync_recv(t_function* tfunc); + + void render_sync_processor(t_service *tservice); + + void render_sync_handler_trait(t_service *tservice); + void render_sync_processor_definition_and_impl(t_service *tservice); + void render_sync_process_delegation_functions(t_service *tservice); + void render_sync_process_function(t_function *tfunc, const string &handler_type); + void render_process_match_statements(t_service* tservice); + void render_sync_handler_succeeded(t_function *tfunc); + void render_sync_handler_failed(t_function *tfunc); + void render_sync_handler_failed_user_exception_branch(t_function *tfunc); + void render_sync_handler_failed_application_exception_branch(t_function *tfunc, const string &app_err_var); + void render_sync_handler_failed_default_exception_branch(t_function *tfunc); + void render_sync_handler_send_exception_response(t_function *tfunc, const string &err_var); + void render_service_call_structs(t_service* tservice); + void render_service_call_args_struct(t_function* tfunc); + void render_service_call_result_value_struct(t_function* tfunc); + + string handler_successful_return_struct(t_function* tfunc); + + // Writes the result of `render_thrift_error_struct` wrapped in an `Err(thrift::Error(...))`. + void render_thrift_error( + const string& error_kind, + const string& error_struct, + const string& sub_error_kind, + const string& error_message + ); + + // Write a thrift::Error variant struct. Error structs take the form: + // ``` + // pub struct error_struct { + // kind: sub_error_kind, + // message: error_message, + // } + // ``` + // A concrete example is: + // ``` + // pub struct ApplicationError { + // kind: ApplicationErrorKind::Unknown, + // message: "This is some error message", + // } + // ``` + void render_thrift_error_struct( + const string& error_struct, + const string& sub_error_kind, + const string& error_message + ); + + // Return a string containing all the unpacked service call args given a service call function + // `t_function`. Prepends the args with either `&mut self` or `&self` and includes the arg types + // in the returned string, for example: + // `fn foo(&mut self, field_0: String)`. + string rust_sync_service_call_declaration(t_function* tfunc, bool self_is_mutable); + + // Return a string containing all the unpacked service call args given a service call function + // `t_function`. Only includes the arg names, each of which is prefixed with the optional prefix + // `field_prefix`, for example: `self.field_0`. + string rust_sync_service_call_invocation(t_function* tfunc, const string& field_prefix = ""); + + // Return a string containing all fields in the struct `tstruct` for use in a function declaration. + // Each field is followed by its type, for example: `field_0: String`. + string struct_to_declaration(t_struct* tstruct, t_rs_generator::e_struct_type struct_type); + + // Return a string containing all fields in the struct `tstruct` for use in a function call, + // for example: `field_0: String`. + string struct_to_invocation(t_struct* tstruct, const string& field_prefix = ""); + + // Write the documentation for a struct, service-call or other documentation-annotated element. + void render_rustdoc(t_doc* tdoc); + + // Return `true` if the true type of `ttype` is a thrift double, `false` otherwise. + bool is_double(t_type* ttype); + + // Return a string representing the rust type given a `t_type`. + string to_rust_type(t_type* ttype, bool ordered_float = true); + + // Return a string representing the `const` rust type given a `t_type` + string to_rust_const_type(t_type* ttype, bool ordered_float = true); + + // Return a string representing the rift `protocol::TType` given a `t_type`. + string to_rust_field_type_enum(t_type* ttype); + + // Return the default value to be used when initializing a struct field which has `OPT_IN_REQ_OUT` + // optionality. + string opt_in_req_out_value(t_type* ttype); + + // Return `true` if we can write a const of the form `pub const FOO: ...`. + bool can_generate_simple_const(t_type* ttype); + + // Return `true` if we cannot write a standard Rust constant (because the type needs some allocation). + bool can_generate_const_holder(t_type* ttype); + + // Return `true` if this type is a void, and should be represented by the rust `()` type. + bool is_void(t_type* ttype); + + t_field::e_req actual_field_req(t_field* tfield, t_rs_generator::e_struct_type struct_type); + + // Return `true` if this `t_field::e_req` is either `t_field::T_OPTIONAL` or `t_field::T_OPT_IN_REQ_OUT` + // and needs to be wrapped by an `Option<TYPE_NAME>`, `false` otherwise. + bool is_optional(t_field::e_req req); + + // Return `true` if the service call has arguments, `false` otherwise. + bool has_args(t_function* tfunc); + + // Return `true` if a service call has non-`()` arguments, `false` otherwise. + bool has_non_void_args(t_function* tfunc); + + // Return `pub ` (notice trailing whitespace!) if the struct should be public, `` (empty string) otherwise. + string visibility_qualifier(t_rs_generator::e_struct_type struct_type); + + // Returns the namespace prefix for a given Thrift service. If the type is defined in the presently-computed + // Thrift program, then an empty string is returned. + string rust_namespace(t_service* tservice); + + // Returns the namespace prefix for a given Thrift type. If the type is defined in the presently-computed + // Thrift program, then an empty string is returned. + string rust_namespace(t_type* ttype); + + // Returns the camel-cased name for a Rust struct type. Handles the case where `tstruct->get_name()` is + // a reserved word. + string rust_struct_name(t_struct* tstruct); + + // Returns the snake-cased name for a Rust field or local variable. Handles the case where + // `tfield->get_name()` is a reserved word. + string rust_field_name(t_field* tstruct); + + // Returns the camel-cased name for a Rust union type. Handles the case where `tstruct->get_name()` is + // a reserved word. + string rust_union_field_name(t_field* tstruct); + + // Converts any variable name into a 'safe' variant that does not clash with any Rust reserved keywords. + string rust_safe_name(const string& name); + + // Return `true` if the name is a reserved Rust keyword, `false` otherwise. + bool is_reserved(const string& name); + + // Return the name of the function that users will invoke to make outgoing service calls. + string service_call_client_function_name(t_function* tfunc); + + // Return the name of the function that users will have to implement to handle incoming service calls. + string service_call_handler_function_name(t_function* tfunc); + + // Return the name of the struct used to pack the arguments for the thrift service call. + string service_call_args_struct_name(t_function* tfunc); + + // Return the name of the struct used to pack the return value + // and user-defined exceptions for the thrift service call. + string service_call_result_struct_name(t_function* tfunc); + + string rust_sync_client_marker_trait_name(t_service* tservice); + + // Return the trait name for the sync service client given a `t_service`. + string rust_sync_client_trait_name(t_service* tservice); + + // Return the name for the sync service client struct given a `t_service`. + string rust_sync_client_impl_name(t_service* tservice); + + // Return the trait name that users will have to implement for the server half of a Thrift service. + string rust_sync_handler_trait_name(t_service* tservice); + + // Return the struct name for the server half of a Thrift service. + string rust_sync_processor_name(t_service* tservice); + + // Return the struct name for the struct that contains all the service-call implementations for + // the server half of a Thrift service. + string rust_sync_processor_impl_name(t_service *tservice); + + // Return the variant name for an enum variant + string rust_enum_variant_name(const string& name); + + // Properly uppercase names for use in Rust. + string rust_upper_case(const string& name); + + // Snake-case field, parameter and function names and make them Rust friendly. + string rust_snake_case(const string& name); + + // Camel-case type/variant names and make them Rust friendly. + string rust_camel_case(const string& name); + + // Replace all instances of `search_string` with `replace_string` in `target`. + void string_replace(string& target, const string& search_string, const string& replace_string); + + // Adjust field identifier to correctly handle unspecified field identifiers + // THRIFT-4953 + string rust_safe_field_id(int32_t id); +}; + +void t_rs_generator::init_generator() { + // make output directory for this thrift program + MKDIR(gen_dir_.c_str()); + + // create the file into which we're going to write the generated code + string f_gen_name = gen_dir_ + "/" + rust_snake_case(get_program()->get_name()) + ".rs"; + f_gen_.open(f_gen_name.c_str()); + + // header comment + f_gen_ << "// " << autogen_summary() << endl; + f_gen_ << "// DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING" << endl; + f_gen_ << endl; + + render_attributes_and_includes(); +} + +void t_rs_generator::render_attributes_and_includes() { + // turn off some compiler/clippy warnings + + // code always includes BTreeMap/BTreeSet/OrderedFloat + f_gen_ << "#![allow(unused_imports)]" << endl; + // code might not include imports from crates + f_gen_ << "#![allow(unused_extern_crates)]" << endl; + // constructors take *all* struct parameters, which can trigger the "too many arguments" warning + // some auto-gen'd types can be deeply nested. clippy recommends factoring them out which is hard to autogen + f_gen_ << "#![cfg_attr(feature = \"cargo-clippy\", allow(too_many_arguments, type_complexity))]" << endl; + // prevent rustfmt from running against this file + // lines are too long, code is (thankfully!) not visual-indented, etc. + f_gen_ << "#![cfg_attr(rustfmt, rustfmt_skip)]" << endl; + f_gen_ << endl; + + // add standard includes + f_gen_ << "extern crate thrift;" << endl; + f_gen_ << endl; + f_gen_ << "use thrift::OrderedFloat;" << endl; + f_gen_ << "use std::cell::RefCell;" << endl; + f_gen_ << "use std::collections::{BTreeMap, BTreeSet};" << endl; + f_gen_ << "use std::convert::{From, TryFrom};" << endl; + f_gen_ << "use std::default::Default;" << endl; + f_gen_ << "use std::error::Error;" << endl; + f_gen_ << "use std::fmt;" << endl; + f_gen_ << "use std::fmt::{Display, Formatter};" << endl; + f_gen_ << "use std::rc::Rc;" << endl; + f_gen_ << endl; + f_gen_ << "use thrift::{ApplicationError, ApplicationErrorKind, ProtocolError, ProtocolErrorKind, TThriftClient};" << endl; + f_gen_ << "use thrift::protocol::{TFieldIdentifier, TListIdentifier, TMapIdentifier, TMessageIdentifier, TMessageType, TInputProtocol, TOutputProtocol, TSetIdentifier, TStructIdentifier, TType};" << endl; + f_gen_ << "use thrift::protocol::field_id;" << endl; + f_gen_ << "use thrift::protocol::verify_expected_message_type;" << endl; + f_gen_ << "use thrift::protocol::verify_expected_sequence_number;" << endl; + f_gen_ << "use thrift::protocol::verify_expected_service_call;" << endl; + f_gen_ << "use thrift::protocol::verify_required_field_exists;" << endl; + f_gen_ << "use thrift::server::TProcessor;" << endl; + f_gen_ << endl; + + // add all the program includes + // NOTE: this is more involved than you would expect because of service extension + // Basically, I have to find the closure of all the services and include their modules at the top-level + + set<string> referenced_modules; + + // first, start by adding explicit thrift includes + const vector<t_program*> includes = get_program()->get_includes(); + vector<t_program*>::const_iterator includes_iter; + for(includes_iter = includes.begin(); includes_iter != includes.end(); ++includes_iter) { + referenced_modules.insert((*includes_iter)->get_name()); + } + + // next, recursively iterate through all the services and add the names of any programs they reference + const vector<t_service*> services = get_program()->get_services(); + vector<t_service*>::const_iterator service_iter; + for (service_iter = services.begin(); service_iter != services.end(); ++service_iter) { + compute_service_referenced_modules(*service_iter, referenced_modules); + } + + // finally, write all the "pub use..." declarations + if (!referenced_modules.empty()) { + set<string>::iterator module_iter; + for (module_iter = referenced_modules.begin(); module_iter != referenced_modules.end(); ++module_iter) { + f_gen_ << "use " << rust_snake_case(*module_iter) << ";" << endl; + } + f_gen_ << endl; + } +} + +void t_rs_generator::compute_service_referenced_modules( + t_service *tservice, + set<string> &referenced_modules +) { + t_service* extends = tservice->get_extends(); + if (extends) { + if (extends->get_program() != get_program()) { + referenced_modules.insert(extends->get_program()->get_name()); + } + compute_service_referenced_modules(extends, referenced_modules); + } +} + +void t_rs_generator::close_generator() { + f_gen_.close(); +} + +//----------------------------------------------------------------------------- +// +// Consts +// +// NOTE: consider using macros to generate constants +// +//----------------------------------------------------------------------------- + +// This is worse than it should be because constants +// aren't (sensibly) limited to scalar types +void t_rs_generator::generate_const(t_const* tconst) { + string name = tconst->get_name(); + t_type* ttype = tconst->get_type(); + t_const_value* tvalue = tconst->get_value(); + + if (can_generate_simple_const(ttype)) { + render_const_value(name, ttype, tvalue); + } else if (can_generate_const_holder(ttype)) { + render_const_value_holder(name, ttype, tvalue); + } else { + throw "cannot generate const for " + name; + } +} + +void t_rs_generator::render_const_value(const string& name, t_type* ttype, t_const_value* tvalue) { + if (!can_generate_simple_const(ttype)) { + throw "cannot generate simple rust constant for " + ttype->get_name(); + } + + f_gen_ << "pub const " << rust_upper_case(name) << ": " << to_rust_const_type(ttype) << " = "; + render_const_value(ttype, tvalue, false); + f_gen_ << ";" << endl; + f_gen_ << endl; +} + +void t_rs_generator::render_const_value_holder(const string& name, t_type* ttype, t_const_value* tvalue) { + if (!can_generate_const_holder(ttype)) { + throw "cannot generate constant holder for " + ttype->get_name(); + } + + string holder_name("Const" + rust_camel_case(name)); + + f_gen_ << indent() << "pub struct " << holder_name << ";" << endl; + f_gen_ << indent() << "impl " << holder_name << " {" << endl; + indent_up(); + + f_gen_ << indent() << "pub fn const_value() -> " << to_rust_type(ttype) << " {" << endl; + indent_up(); + render_const_value(ttype, tvalue); + indent_down(); + f_gen_ << indent() << "}" << endl; + + indent_down(); + f_gen_ << indent() << "}" << endl; + f_gen_ << endl; +} + +void t_rs_generator::render_const_value(t_type* ttype, t_const_value* tvalue, bool is_owned) { + if (ttype->is_base_type()) { + t_base_type* tbase_type = (t_base_type*)ttype; + switch (tbase_type->get_base()) { + case t_base_type::TYPE_STRING: + if (tbase_type->is_binary()) { + if (is_owned) { + f_gen_ << "\"" << tvalue->get_string() << "\""<< ".to_owned().into_bytes()"; + } else { + f_gen_ << "b\"" << tvalue->get_string() << "\""; + } + } else { + f_gen_ << "\"" << tvalue->get_string() << "\""; + if (is_owned) { + f_gen_ << ".to_owned()"; + } + } + break; + case t_base_type::TYPE_BOOL: + f_gen_ << (tvalue->get_integer() ? "true" : "false"); + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + f_gen_ << tvalue->get_integer(); + break; + case t_base_type::TYPE_DOUBLE: + f_gen_ << "OrderedFloat::from(" << tvalue->get_double() << " as f64)"; + break; + default: + throw "cannot generate const value for " + t_base_type::t_base_name(tbase_type->get_base()); + } + } else if (ttype->is_typedef()) { + render_const_value(get_true_type(ttype), tvalue); + } else if (ttype->is_enum()) { + f_gen_ << indent() << "{" << endl; + indent_up(); + f_gen_ + << indent() + << to_rust_type(ttype) + << "::try_from(" + << tvalue->get_integer() + << ").expect(\"expecting valid const value\")" + << endl; + indent_down(); + f_gen_ << indent() << "}" << endl; + } else if (ttype->is_struct() || ttype->is_xception()) { + render_const_struct(ttype, tvalue); + } else if (ttype->is_container()) { + f_gen_ << indent() << "{" << endl; + indent_up(); + + if (ttype->is_list()) { + render_const_list(ttype, tvalue); + } else if (ttype->is_set()) { + render_const_set(ttype, tvalue); + } else if (ttype->is_map()) { + render_const_map(ttype, tvalue); + } else { + throw "cannot generate const container value for " + ttype->get_name(); + } + + indent_down(); + f_gen_ << indent() << "}" << endl; + } else { + throw "cannot generate const value for " + ttype->get_name(); + } +} + +void t_rs_generator::render_const_struct(t_type* ttype, t_const_value*) { + if (((t_struct*)ttype)->is_union()) { + f_gen_ << indent() << "{" << endl; + indent_up(); + f_gen_ << indent() << "unimplemented!()" << endl; + indent_down(); + f_gen_ << indent() << "}" << endl; + } else { + f_gen_ << indent() << "{" << endl; + indent_up(); + f_gen_ << indent() << "unimplemented!()" << endl; + indent_down(); + f_gen_ << indent() << "}" << endl; + } +} + +void t_rs_generator::render_const_list(t_type* ttype, t_const_value* tvalue) { + t_type* elem_type = ((t_list*)ttype)->get_elem_type(); + f_gen_ << indent() << "let mut l: Vec<" << to_rust_type(elem_type) << "> = Vec::new();" << endl; + const vector<t_const_value*>& elems = tvalue->get_list(); + vector<t_const_value*>::const_iterator elem_iter; + for(elem_iter = elems.begin(); elem_iter != elems.end(); ++elem_iter) { + t_const_value* elem_value = (*elem_iter); + render_container_const_value("l.push", elem_type, elem_value); + } + f_gen_ << indent() << "l" << endl; +} + +void t_rs_generator::render_const_set(t_type* ttype, t_const_value* tvalue) { + t_type* elem_type = ((t_set*)ttype)->get_elem_type(); + f_gen_ << indent() << "let mut s: BTreeSet<" << to_rust_type(elem_type) << "> = BTreeSet::new();" << endl; + const vector<t_const_value*>& elems = tvalue->get_list(); + vector<t_const_value*>::const_iterator elem_iter; + for(elem_iter = elems.begin(); elem_iter != elems.end(); ++elem_iter) { + t_const_value* elem_value = (*elem_iter); + render_container_const_value("s.insert", elem_type, elem_value); + } + f_gen_ << indent() << "s" << endl; +} + +void t_rs_generator::render_const_map(t_type* ttype, t_const_value* tvalue) { + t_type* key_type = ((t_map*)ttype)->get_key_type(); + t_type* val_type = ((t_map*)ttype)->get_val_type(); + f_gen_ + << indent() + << "let mut m: BTreeMap<" + << to_rust_type(key_type) << ", " << to_rust_type(val_type) + << "> = BTreeMap::new();" + << endl; + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& elems = tvalue->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator elem_iter; + for (elem_iter = elems.begin(); elem_iter != elems.end(); ++elem_iter) { + t_const_value* key_value = elem_iter->first; + t_const_value* val_value = elem_iter->second; + if (get_true_type(key_type)->is_base_type()) { + f_gen_ << indent() << "let k = "; + render_const_value(key_type, key_value); + f_gen_ << ";" << endl; + } else { + f_gen_ << indent() << "let k = {" << endl; + indent_up(); + render_const_value(key_type, key_value); + indent_down(); + f_gen_ << indent() << "};" << endl; + } + if (get_true_type(val_type)->is_base_type()) { + f_gen_ << indent() << "let v = "; + render_const_value(val_type, val_value); + f_gen_ << ";" << endl; + } else { + f_gen_ << indent() << "let v = {" << endl; + indent_up(); + render_const_value(val_type, val_value); + indent_down(); + f_gen_ << indent() << "};" << endl; + } + f_gen_ << indent() << "m.insert(k, v);" << endl; + } + f_gen_ << indent() << "m" << endl; +} + +void t_rs_generator::render_container_const_value( + const string& insert_function, + t_type* ttype, + t_const_value* tvalue +) { + if (get_true_type(ttype)->is_base_type()) { + f_gen_ << indent() << insert_function << "("; + render_const_value(ttype, tvalue); + f_gen_ << ");" << endl; + } else { + f_gen_ << indent() << insert_function << "(" << endl; + indent_up(); + render_const_value(ttype, tvalue); + indent_down(); + f_gen_ << indent() << ");" << endl; + } +} + +//----------------------------------------------------------------------------- +// +// Typedefs +// +//----------------------------------------------------------------------------- + +void t_rs_generator::generate_typedef(t_typedef* ttypedef) { + std::string actual_type = to_rust_type(ttypedef->get_type()); + f_gen_ << "pub type " << rust_safe_name(ttypedef->get_symbolic()) << " = " << actual_type << ";" << endl; + f_gen_ << endl; +} + +//----------------------------------------------------------------------------- +// +// Enums +// +//----------------------------------------------------------------------------- + +void t_rs_generator::generate_enum(t_enum* tenum) { + string enum_name(rust_camel_case(tenum->get_name())); + render_enum_definition(tenum, enum_name); + render_enum_impl(enum_name); + render_enum_conversion(tenum, enum_name); +} + +void t_rs_generator::render_enum_definition(t_enum* tenum, const string& enum_name) { + render_rustdoc((t_doc*) tenum); + f_gen_ << "#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]" << endl; + f_gen_ << "pub enum " << enum_name << " {" << endl; + indent_up(); + + vector<t_enum_value*> constants = tenum->get_constants(); + vector<t_enum_value*>::iterator constants_iter; + for (constants_iter = constants.begin(); constants_iter != constants.end(); ++constants_iter) { + t_enum_value* val = (*constants_iter); + render_rustdoc((t_doc*) val); + f_gen_ + << indent() + << rust_enum_variant_name(val->get_name()) + << " = " + << val->get_value() + << "," + << endl; + } + + indent_down(); + f_gen_ << "}" << endl; + f_gen_ << endl; +} + +void t_rs_generator::render_enum_impl(const string& enum_name) { + f_gen_ << "impl " << enum_name << " {" << endl; + indent_up(); + + f_gen_ + << indent() + << "pub fn write_to_out_protocol(&self, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> {" + << endl; + indent_up(); + f_gen_ << indent() << "o_prot.write_i32(*self as i32)" << endl; + indent_down(); + f_gen_ << indent() << "}" << endl; + + f_gen_ + << indent() + << "pub fn read_from_in_protocol(i_prot: &mut dyn TInputProtocol) -> thrift::Result<" << enum_name << "> {" + << endl; + indent_up(); + + f_gen_ << indent() << "let enum_value = i_prot.read_i32()?;" << endl; + f_gen_ << indent() << enum_name << "::try_from(enum_value)"; + + indent_down(); + f_gen_ << indent() << "}" << endl; + + indent_down(); + f_gen_ << "}" << endl; + f_gen_ << endl; +} + +void t_rs_generator::render_enum_conversion(t_enum* tenum, const string& enum_name) { + f_gen_ << "impl TryFrom<i32> for " << enum_name << " {" << endl; + indent_up(); + + f_gen_ << indent() << "type Error = thrift::Error;"; + + f_gen_ << indent() << "fn try_from(i: i32) -> Result<Self, Self::Error> {" << endl; + indent_up(); + + f_gen_ << indent() << "match i {" << endl; + indent_up(); + + vector<t_enum_value*> constants = tenum->get_constants(); + vector<t_enum_value*>::iterator constants_iter; + for (constants_iter = constants.begin(); constants_iter != constants.end(); ++constants_iter) { + t_enum_value* val = (*constants_iter); + f_gen_ + << indent() + << val->get_value() + << " => Ok(" << enum_name << "::" << rust_enum_variant_name(val->get_name()) << ")," + << endl; + } + f_gen_ << indent() << "_ => {" << endl; + indent_up(); + render_thrift_error( + "Protocol", + "ProtocolError", + "ProtocolErrorKind::InvalidData", + "format!(\"cannot convert enum constant {} to " + enum_name + "\", i)" + ); + indent_down(); + f_gen_ << indent() << "}," << endl; + + indent_down(); + f_gen_ << indent() << "}" << endl; + + indent_down(); + f_gen_ << indent() << "}" << endl; + + indent_down(); + f_gen_ << "}" << endl; + f_gen_ << endl; +} + +//----------------------------------------------------------------------------- +// +// Structs, Unions and Exceptions +// +//----------------------------------------------------------------------------- + +void t_rs_generator::generate_xception(t_struct* txception) { + render_struct(rust_struct_name(txception), txception, t_rs_generator::T_EXCEPTION); +} + +void t_rs_generator::generate_struct(t_struct* tstruct) { + if (tstruct->is_union()) { + render_union(tstruct); + } else if (tstruct->is_struct()) { + render_struct(rust_struct_name(tstruct), tstruct, t_rs_generator::T_REGULAR); + } else { + throw "cannot generate struct for exception"; + } +} + +void t_rs_generator::render_struct( + const string& struct_name, + t_struct* tstruct, + t_rs_generator::e_struct_type struct_type +) { + render_type_comment(struct_name); + render_struct_definition(struct_name, tstruct, struct_type); + render_struct_impl(struct_name, tstruct, struct_type); + if (struct_type == t_rs_generator::T_REGULAR || struct_type == t_rs_generator::T_EXCEPTION) { + render_struct_default_trait_impl(struct_name, tstruct); + } + if (struct_type == t_rs_generator::T_EXCEPTION) { + render_exception_struct_error_trait_impls(struct_name, tstruct); + } +} + +void t_rs_generator::render_struct_definition( + const string& struct_name, + t_struct* tstruct, + t_rs_generator::e_struct_type struct_type +) { + render_rustdoc((t_doc*) tstruct); + f_gen_ << "#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]" << endl; + f_gen_ << visibility_qualifier(struct_type) << "struct " << struct_name << " {" << endl; + + // render the members + vector<t_field*> members = tstruct->get_sorted_members(); + if (!members.empty()) { + indent_up(); + + vector<t_field*>::iterator members_iter; + for(members_iter = members.begin(); members_iter != members.end(); ++members_iter) { + t_field* member = (*members_iter); + t_field::e_req member_req = actual_field_req(member, struct_type); + + string rust_type = to_rust_type(member->get_type()); + rust_type = is_optional(member_req) ? "Option<" + rust_type + ">" : rust_type; + + render_rustdoc((t_doc*) member); + f_gen_ + << indent() + << visibility_qualifier(struct_type) + << rust_field_name(member) << ": " << rust_type << "," + << endl; + } + + indent_down(); + } + + f_gen_ << "}" << endl; + f_gen_ << endl; +} + +void t_rs_generator::render_exception_struct_error_trait_impls(const string& struct_name, t_struct* tstruct) { + // error::Error trait + f_gen_ << "impl Error for " << struct_name << " {" << endl; + indent_up(); + f_gen_ << indent() << "fn description(&self) -> &str {" << endl; + indent_up(); + f_gen_ << indent() << "\"" << "remote service threw " << tstruct->get_name() << "\"" << endl; // use *original* name + indent_down(); + f_gen_ << indent() << "}" << endl; + indent_down(); + f_gen_ << "}" << endl; + f_gen_ << endl; + + // convert::From trait + f_gen_ << "impl From<" << struct_name << "> for thrift::Error {" << endl; + indent_up(); + f_gen_ << indent() << "fn from(e: " << struct_name << ") -> Self {" << endl; + indent_up(); + f_gen_ << indent() << "thrift::Error::User(Box::new(e))" << endl; + indent_down(); + f_gen_ << indent() << "}" << endl; + indent_down(); + f_gen_ << "}" << endl; + f_gen_ << endl; + + // fmt::Display trait + f_gen_ << "impl Display for " << struct_name << " {" << endl; + indent_up(); + f_gen_ << indent() << "fn fmt(&self, f: &mut Formatter) -> fmt::Result {" << endl; + indent_up(); + f_gen_ << indent() << "self.description().fmt(f)" << endl; + indent_down(); + f_gen_ << indent() << "}" << endl; + indent_down(); + f_gen_ << "}" << endl; + f_gen_ << endl; +} + +void t_rs_generator::render_struct_default_trait_impl(const string& struct_name, t_struct* tstruct) { + bool has_required_field = false; + + const vector<t_field*>& members = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator members_iter; + for (members_iter = members.begin(); members_iter != members.end(); ++members_iter) { + t_field* member = *members_iter; + if (!is_optional(member->get_req())) { + has_required_field = true; + break; + } + } + + if (has_required_field) { + return; + } + + f_gen_ << "impl Default for " << struct_name << " {" << endl; + indent_up(); + f_gen_ << indent() << "fn default() -> Self {" << endl; + indent_up(); + + if (members.empty()) { + f_gen_ << indent() << struct_name << "{}" << endl; + } else { + f_gen_ << indent() << struct_name << "{" << endl; + indent_up(); + for (members_iter = members.begin(); members_iter != members.end(); ++members_iter) { + t_field *member = (*members_iter); + string member_name(rust_field_name(member)); + f_gen_ << indent() << member_name << ": " << opt_in_req_out_value(member->get_type()) << "," << endl; + } + indent_down(); + f_gen_ << indent() << "}" << endl; + } + + indent_down(); + f_gen_ << indent() << "}" << endl; + indent_down(); + f_gen_ << "}" << endl; + f_gen_ << endl; +} + +void t_rs_generator::render_struct_impl( + const string& struct_name, + t_struct* tstruct, + t_rs_generator::e_struct_type struct_type +) { + f_gen_ << "impl " << struct_name << " {" << endl; + indent_up(); + + if (struct_type == t_rs_generator::T_REGULAR || struct_type == t_rs_generator::T_EXCEPTION) { + render_struct_constructor(struct_name, tstruct, struct_type); + } + + render_struct_sync_read(struct_name, tstruct, struct_type); + render_struct_sync_write(tstruct, struct_type); + + if (struct_type == t_rs_generator::T_RESULT) { + render_result_struct_to_result_method(tstruct); + } + + indent_down(); + f_gen_ << "}" << endl; + f_gen_ << endl; +} + +void t_rs_generator::render_struct_constructor( + const string& struct_name, + t_struct* tstruct, + t_rs_generator::e_struct_type struct_type +) { + const vector<t_field*>& members = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator members_iter; + + // build the convenience type parameters that allows us to pass unwrapped values to a constructor and + // have them automatically converted into Option<value> + bool first_arg = true; + + ostringstream generic_type_parameters; + ostringstream generic_type_qualifiers; + for(members_iter = members.begin(); members_iter != members.end(); ++members_iter) { + t_field* member = (*members_iter); + t_field::e_req member_req = actual_field_req(member, struct_type); + + if (is_optional(member_req)) { + if (first_arg) { + first_arg = false; + } else { + generic_type_parameters << ", "; + generic_type_qualifiers << ", "; + } + generic_type_parameters << "F" << rust_safe_field_id(member->get_key()); + generic_type_qualifiers << "F" << rust_safe_field_id(member->get_key()) << ": Into<Option<" << to_rust_type(member->get_type()) << ">>"; + } + } + + string type_parameter_string = generic_type_parameters.str(); + if (type_parameter_string.length() != 0) { + type_parameter_string = "<" + type_parameter_string + ">"; + } + + string type_qualifier_string = generic_type_qualifiers.str(); + if (type_qualifier_string.length() != 0) { + type_qualifier_string = "where " + type_qualifier_string + " "; + } + + // now build the actual constructor arg list + // when we're building this list we have to use the type parameters in place of the actual type names + // if necessary + ostringstream args; + first_arg = true; + for(members_iter = members.begin(); members_iter != members.end(); ++members_iter) { + t_field* member = (*members_iter); + t_field::e_req member_req = actual_field_req(member, struct_type); + string member_name(rust_field_name(member)); + + if (first_arg) { + first_arg = false; + } else { + args << ", "; + } + + if (is_optional(member_req)) { + args << member_name << ": " << "F" << rust_safe_field_id(member->get_key()); + } else { + args << member_name << ": " << to_rust_type(member->get_type()); + } + } + + string arg_string = args.str(); + + string visibility(visibility_qualifier(struct_type)); + f_gen_ + << indent() + << visibility + << "fn new" + << type_parameter_string + << "(" + << arg_string + << ") -> " + << struct_name + << " " + << type_qualifier_string + << "{" + << endl; + indent_up(); + + if (members.size() == 0) { + f_gen_ << indent() << struct_name << " {}" << endl; + } else { + f_gen_ << indent() << struct_name << " {" << endl; + indent_up(); + + for(members_iter = members.begin(); members_iter != members.end(); ++members_iter) { + t_field* member = (*members_iter); + t_field::e_req member_req = actual_field_req(member, struct_type); + string member_name(rust_field_name(member)); + + if (is_optional(member_req)) { + f_gen_ << indent() << member_name << ": " << member_name << ".into()," << endl; + } else { + f_gen_ << indent() << member_name << ": " << member_name << "," << endl; + } + } + + indent_down(); + f_gen_ << indent() << "}" << endl; + } + + indent_down(); + f_gen_ << indent() << "}" << endl; +} + +void t_rs_generator::render_result_struct_to_result_method(t_struct* tstruct) { + // we don't use the rust struct name in this method, just the service call name + string service_call_name = tstruct->get_name(); + + // check that we actually have a result + size_t index = service_call_name.find(RESULT_STRUCT_SUFFIX, 0); + if (index == std::string::npos) { + throw "result struct " + service_call_name + " missing result suffix"; + } else { + service_call_name.replace(index, 6, ""); + } + + const vector<t_field*>& members = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator members_iter; + + // find out what the call's expected return type was + string rust_return_type = "()"; + for(members_iter = members.begin(); members_iter != members.end(); ++members_iter) { + t_field* member = (*members_iter); + if (member->get_name() == SERVICE_RESULT_VARIABLE) { // don't have to check safe name here + rust_return_type = to_rust_type(member->get_type()); + break; + } + } + + // NOTE: ideally I would generate the branches and render them separately + // I tried this however, and the resulting code was harder to understand + // maintaining a rendered branch count (while a little ugly) got me the + // rendering I wanted with code that was reasonably understandable + + f_gen_ << indent() << "fn ok_or(self) -> thrift::Result<" << rust_return_type << "> {" << endl; + indent_up(); + + int rendered_branch_count = 0; + + // render the exception branches + for(members_iter = members.begin(); members_iter != members.end(); ++members_iter) { + t_field* tfield = (*members_iter); + if (tfield->get_name() != SERVICE_RESULT_VARIABLE) { // don't have to check safe name here + string field_name("self." + rust_field_name(tfield)); + string branch_statement = rendered_branch_count == 0 ? "if" : "} else if"; + + f_gen_ << indent() << branch_statement << " " << field_name << ".is_some() {" << endl; + indent_up(); + f_gen_ << indent() << "Err(thrift::Error::User(Box::new(" << field_name << ".unwrap())))" << endl; + indent_down(); + + rendered_branch_count++; + } + } + + // render the return value branches + if (rust_return_type == "()") { + if (rendered_branch_count == 0) { + // we have the unit return and this service call has no user-defined + // exceptions. this means that we've a trivial return (happens with oneways) + f_gen_ << indent() << "Ok(())" << endl; + } else { + // we have the unit return, but there are user-defined exceptions + // if we've gotten this far then we have the default return (i.e. call successful) + f_gen_ << indent() << "} else {" << endl; + indent_up(); + f_gen_ << indent() << "Ok(())" << endl; + indent_down(); + f_gen_ << indent() << "}" << endl; + } + } else { + string branch_statement = rendered_branch_count == 0 ? "if" : "} else if"; + f_gen_ << indent() << branch_statement << " self." << SERVICE_RESULT_VARIABLE << ".is_some() {" << endl; + indent_up(); + f_gen_ << indent() << "Ok(self." << SERVICE_RESULT_VARIABLE << ".unwrap())" << endl; + indent_down(); + f_gen_ << indent() << "} else {" << endl; + indent_up(); + // if we haven't found a valid return value *or* a user exception + // then we're in trouble; return a default error + render_thrift_error( + "Application", + "ApplicationError", + "ApplicationErrorKind::MissingResult", + "\"no result received for " + service_call_name + "\"" + ); + indent_down(); + f_gen_ << indent() << "}" << endl; + } + + indent_down(); + f_gen_ << indent() << "}" << endl; +} + +void t_rs_generator::render_union(t_struct* tstruct) { + string union_name(rust_struct_name(tstruct)); + render_type_comment(union_name); + render_union_definition(union_name, tstruct); + render_union_impl(union_name, tstruct); +} + +void t_rs_generator::render_union_definition(const string& union_name, t_struct* tstruct) { + const vector<t_field*>& members = tstruct->get_sorted_members(); + if (members.empty()) { + throw "cannot generate rust enum with 0 members"; // may be valid thrift, but it's invalid rust + } + + f_gen_ << "#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]" << endl; + f_gen_ << "pub enum " << union_name << " {" << endl; + indent_up(); + + vector<t_field*>::const_iterator member_iter; + for(member_iter = members.begin(); member_iter != members.end(); ++member_iter) { + t_field* tfield = (*member_iter); + f_gen_ + << indent() + << rust_union_field_name(tfield) + << "(" << to_rust_type(tfield->get_type()) << ")," + << endl; + } + + indent_down(); + f_gen_ << "}" << endl; + f_gen_ << endl; +} + +void t_rs_generator::render_union_impl(const string& union_name, t_struct* tstruct) { + f_gen_ << "impl " << union_name << " {" << endl; + indent_up(); + + render_union_sync_read(union_name, tstruct); + render_union_sync_write(union_name, tstruct); + + indent_down(); + f_gen_ << "}" << endl; + f_gen_ << endl; +} + +//----------------------------------------------------------------------------- +// +// Sync Struct Write +// +//----------------------------------------------------------------------------- + +void t_rs_generator::render_struct_sync_write( + t_struct *tstruct, + t_rs_generator::e_struct_type struct_type +) { + f_gen_ + << indent() + << visibility_qualifier(struct_type) + << "fn write_to_out_protocol(&self, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> {" + << endl; + indent_up(); + + // write struct header to output protocol + // note: use the *original* struct name here + f_gen_ << indent() << "let struct_ident = TStructIdentifier::new(\"" + tstruct->get_name() + "\");" << endl; + f_gen_ << indent() << "o_prot.write_struct_begin(&struct_ident)?;" << endl; + + // write struct members to output protocol + vector<t_field*> members = tstruct->get_sorted_members(); + if (!members.empty()) { + vector<t_field*>::iterator members_iter; + for(members_iter = members.begin(); members_iter != members.end(); ++members_iter) { + t_field* member = (*members_iter); + t_field::e_req member_req = actual_field_req(member, struct_type); + string member_var("self." + rust_field_name(member)); + render_struct_field_sync_write(member_var, false, member, member_req); + } + } + + // write struct footer to output protocol + f_gen_ << indent() << "o_prot.write_field_stop()?;" << endl; + f_gen_ << indent() << "o_prot.write_struct_end()" << endl; + + indent_down(); + f_gen_ << indent() << "}" << endl; +} + +void t_rs_generator::render_union_sync_write(const string &union_name, t_struct *tstruct) { + f_gen_ + << indent() + << "pub fn write_to_out_protocol(&self, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> {" + << endl; + indent_up(); + + // write struct header to output protocol + // note: use the *original* struct name here + f_gen_ << indent() << "let struct_ident = TStructIdentifier::new(\"" + tstruct->get_name() + "\");" << endl; + f_gen_ << indent() << "o_prot.write_struct_begin(&struct_ident)?;" << endl; + + // write the enum field to the output protocol + vector<t_field*> members = tstruct->get_sorted_members(); + if (!members.empty()) { + f_gen_ << indent() << "match *self {" << endl; + indent_up(); + vector<t_field*>::iterator members_iter; + for(members_iter = members.begin(); members_iter != members.end(); ++members_iter) { + t_field* member = (*members_iter); + t_field::e_req member_req = t_field::T_REQUIRED; + t_type* ttype = member->get_type(); + string match_var((ttype->is_base_type() && !ttype->is_string()) ? "f" : "ref f"); + f_gen_ + << indent() + << union_name << "::" << rust_union_field_name(member) + << "(" << match_var << ") => {" + << endl; + indent_up(); + render_struct_field_sync_write("f", true, member, member_req); + indent_down(); + f_gen_ << indent() << "}," << endl; + } + indent_down(); + f_gen_ << indent() << "}" << endl; + } + + // write struct footer to output protocol + f_gen_ << indent() << "o_prot.write_field_stop()?;" << endl; + f_gen_ << indent() << "o_prot.write_struct_end()" << endl; + + indent_down(); + f_gen_ << indent() << "}" << endl; +} + +void t_rs_generator::render_struct_field_sync_write( + const string &field_var, + bool field_var_is_ref, + t_field *tfield, + t_field::e_req req +) { + t_type* field_type = tfield->get_type(); + t_type* actual_type = get_true_type(field_type); + + ostringstream field_stream; + field_stream + << "TFieldIdentifier::new(" + << "\"" << tfield->get_name() << "\"" << ", " // note: use *original* name + << to_rust_field_type_enum(field_type) << ", " + << tfield->get_key() << ")"; + string field_ident_string = field_stream.str(); + + if (is_optional(req)) { + string let_var((actual_type->is_base_type() && !actual_type->is_string()) ? "fld_var" : "ref fld_var"); + f_gen_ << indent() << "if let Some(" << let_var << ") = " << field_var << " {" << endl; + indent_up(); + f_gen_ << indent() << "o_prot.write_field_begin(&" << field_ident_string << ")?;" << endl; + render_type_sync_write("fld_var", true, field_type); + f_gen_ << indent() << "o_prot.write_field_end()?;" << endl; + f_gen_ << indent() << "()" << endl; // FIXME: remove this extraneous '()' + indent_down(); + f_gen_ << indent() << "} else {" << endl; // FIXME: remove else branch + indent_up(); + /* FIXME: rethink how I deal with OPT_IN_REQ_OUT + if (req == t_field::T_OPT_IN_REQ_OUT) { + f_gen_ << indent() << "let field_ident = " << field_ident_string << ";" << endl; + f_gen_ << indent() << "o_prot.write_field_begin(&field_ident)?;" << endl; + f_gen_ << indent() << "o_prot.write_field_end()?;" << endl; + }*/ + f_gen_ << indent() << "()" << endl; + indent_down(); + f_gen_ << indent() << "}" << endl; + } else { + f_gen_ << indent() << "o_prot.write_field_begin(&" << field_ident_string << ")?;" << endl; + render_type_sync_write(field_var, field_var_is_ref, tfield->get_type()); + f_gen_ << indent() << "o_prot.write_field_end()?;" << endl; + } +} + +void t_rs_generator::render_type_sync_write(const string &type_var, bool type_var_is_ref, t_type *ttype) { + if (ttype->is_base_type()) { + t_base_type* tbase_type = (t_base_type*)ttype; + switch (tbase_type->get_base()) { + case t_base_type::TYPE_VOID: + throw "cannot write field of type TYPE_VOID to output protocol"; + case t_base_type::TYPE_STRING: { + string ref(type_var_is_ref ? "" : "&"); + if (tbase_type->is_binary()) { + f_gen_ << indent() << "o_prot.write_bytes(" + ref + type_var + ")?;" << endl; + } else { + f_gen_ << indent() << "o_prot.write_string(" + ref + type_var + ")?;" << endl; + } + return; + } + case t_base_type::TYPE_BOOL: + f_gen_ << indent() << "o_prot.write_bool(" + type_var + ")?;" << endl; + return; + case t_base_type::TYPE_I8: + f_gen_ << indent() << "o_prot.write_i8(" + type_var + ")?;" << endl; + return; + case t_base_type::TYPE_I16: + f_gen_ << indent() << "o_prot.write_i16(" + type_var + ")?;" << endl; + return; + case t_base_type::TYPE_I32: + f_gen_ << indent() << "o_prot.write_i32(" + type_var + ")?;" << endl; + return; + case t_base_type::TYPE_I64: + f_gen_ << indent() << "o_prot.write_i64(" + type_var + ")?;" << endl; + return; + case t_base_type::TYPE_DOUBLE: + f_gen_ << indent() << "o_prot.write_double(" + type_var + ".into())?;" << endl; + return; + } + } else if (ttype->is_typedef()) { + t_typedef* ttypedef = (t_typedef*) ttype; + render_type_sync_write(type_var, type_var_is_ref, ttypedef->get_type()); + return; + } else if (ttype->is_enum() || ttype->is_struct() || ttype->is_xception()) { + f_gen_ << indent() << type_var + ".write_to_out_protocol(o_prot)?;" << endl; + return; + } else if (ttype->is_map()) { + render_map_sync_write(type_var, type_var_is_ref, (t_map *) ttype); + return; + } else if (ttype->is_set()) { + render_set_sync_write(type_var, type_var_is_ref, (t_set *) ttype); + return; + } else if (ttype->is_list()) { + render_list_sync_write(type_var, type_var_is_ref, (t_list *) ttype); + return; + } + + throw "cannot write unsupported type " + ttype->get_name(); +} + +void t_rs_generator::render_list_sync_write(const string &list_var, bool list_var_is_ref, t_list *tlist) { + t_type* elem_type = tlist->get_elem_type(); + + f_gen_ + << indent() + << "o_prot.write_list_begin(" + << "&TListIdentifier::new(" + << to_rust_field_type_enum(elem_type) << ", " + << list_var << ".len() as i32" << ")" + << ")?;" + << endl; + + string ref(list_var_is_ref ? "" : "&"); + f_gen_ << indent() << "for e in " << ref << list_var << " {" << endl; + indent_up(); + render_type_sync_write(string_container_write_variable(elem_type, "e"), true, elem_type); + f_gen_ << indent() << "o_prot.write_list_end()?;" << endl; + indent_down(); + f_gen_ << indent() << "}" << endl; +} + +void t_rs_generator::render_set_sync_write(const string &set_var, bool set_var_is_ref, t_set *tset) { + t_type* elem_type = tset->get_elem_type(); + + f_gen_ + << indent() + << "o_prot.write_set_begin(" + << "&TSetIdentifier::new(" + << to_rust_field_type_enum(elem_type) << ", " + << set_var << ".len() as i32" << ")" + << ")?;" + << endl; + + string ref(set_var_is_ref ? "" : "&"); + f_gen_ << indent() << "for e in " << ref << set_var << " {" << endl; + indent_up(); + render_type_sync_write(string_container_write_variable(elem_type, "e"), true, elem_type); + f_gen_ << indent() << "o_prot.write_set_end()?;" << endl; + indent_down(); + f_gen_ << indent() << "}" << endl; +} + +void t_rs_generator::render_map_sync_write(const string &map_var, bool map_var_is_ref, t_map *tmap) { + t_type* key_type = tmap->get_key_type(); + t_type* val_type = tmap->get_val_type(); + + f_gen_ + << indent() + << "o_prot.write_map_begin(" + << "&TMapIdentifier::new(" + << to_rust_field_type_enum(key_type) << ", " + << to_rust_field_type_enum(val_type) << ", " + << map_var << ".len() as i32)" + << ")?;" + << endl; + + string ref(map_var_is_ref ? "" : "&"); + f_gen_ << indent() << "for (k, v) in " << ref << map_var << " {" << endl; + indent_up(); + render_type_sync_write(string_container_write_variable(key_type, "k"), true, key_type); + render_type_sync_write(string_container_write_variable(val_type, "v"), true, val_type); + f_gen_ << indent() << "o_prot.write_map_end()?;" << endl; + indent_down(); + f_gen_ << indent() << "}" << endl; +} + +string t_rs_generator::string_container_write_variable(t_type* ttype, const string& base_var) { + bool type_needs_deref = needs_deref_on_container_write(ttype); + bool type_is_double = is_double(ttype); + + string write_variable; + + if (type_is_double && type_needs_deref) { + write_variable = "(*" + base_var + ")"; + } else if (type_needs_deref) { + write_variable = "*" + base_var; + } else { + write_variable = base_var; + } + + return write_variable; +} + +bool t_rs_generator::needs_deref_on_container_write(t_type* ttype) { + ttype = get_true_type(ttype); + return ttype->is_base_type() && !ttype->is_string(); +} + +//----------------------------------------------------------------------------- +// +// Sync Struct Read +// +//----------------------------------------------------------------------------- + +void t_rs_generator::render_struct_sync_read( + const string &struct_name, + t_struct *tstruct, t_rs_generator::e_struct_type struct_type +) { + f_gen_ + << indent() + << visibility_qualifier(struct_type) + << "fn read_from_in_protocol(i_prot: &mut dyn TInputProtocol) -> thrift::Result<" << struct_name << "> {" + << endl; + + indent_up(); + + f_gen_ << indent() << "i_prot.read_struct_begin()?;" << endl; + + // create temporary variables: one for each field in the struct + const vector<t_field*> members = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator members_iter; + for (members_iter = members.begin(); members_iter != members.end(); ++members_iter) { + t_field* member = (*members_iter); + t_field::e_req member_req = actual_field_req(member, struct_type); + + f_gen_ + << indent() + << "let mut " << struct_field_read_temp_variable(member) + << ": Option<" << to_rust_type(member->get_type()) << "> = "; + if (member_req == t_field::T_OPT_IN_REQ_OUT) { + f_gen_ << opt_in_req_out_value(member->get_type()) << ";"; + } else { + f_gen_ << "None;"; + } + f_gen_ << endl; + } + + // now loop through the fields we've received + f_gen_ << indent() << "loop {" << endl; // start loop + indent_up(); + + // break out if you've found the Stop field + f_gen_ << indent() << "let field_ident = i_prot.read_field_begin()?;" << endl; + f_gen_ << indent() << "if field_ident.field_type == TType::Stop {" << endl; + indent_up(); + f_gen_ << indent() << "break;" << endl; + indent_down(); + f_gen_ << indent() << "}" << endl; + + // now read all the fields found + f_gen_ << indent() << "let field_id = field_id(&field_ident)?;" << endl; + f_gen_ << indent() << "match field_id {" << endl; // start match + indent_up(); + + for (members_iter = members.begin(); members_iter != members.end(); ++members_iter) { + t_field* tfield = (*members_iter); + f_gen_ << indent() << rust_safe_field_id(tfield->get_key()) << " => {" << endl; + indent_up(); + render_type_sync_read("val", tfield->get_type()); + f_gen_ << indent() << struct_field_read_temp_variable(tfield) << " = Some(val);" << endl; + indent_down(); + f_gen_ << indent() << "}," << endl; + } + + // default case (skip fields) + f_gen_ << indent() << "_ => {" << endl; + indent_up(); + f_gen_ << indent() << "i_prot.skip(field_ident.field_type)?;" << endl; + indent_down(); + f_gen_ << indent() << "}," << endl; + + indent_down(); + f_gen_ << indent() << "};" << endl; // finish match + f_gen_ << indent() << "i_prot.read_field_end()?;" << endl; + indent_down(); + f_gen_ << indent() << "}" << endl; // finish loop + f_gen_ << indent() << "i_prot.read_struct_end()?;" << endl; // read message footer from the wire + + // verify that all required fields exist + for (members_iter = members.begin(); members_iter != members.end(); ++members_iter) { + t_field* tfield = (*members_iter); + t_field::e_req req = actual_field_req(tfield, struct_type); + if (!is_optional(req)) { + f_gen_ + << indent() + << "verify_required_field_exists(" + << "\"" << struct_name << "." << rust_field_name(tfield) << "\"" + << ", " + << "&" << struct_field_read_temp_variable(tfield) + << ")?;" << endl; + } + } + + // construct the struct + if (members.size() == 0) { + f_gen_ << indent() << "let ret = " << struct_name << " {};" << endl; + } else { + f_gen_ << indent() << "let ret = " << struct_name << " {" << endl; + indent_up(); + + for (members_iter = members.begin(); members_iter != members.end(); ++members_iter) { + t_field* tfield = (*members_iter); + t_field::e_req req = actual_field_req(tfield, struct_type); + string field_name(rust_field_name(tfield)); + string field_key = struct_field_read_temp_variable(tfield); + if (is_optional(req)) { + f_gen_ << indent() << field_name << ": " << field_key << "," << endl; + } else { + f_gen_ + << indent() + << field_name + << ": " + << field_key + << ".expect(\"auto-generated code should have checked for presence of required fields\")" + << "," + << endl; + } + } + + indent_down(); + f_gen_ << indent() << "};" << endl; + } + + // return the constructed value + f_gen_ << indent() << "Ok(ret)" << endl; + + indent_down(); + f_gen_ << indent() << "}" << endl; +} + +void t_rs_generator::render_union_sync_read(const string &union_name, t_struct *tstruct) { + f_gen_ + << indent() + << "pub fn read_from_in_protocol(i_prot: &mut dyn TInputProtocol) -> thrift::Result<" << union_name << "> {" + << endl; + indent_up(); + + // create temporary variables to hold the + // completed union as well as a count of fields read + f_gen_ << indent() << "let mut ret: Option<" << union_name << "> = None;" << endl; + f_gen_ << indent() << "let mut received_field_count = 0;" << endl; + + // read the struct preamble + f_gen_ << indent() << "i_prot.read_struct_begin()?;" << endl; + + // now loop through the fields we've received + f_gen_ << indent() << "loop {" << endl; // start loop + indent_up(); + + // break out if you've found the Stop field + f_gen_ << indent() << "let field_ident = i_prot.read_field_begin()?;" << endl; + f_gen_ << indent() << "if field_ident.field_type == TType::Stop {" << endl; + indent_up(); + f_gen_ << indent() << "break;" << endl; + indent_down(); + f_gen_ << indent() << "}" << endl; + + // now read all the fields found + f_gen_ << indent() << "let field_id = field_id(&field_ident)?;" << endl; + f_gen_ << indent() << "match field_id {" << endl; // start match + indent_up(); + + const vector<t_field*> members = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator members_iter; + for (members_iter = members.begin(); members_iter != members.end(); ++members_iter) { + t_field* member = (*members_iter); + f_gen_ << indent() << rust_safe_field_id(member->get_key()) << " => {" << endl; + indent_up(); + render_type_sync_read("val", member->get_type()); + f_gen_ << indent() << "if ret.is_none() {" << endl; + indent_up(); + f_gen_ + << indent() + << "ret = Some(" << union_name << "::" << rust_union_field_name(member) << "(val));" + << endl; + indent_down(); + f_gen_ << indent() << "}" << endl; + f_gen_ << indent() << "received_field_count += 1;" << endl; + indent_down(); + f_gen_ << indent() << "}," << endl; + } + + // default case (skip fields) + f_gen_ << indent() << "_ => {" << endl; + indent_up(); + f_gen_ << indent() << "i_prot.skip(field_ident.field_type)?;" << endl; + f_gen_ << indent() << "received_field_count += 1;" << endl; + indent_down(); + f_gen_ << indent() << "}," << endl; + + indent_down(); + f_gen_ << indent() << "};" << endl; // finish match + f_gen_ << indent() << "i_prot.read_field_end()?;" << endl; + indent_down(); + f_gen_ << indent() << "}" << endl; // finish loop + f_gen_ << indent() << "i_prot.read_struct_end()?;" << endl; // finish reading message from wire + + // return the value or an error + f_gen_ << indent() << "if received_field_count == 0 {" << endl; + indent_up(); + render_thrift_error( + "Protocol", + "ProtocolError", + "ProtocolErrorKind::InvalidData", + "\"received empty union from remote " + union_name + "\"" + ); + indent_down(); + f_gen_ << indent() << "} else if received_field_count > 1 {" << endl; + indent_up(); + render_thrift_error( + "Protocol", + "ProtocolError", + "ProtocolErrorKind::InvalidData", + "\"received multiple fields for union from remote " + union_name + "\"" + ); + indent_down(); + f_gen_ << indent() << "} else {" << endl; + indent_up(); + f_gen_ << indent() << "Ok(ret.expect(\"return value should have been constructed\"))" << endl; + indent_down(); + f_gen_ << indent() << "}" << endl; + + indent_down(); + f_gen_ << indent() << "}" << endl; +} + +// Construct the rust representation of all supported types from the wire. +void t_rs_generator::render_type_sync_read(const string &type_var, t_type *ttype, bool is_boxed) { + if (ttype->is_base_type()) { + t_base_type* tbase_type = (t_base_type*)ttype; + switch (tbase_type->get_base()) { + case t_base_type::TYPE_VOID: + throw "cannot read field of type TYPE_VOID from input protocol"; + case t_base_type::TYPE_STRING: + if (tbase_type->is_binary()) { + f_gen_ << indent() << "let " << type_var << " = i_prot.read_bytes()?;" << endl; + } else { + f_gen_ << indent() << "let " << type_var << " = i_prot.read_string()?;" << endl; + } + return; + case t_base_type::TYPE_BOOL: + f_gen_ << indent() << "let " << type_var << " = i_prot.read_bool()?;" << endl; + return; + case t_base_type::TYPE_I8: + f_gen_ << indent() << "let " << type_var << " = i_prot.read_i8()?;" << endl; + return; + case t_base_type::TYPE_I16: + f_gen_ << indent() << "let " << type_var << " = i_prot.read_i16()?;" << endl; + return; + case t_base_type::TYPE_I32: + f_gen_ << indent() << "let " << type_var << " = i_prot.read_i32()?;" << endl; + return; + case t_base_type::TYPE_I64: + f_gen_ << indent() << "let " << type_var << " = i_prot.read_i64()?;" << endl; + return; + case t_base_type::TYPE_DOUBLE: + f_gen_ << indent() << "let " << type_var << " = OrderedFloat::from(i_prot.read_double()?);" << endl; + return; + } + } else if (ttype->is_typedef()) { + // FIXME: not a fan of separate `is_boxed` parameter + // This is problematic because it's an optional parameter, and only comes + // into play once. The core issue is that I lose an important piece of type + // information (whether the type is a fwd ref) by unwrapping the typedef'd + // type and making the recursive call using it. I can't modify or wrap the + // generated string after the fact because it's written directly into the file, + // so I have to pass this parameter along. Going with this approach because it + // seems like the lowest-cost option to easily support recursive types. + t_typedef* ttypedef = (t_typedef*)ttype; + render_type_sync_read(type_var, ttypedef->get_type(), ttypedef->is_forward_typedef()); + return; + } else if (ttype->is_enum() || ttype->is_struct() || ttype->is_xception()) { + string read_call(to_rust_type(ttype) + "::read_from_in_protocol(i_prot)?"); + read_call = is_boxed ? "Box::new(" + read_call + ")" : read_call; + f_gen_ + << indent() + << "let " << type_var << " = " << read_call << ";" + << endl; + return; + } else if (ttype->is_map()) { + render_map_sync_read((t_map *) ttype, type_var); + return; + } else if (ttype->is_set()) { + render_set_sync_read((t_set *) ttype, type_var); + return; + } else if (ttype->is_list()) { + render_list_sync_read((t_list *) ttype, type_var); + return; + } + + throw "cannot read unsupported type " + ttype->get_name(); +} + +// Construct the rust representation of a list from the wire. +void t_rs_generator::render_list_sync_read(t_list *tlist, const string &list_var) { + t_type* elem_type = tlist->get_elem_type(); + + f_gen_ << indent() << "let list_ident = i_prot.read_list_begin()?;" << endl; + f_gen_ + << indent() + << "let mut " << list_var << ": " << to_rust_type((t_type*) tlist) + << " = Vec::with_capacity(list_ident.size as usize);" + << endl; + f_gen_ << indent() << "for _ in 0..list_ident.size {" << endl; + + indent_up(); + + string list_elem_var = tmp("list_elem_"); + render_type_sync_read(list_elem_var, elem_type); + f_gen_ << indent() << list_var << ".push(" << list_elem_var << ");" << endl; + + indent_down(); + + f_gen_ << indent() << "}" << endl; + f_gen_ << indent() << "i_prot.read_list_end()?;" << endl; +} + +// Construct the rust representation of a set from the wire. +void t_rs_generator::render_set_sync_read(t_set *tset, const string &set_var) { + t_type* elem_type = tset->get_elem_type(); + + f_gen_ << indent() << "let set_ident = i_prot.read_set_begin()?;" << endl; + f_gen_ + << indent() + << "let mut " << set_var << ": " << to_rust_type((t_type*) tset) + << " = BTreeSet::new();" + << endl; + f_gen_ << indent() << "for _ in 0..set_ident.size {" << endl; + + indent_up(); + + string set_elem_var = tmp("set_elem_"); + render_type_sync_read(set_elem_var, elem_type); + f_gen_ << indent() << set_var << ".insert(" << set_elem_var << ");" << endl; + + indent_down(); + + f_gen_ << indent() << "}" << endl; + f_gen_ << indent() << "i_prot.read_set_end()?;" << endl; +} + +// Construct the rust representation of a map from the wire. +void t_rs_generator::render_map_sync_read(t_map *tmap, const string &map_var) { + t_type* key_type = tmap->get_key_type(); + t_type* val_type = tmap->get_val_type(); + + f_gen_ << indent() << "let map_ident = i_prot.read_map_begin()?;" << endl; + f_gen_ + << indent() + << "let mut " << map_var << ": " << to_rust_type((t_type*) tmap) + << " = BTreeMap::new();" + << endl; + f_gen_ << indent() << "for _ in 0..map_ident.size {" << endl; + + indent_up(); + + string key_elem_var = tmp("map_key_"); + render_type_sync_read(key_elem_var, key_type); + string val_elem_var = tmp("map_val_"); + render_type_sync_read(val_elem_var, val_type); + f_gen_ << indent() << map_var << ".insert(" << key_elem_var << ", " << val_elem_var << ");" << endl; + + indent_down(); + + f_gen_ << indent() << "}" << endl; + f_gen_ << indent() << "i_prot.read_map_end()?;" << endl; +} + +string t_rs_generator::struct_field_read_temp_variable(t_field* tfield) { + std::ostringstream foss; + foss << "f_" << rust_safe_field_id(tfield->get_key()); + return foss.str(); +} + +//----------------------------------------------------------------------------- +// +// Sync Client +// +//----------------------------------------------------------------------------- + +void t_rs_generator::generate_service(t_service* tservice) { + render_sync_client(tservice); + render_sync_processor(tservice); + render_service_call_structs(tservice); +} + +void t_rs_generator::render_service_call_structs(t_service* tservice) { + const std::vector<t_function*> functions = tservice->get_functions(); + std::vector<t_function*>::const_iterator func_iter; + + // thrift args for service calls are packed + // into a struct that's transmitted over the wire, so + // generate structs for those too + // + // thrift returns are *also* packed into a struct + // that's passed over the wire, so, generate the struct + // for that too. Note that this result struct *also* + // contains the exceptions as well + for(func_iter = functions.begin(); func_iter != functions.end(); ++func_iter) { + t_function* tfunc = (*func_iter); + render_service_call_args_struct(tfunc); + if (!tfunc->is_oneway()) { + render_service_call_result_value_struct(tfunc); + } + } +} + +void t_rs_generator::render_sync_client(t_service* tservice) { + string client_impl_name(rust_sync_client_impl_name(tservice)); + + render_type_comment(tservice->get_name() + " service client"); // note: use *original* name + render_sync_client_trait(tservice); + render_sync_client_marker_trait(tservice); + render_sync_client_definition_and_impl(client_impl_name); + render_sync_client_tthriftclient_impl(client_impl_name); + render_sync_client_marker_trait_impls(tservice, client_impl_name); f_gen_ << endl; + render_sync_client_process_impl(tservice); +} + +void t_rs_generator::render_sync_client_trait(t_service *tservice) { + string extension = ""; + if (tservice->get_extends()) { + t_service* extends = tservice->get_extends(); + extension = " : " + rust_namespace(extends) + rust_sync_client_trait_name(extends); + } + + render_rustdoc((t_doc*) tservice); + f_gen_ << "pub trait " << rust_sync_client_trait_name(tservice) << extension << " {" << endl; + indent_up(); + + const std::vector<t_function*> functions = tservice->get_functions(); + std::vector<t_function*>::const_iterator func_iter; + for(func_iter = functions.begin(); func_iter != functions.end(); ++func_iter) { + t_function* tfunc = (*func_iter); + string func_name = service_call_client_function_name(tfunc); + string func_args = rust_sync_service_call_declaration(tfunc, true); + string func_return = to_rust_type(tfunc->get_returntype()); + render_rustdoc((t_doc*) tfunc); + f_gen_ << indent() << "fn " << func_name << func_args << " -> thrift::Result<" << func_return << ">;" << endl; + } + + indent_down(); + f_gen_ << indent() << "}" << endl; + f_gen_ << endl; +} + +void t_rs_generator::render_sync_client_marker_trait(t_service *tservice) { + f_gen_ << indent() << "pub trait " << rust_sync_client_marker_trait_name(tservice) << " {}" << endl; + f_gen_ << endl; +} + +void t_rs_generator::render_sync_client_marker_trait_impls(t_service *tservice, const string &impl_struct_name) { + f_gen_ + << indent() + << "impl " + << SYNC_CLIENT_GENERIC_BOUND_VARS + << " " + << rust_namespace(tservice) << rust_sync_client_marker_trait_name(tservice) + << " for " + << impl_struct_name << SYNC_CLIENT_GENERIC_BOUND_VARS + << " " + << SYNC_CLIENT_GENERIC_BOUNDS + << " {}" + << endl; + + t_service* extends = tservice->get_extends(); + if (extends) { + render_sync_client_marker_trait_impls(extends, impl_struct_name); + } +} + +void t_rs_generator::render_sync_client_definition_and_impl(const string& client_impl_name) { + + // render the definition for the client struct + f_gen_ + << "pub struct " + << client_impl_name + << SYNC_CLIENT_GENERIC_BOUND_VARS + << " " + << SYNC_CLIENT_GENERIC_BOUNDS + << " {" + << endl; + indent_up(); + f_gen_ << indent() << "_i_prot: IP," << endl; + f_gen_ << indent() << "_o_prot: OP," << endl; + f_gen_ << indent() << "_sequence_number: i32," << endl; + indent_down(); + f_gen_ << "}" << endl; + f_gen_ << endl; + + // render the struct implementation + // this includes the new() function as well as the helper send/recv methods for each service call + f_gen_ + << "impl " + << SYNC_CLIENT_GENERIC_BOUND_VARS + << " " + << client_impl_name + << SYNC_CLIENT_GENERIC_BOUND_VARS + << " " + << SYNC_CLIENT_GENERIC_BOUNDS + << " {" + << endl; + indent_up(); + render_sync_client_lifecycle_functions(client_impl_name); + indent_down(); + f_gen_ << "}" << endl; + f_gen_ << endl; +} + +void t_rs_generator::render_sync_client_lifecycle_functions(const string& client_struct) { + f_gen_ + << indent() + << "pub fn new(input_protocol: IP, output_protocol: OP) -> " + << client_struct + << SYNC_CLIENT_GENERIC_BOUND_VARS + << " {" + << endl; + indent_up(); + + f_gen_ + << indent() + << client_struct + << " { _i_prot: input_protocol, _o_prot: output_protocol, _sequence_number: 0 }" + << endl; + + indent_down(); + f_gen_ << indent() << "}" << endl; +} + +void t_rs_generator::render_sync_client_tthriftclient_impl(const string &client_impl_name) { + f_gen_ + << indent() + << "impl " + << SYNC_CLIENT_GENERIC_BOUND_VARS + << " TThriftClient for " + << client_impl_name + << SYNC_CLIENT_GENERIC_BOUND_VARS + << " " + << SYNC_CLIENT_GENERIC_BOUNDS + << " {" << endl; + indent_up(); + + f_gen_ << indent() << "fn i_prot_mut(&mut self) -> &mut dyn TInputProtocol { &mut self._i_prot }" << endl; + f_gen_ << indent() << "fn o_prot_mut(&mut self) -> &mut dyn TOutputProtocol { &mut self._o_prot }" << endl; + f_gen_ << indent() << "fn sequence_number(&self) -> i32 { self._sequence_number }" << endl; + f_gen_ + << indent() + << "fn increment_sequence_number(&mut self) -> i32 { self._sequence_number += 1; self._sequence_number }" + << endl; + + indent_down(); + f_gen_ << indent() << "}" << endl; + f_gen_ << endl; +} + +void t_rs_generator::render_sync_client_process_impl(t_service* tservice) { + string marker_extension = "" + sync_client_marker_traits_for_extension(tservice); + + f_gen_ + << "impl <C: TThriftClient + " << rust_sync_client_marker_trait_name(tservice) << marker_extension << "> " + << rust_sync_client_trait_name(tservice) + << " for C {" << endl; + indent_up(); + + const std::vector<t_function*> functions = tservice->get_functions(); + std::vector<t_function*>::const_iterator func_iter; + for(func_iter = functions.begin(); func_iter != functions.end(); ++func_iter) { + t_function* func = (*func_iter); + render_sync_send_recv_wrapper(func); + } + + indent_down(); + f_gen_ << "}" << endl; + f_gen_ << endl; +} + +string t_rs_generator::sync_client_marker_traits_for_extension(t_service *tservice) { + string marker_extension; + + t_service* extends = tservice->get_extends(); + if (extends) { + marker_extension = " + " + rust_namespace(extends) + rust_sync_client_marker_trait_name(extends); + marker_extension = marker_extension + sync_client_marker_traits_for_extension(extends); + } + + return marker_extension; +} + +void t_rs_generator::render_sync_send_recv_wrapper(t_function* tfunc) { + string func_name = service_call_client_function_name(tfunc); + string func_decl_args = rust_sync_service_call_declaration(tfunc, true); + string func_call_args = rust_sync_service_call_invocation(tfunc); + string func_return = to_rust_type(tfunc->get_returntype()); + + f_gen_ + << indent() + << "fn " << func_name << func_decl_args << " -> thrift::Result<" << func_return + << "> {" + << endl; + indent_up(); + + f_gen_ << indent() << "(" << endl; + indent_up(); + render_sync_send(tfunc); + indent_down(); + f_gen_ << indent() << ")?;" << endl; + if (tfunc->is_oneway()) { + f_gen_ << indent() << "Ok(())" << endl; + } else { + render_sync_recv(tfunc); + } + + indent_down(); + f_gen_ << indent() << "}" << endl; +} + +void t_rs_generator::render_sync_send(t_function* tfunc) { + f_gen_ << indent() << "{" << endl; + indent_up(); + + // increment the sequence number and generate the call header + string message_type = tfunc->is_oneway() ? "TMessageType::OneWay" : "TMessageType::Call"; + f_gen_ << indent() << "self.increment_sequence_number();" << endl; + f_gen_ + << indent() + << "let message_ident = " + << "TMessageIdentifier::new(\"" << tfunc->get_name() << "\", " // note: use *original* name + << message_type << ", " + << "self.sequence_number());" + << endl; + // pack the arguments into the containing struct that we'll write out over the wire + // note that this struct is generated even if we have 0 args + ostringstream struct_definition; + vector<t_field*> members = tfunc->get_arglist()->get_sorted_members(); + vector<t_field*>::iterator members_iter; + for (members_iter = members.begin(); members_iter != members.end(); ++members_iter) { + t_field* member = (*members_iter); + string member_name(rust_field_name(member)); + struct_definition << member_name << ": " << member_name << ", "; + } + string struct_fields = struct_definition.str(); + if (struct_fields.size() > 0) { + struct_fields = struct_fields.substr(0, struct_fields.size() - 2); // strip trailing comma + } + f_gen_ + << indent() + << "let call_args = " + << service_call_args_struct_name(tfunc) + << " { " + << struct_fields + << " };" + << endl; + // write everything over the wire + f_gen_ << indent() << "self.o_prot_mut().write_message_begin(&message_ident)?;" << endl; + f_gen_ << indent() << "call_args.write_to_out_protocol(self.o_prot_mut())?;" << endl; // written even if we have 0 args + f_gen_ << indent() << "self.o_prot_mut().write_message_end()?;" << endl; + f_gen_ << indent() << "self.o_prot_mut().flush()" << endl; + + indent_down(); + f_gen_ << indent() << "}" << endl; +} + +void t_rs_generator::render_sync_recv(t_function* tfunc) { + f_gen_ << indent() << "{" << endl; + indent_up(); + + f_gen_ << indent() << "let message_ident = self.i_prot_mut().read_message_begin()?;" << endl; + f_gen_ << indent() << "verify_expected_sequence_number(self.sequence_number(), message_ident.sequence_number)?;" << endl; + f_gen_ << indent() << "verify_expected_service_call(\"" << tfunc->get_name() <<"\", &message_ident.name)?;" << endl; // note: use *original* name + // FIXME: replace with a "try" block + f_gen_ << indent() << "if message_ident.message_type == TMessageType::Exception {" << endl; + indent_up(); + f_gen_ << indent() << "let remote_error = thrift::Error::read_application_error_from_in_protocol(self.i_prot_mut())?;" << endl; + f_gen_ << indent() << "self.i_prot_mut().read_message_end()?;" << endl; + f_gen_ << indent() << "return Err(thrift::Error::Application(remote_error))" << endl; + indent_down(); + f_gen_ << indent() << "}" << endl; + f_gen_ << indent() << "verify_expected_message_type(TMessageType::Reply, message_ident.message_type)?;" << endl; + f_gen_ << indent() << "let result = " << service_call_result_struct_name(tfunc) << "::read_from_in_protocol(self.i_prot_mut())?;" << endl; + f_gen_ << indent() << "self.i_prot_mut().read_message_end()?;" << endl; + f_gen_ << indent() << "result.ok_or()" << endl; + + indent_down(); + f_gen_ << indent() << "}" << endl; +} + +string t_rs_generator::rust_sync_service_call_declaration(t_function* tfunc, bool self_is_mutable) { + ostringstream func_args; + + if (self_is_mutable) { + func_args << "(&mut self"; + } else { + func_args << "(&self"; + } + + if (has_args(tfunc)) { + func_args << ", "; // put comma after "self" + func_args << struct_to_declaration(tfunc->get_arglist(), T_ARGS); + } + + func_args << ")"; + return func_args.str(); +} + +string t_rs_generator::rust_sync_service_call_invocation(t_function* tfunc, const string& field_prefix) { + ostringstream func_args; + func_args << "("; + + if (has_args(tfunc)) { + func_args << struct_to_invocation(tfunc->get_arglist(), field_prefix); + } + + func_args << ")"; + return func_args.str(); +} + +string t_rs_generator::struct_to_declaration(t_struct* tstruct, t_rs_generator::e_struct_type struct_type) { + ostringstream args; + + bool first_arg = true; + std::vector<t_field*> fields = tstruct->get_sorted_members(); + std::vector<t_field*>::iterator field_iter; + for (field_iter = fields.begin(); field_iter != fields.end(); ++field_iter) { + t_field* tfield = (*field_iter); + t_field::e_req field_req = actual_field_req(tfield, struct_type); + string rust_type = to_rust_type(tfield->get_type()); + rust_type = is_optional(field_req) ? "Option<" + rust_type + ">" : rust_type; + + if (first_arg) { + first_arg = false; + } else { + args << ", "; + } + + args << rust_field_name(tfield) << ": " << rust_type; + } + + return args.str(); +} + +string t_rs_generator::struct_to_invocation(t_struct* tstruct, const string& field_prefix) { + ostringstream args; + + bool first_arg = true; + std::vector<t_field*> fields = tstruct->get_sorted_members(); + std::vector<t_field*>::iterator field_iter; + for (field_iter = fields.begin(); field_iter != fields.end(); ++field_iter) { + t_field* tfield = (*field_iter); + + if (first_arg) { + first_arg = false; + } else { + args << ", "; + } + + args << field_prefix << rust_field_name(tfield); + } + + return args.str(); +} + +void t_rs_generator::render_service_call_args_struct(t_function* tfunc) { + string args_struct_name(service_call_args_struct_name(tfunc)); + render_struct(args_struct_name, tfunc->get_arglist(), t_rs_generator::T_ARGS); +} + +void t_rs_generator::render_service_call_result_value_struct(t_function* tfunc) { + string result_struct_name = service_call_result_struct_name(tfunc); + t_struct result(program_, result_struct_name); + + t_field return_value(tfunc->get_returntype(), SERVICE_RESULT_VARIABLE, 0); + return_value.set_req(t_field::T_OPTIONAL); + if (!tfunc->get_returntype()->is_void()) { + result.append(&return_value); + } + + t_struct* exceptions = tfunc->get_xceptions(); + const vector<t_field*>& exception_types = exceptions->get_members(); + vector<t_field*>::const_iterator exception_iter; + for(exception_iter = exception_types.begin(); exception_iter != exception_types.end(); ++exception_iter) { + t_field* exception_type = *exception_iter; + exception_type->set_req(t_field::T_OPTIONAL); + result.append(exception_type); + } + + render_struct(result_struct_name, &result, t_rs_generator::T_RESULT); +} + +//----------------------------------------------------------------------------- +// +// Sync Processor +// +//----------------------------------------------------------------------------- + +void t_rs_generator::render_sync_processor(t_service *tservice) { + render_type_comment(tservice->get_name() + " service processor"); // note: use *original* name + render_sync_handler_trait(tservice); + render_sync_processor_definition_and_impl(tservice); +} + +void t_rs_generator::render_sync_handler_trait(t_service *tservice) { + string extension = ""; + if (tservice->get_extends() != NULL) { + t_service* extends = tservice->get_extends(); + extension = " : " + rust_namespace(extends) + rust_sync_handler_trait_name(extends); + } + + const std::vector<t_function*> functions = tservice->get_functions(); + std::vector<t_function*>::const_iterator func_iter; + + render_rustdoc((t_doc*) tservice); + f_gen_ << "pub trait " << rust_sync_handler_trait_name(tservice) << extension << " {" << endl; + indent_up(); + for(func_iter = functions.begin(); func_iter != functions.end(); ++func_iter) { + t_function* tfunc = (*func_iter); + string func_name = service_call_handler_function_name(tfunc); + string func_args = rust_sync_service_call_declaration(tfunc, false); + string func_return = to_rust_type(tfunc->get_returntype()); + render_rustdoc((t_doc*) tfunc); + f_gen_ + << indent() + << "fn " + << func_name << func_args + << " -> thrift::Result<" << func_return << ">;" + << endl; + } + indent_down(); + f_gen_ << indent() << "}" << endl; + f_gen_ << endl; +} + +void t_rs_generator::render_sync_processor_definition_and_impl(t_service *tservice) { + string service_processor_name = rust_sync_processor_name(tservice); + string handler_trait_name = rust_sync_handler_trait_name(tservice); + + // struct + f_gen_ + << indent() + << "pub struct " << service_processor_name + << "<H: " << handler_trait_name + << "> {" + << endl; + indent_up(); + f_gen_ << indent() << "handler: H," << endl; + indent_down(); + f_gen_ << indent() << "}" << endl; + f_gen_ << endl; + + // delegating impl + f_gen_ + << indent() + << "impl <H: " << handler_trait_name << "> " + << service_processor_name + << "<H> {" + << endl; + indent_up(); + f_gen_ << indent() << "pub fn new(handler: H) -> " << service_processor_name << "<H> {" << endl; + indent_up(); + f_gen_ << indent() << service_processor_name << " {" << endl; + indent_up(); + f_gen_ << indent() << "handler," << endl; + indent_down(); + f_gen_ << indent() << "}" << endl; + indent_down(); + f_gen_ << indent() << "}" << endl; + render_sync_process_delegation_functions(tservice); + indent_down(); + f_gen_ << indent() << "}" << endl; + f_gen_ << endl; + + // actual impl + string service_actual_processor_name = rust_sync_processor_impl_name(tservice); + f_gen_ << indent() << "pub struct " << service_actual_processor_name << ";" << endl; + f_gen_ << endl; + f_gen_ << indent() << "impl " << service_actual_processor_name << " {" << endl; + indent_up(); + + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator func_iter; + for(func_iter = functions.begin(); func_iter != functions.end(); ++func_iter) { + t_function* tfunc = (*func_iter); + render_sync_process_function(tfunc, handler_trait_name); + } + + indent_down(); + f_gen_ << indent() << "}" << endl; + f_gen_ << endl; + + // processor impl + f_gen_ + << indent() + << "impl <H: " + << handler_trait_name << "> TProcessor for " + << service_processor_name + << "<H> {" + << endl; + indent_up(); + + f_gen_ + << indent() + << "fn process(&self, i_prot: &mut dyn TInputProtocol, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> {" + << endl; + indent_up(); + + f_gen_ << indent() << "let message_ident = i_prot.read_message_begin()?;" << endl; + + f_gen_ << indent() << "let res = match &*message_ident.name {" << endl; // [sigh] explicit deref coercion + indent_up(); + render_process_match_statements(tservice); + f_gen_ << indent() << "method => {" << endl; + indent_up(); + render_thrift_error( + "Application", + "ApplicationError", + "ApplicationErrorKind::UnknownMethod", + "format!(\"unknown method {}\", method)" + ); + indent_down(); + f_gen_ << indent() << "}," << endl; + + indent_down(); + f_gen_ << indent() << "};" << endl; + f_gen_ << indent() << "thrift::server::handle_process_result(&message_ident, res, o_prot)" << endl; + + indent_down(); + f_gen_ << indent() << "}" << endl; + + indent_down(); + f_gen_ << indent() << "}" << endl; + f_gen_ << endl; +} + +void t_rs_generator::render_sync_process_delegation_functions(t_service *tservice) { + string actual_processor(rust_namespace(tservice) + rust_sync_processor_impl_name(tservice)); + + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator func_iter; + for(func_iter = functions.begin(); func_iter != functions.end(); ++func_iter) { + t_function* tfunc = (*func_iter); + string function_name("process_" + rust_snake_case(tfunc->get_name())); + f_gen_ + << indent() + << "fn " << function_name + << "(&self, " + << "incoming_sequence_number: i32, " + << "i_prot: &mut dyn TInputProtocol, " + << "o_prot: &mut dyn TOutputProtocol) " + << "-> thrift::Result<()> {" + << endl; + indent_up(); + + f_gen_ + << indent() + << actual_processor + << "::" << function_name + << "(" + << "&self.handler, " + << "incoming_sequence_number, " + << "i_prot, " + << "o_prot" + << ")" + << endl; + + indent_down(); + f_gen_ << indent() << "}" << endl; + } + + t_service* extends = tservice->get_extends(); + if (extends) { + render_sync_process_delegation_functions(extends); + } +} + +void t_rs_generator::render_process_match_statements(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator func_iter; + for(func_iter = functions.begin(); func_iter != functions.end(); ++func_iter) { + t_function* tfunc = (*func_iter); + f_gen_ << indent() << "\"" << tfunc->get_name() << "\"" << " => {" << endl; // note: use *original* name + indent_up(); + f_gen_ + << indent() + << "self.process_" << rust_snake_case(tfunc->get_name()) + << "(message_ident.sequence_number, i_prot, o_prot)" + << endl; + indent_down(); + f_gen_ << indent() << "}," << endl; + } + + t_service* extends = tservice->get_extends(); + if (extends) { + render_process_match_statements(extends); + } +} + +void t_rs_generator::render_sync_process_function(t_function *tfunc, const string &handler_type) { + string sequence_number_param("incoming_sequence_number"); + string output_protocol_param("o_prot"); + + if (tfunc->is_oneway()) { + sequence_number_param = "_"; + output_protocol_param = "_"; + } + + f_gen_ + << indent() + << "pub fn process_" << rust_snake_case(tfunc->get_name()) + << "<H: " << handler_type << ">" + << "(handler: &H, " + << sequence_number_param << ": i32, " + << "i_prot: &mut dyn TInputProtocol, " + << output_protocol_param << ": &mut dyn TOutputProtocol) " + << "-> thrift::Result<()> {" + << endl; + + indent_up(); + + // *always* read arguments from the input protocol + f_gen_ + << indent() + << "let " + << (has_non_void_args(tfunc) ? "args" : "_") + << " = " + << service_call_args_struct_name(tfunc) + << "::read_from_in_protocol(i_prot)?;" + << endl; + + f_gen_ + << indent() + << "match handler." + << service_call_handler_function_name(tfunc) + << rust_sync_service_call_invocation(tfunc, "args.") + << " {" + << endl; // start match + indent_up(); + + // handler succeeded + string handler_return_variable = tfunc->is_oneway() || tfunc->get_returntype()->is_void() ? "_" : "handler_return"; + f_gen_ << indent() << "Ok(" << handler_return_variable << ") => {" << endl; + indent_up(); + render_sync_handler_succeeded(tfunc); + indent_down(); + f_gen_ << indent() << "}," << endl; + // handler failed + f_gen_ << indent() << "Err(e) => {" << endl; + indent_up(); + render_sync_handler_failed(tfunc); + indent_down(); + f_gen_ << indent() << "}," << endl; + + indent_down(); + f_gen_ << indent() << "}" << endl; // end match + + indent_down(); + f_gen_ << indent() << "}" << endl; // end function +} + +void t_rs_generator::render_sync_handler_succeeded(t_function *tfunc) { + if (tfunc->is_oneway()) { + f_gen_ << indent() << "Ok(())" << endl; + } else { + f_gen_ + << indent() + << "let message_ident = TMessageIdentifier::new(" + << "\"" << tfunc->get_name() << "\", " // note: use *original* name + << "TMessageType::Reply, " + << "incoming_sequence_number);" + << endl; + f_gen_ << indent() << "o_prot.write_message_begin(&message_ident)?;" << endl; + f_gen_ << indent() << "let ret = " << handler_successful_return_struct(tfunc) <<";" << endl; + f_gen_ << indent() << "ret.write_to_out_protocol(o_prot)?;" << endl; + f_gen_ << indent() << "o_prot.write_message_end()?;" << endl; + f_gen_ << indent() << "o_prot.flush()" << endl; + } +} + +void t_rs_generator::render_sync_handler_failed(t_function *tfunc) { + string err_var("e"); + + f_gen_ << indent() << "match " << err_var << " {" << endl; + indent_up(); + + // if there are any user-defined exceptions for this service call handle them first + if (tfunc->get_xceptions() != NULL && tfunc->get_xceptions()->get_sorted_members().size() > 0) { + string user_err_var("usr_err"); + f_gen_ << indent() << "thrift::Error::User(" << user_err_var << ") => {" << endl; + indent_up(); + render_sync_handler_failed_user_exception_branch(tfunc); + indent_down(); + f_gen_ << indent() << "}," << endl; + } + + // application error + string app_err_var("app_err"); + f_gen_ << indent() << "thrift::Error::Application(" << app_err_var << ") => {" << endl; + indent_up(); + render_sync_handler_failed_application_exception_branch(tfunc, app_err_var); + indent_down(); + f_gen_ << indent() << "}," << endl; + + // default case + f_gen_ << indent() << "_ => {" << endl; + indent_up(); + render_sync_handler_failed_default_exception_branch(tfunc); + indent_down(); + f_gen_ << indent() << "}," << endl; + + indent_down(); + f_gen_ << indent() << "}" << endl; +} + +void t_rs_generator::render_sync_handler_failed_user_exception_branch(t_function *tfunc) { + if (tfunc->get_xceptions() == NULL || tfunc->get_xceptions()->get_sorted_members().empty()) { + throw "cannot render user exception branches if no user exceptions defined"; + } + + const vector<t_field*> txceptions = tfunc->get_xceptions()->get_sorted_members(); + vector<t_field*>::const_iterator xception_iter; + int branches_rendered = 0; + + // run through all user-defined exceptions + for (xception_iter = txceptions.begin(); xception_iter != txceptions.end(); ++xception_iter) { + t_field* xception_field = (*xception_iter); + + string if_statement(branches_rendered == 0 ? "if usr_err" : "} else if usr_err"); + string exception_type(to_rust_type(xception_field->get_type())); + f_gen_ << indent() << if_statement << ".downcast_ref::<" << exception_type << ">().is_some() {" << endl; + indent_up(); + + f_gen_ + << indent() + << "let err = usr_err.downcast::<" << exception_type << ">().expect(\"downcast already checked\");" + << endl; + + // render the members of the return struct + ostringstream members; + + bool has_result_variable = !(tfunc->is_oneway() || tfunc->get_returntype()->is_void()); + if (has_result_variable) { + members << SERVICE_RESULT_VARIABLE << ": None, "; + } + + vector<t_field*>::const_iterator xception_members_iter; + for(xception_members_iter = txceptions.begin(); xception_members_iter != txceptions.end(); ++xception_members_iter) { + t_field* member = (*xception_members_iter); + string member_name(rust_field_name(member)); + if (member == xception_field) { + members << member_name << ": Some(*err), "; + } else { + members << member_name << ": None, "; + } + } + + string member_string = members.str(); + member_string.replace(member_string.size() - 2, 2, " "); // trim trailing comma + + // now write out the return struct + f_gen_ + << indent() + << "let ret_err = " + << service_call_result_struct_name(tfunc) + << "{ " << member_string << "};" + << endl; + + f_gen_ + << indent() + << "let message_ident = " + << "TMessageIdentifier::new(" + << "\"" << tfunc->get_name() << "\", " // note: use *original* name + << "TMessageType::Reply, " + << "incoming_sequence_number);" + << endl; + f_gen_ << indent() << "o_prot.write_message_begin(&message_ident)?;" << endl; + f_gen_ << indent() << "ret_err.write_to_out_protocol(o_prot)?;" << endl; + f_gen_ << indent() << "o_prot.write_message_end()?;" << endl; + f_gen_ << indent() << "o_prot.flush()" << endl; + + indent_down(); + + branches_rendered++; + } + + // the catch all, if somehow it was a user exception that we don't support + f_gen_ << indent() << "} else {" << endl; + indent_up(); + + // FIXME: same as default block below + + f_gen_ << indent() << "let ret_err = {" << endl; + indent_up(); + render_thrift_error_struct("ApplicationError", "ApplicationErrorKind::Unknown", "usr_err.description()"); + indent_down(); + f_gen_ << indent() << "};" << endl; + render_sync_handler_send_exception_response(tfunc, "ret_err"); + + indent_down(); + f_gen_ << indent() << "}" << endl; +} + +void t_rs_generator::render_sync_handler_failed_application_exception_branch( + t_function *tfunc, + const string &app_err_var +) { + if (tfunc->is_oneway()) { + f_gen_ << indent() << "Err(thrift::Error::Application(" << app_err_var << "))" << endl; + } else { + render_sync_handler_send_exception_response(tfunc, app_err_var); + } +} + +void t_rs_generator::render_sync_handler_failed_default_exception_branch(t_function *tfunc) { + f_gen_ << indent() << "let ret_err = {" << endl; + indent_up(); + render_thrift_error_struct("ApplicationError", "ApplicationErrorKind::Unknown", "e.description()"); + indent_down(); + f_gen_ << indent() << "};" << endl; + if (tfunc->is_oneway()) { + f_gen_ << indent() << "Err(thrift::Error::Application(ret_err))" << endl; + } else { + render_sync_handler_send_exception_response(tfunc, "ret_err"); + } +} + +void t_rs_generator::render_sync_handler_send_exception_response(t_function *tfunc, const string &err_var) { + f_gen_ + << indent() + << "let message_ident = TMessageIdentifier::new(" + << "\"" << tfunc->get_name() << "\", " // note: use *original* name + << "TMessageType::Exception, " + << "incoming_sequence_number);" + << endl; + f_gen_ << indent() << "o_prot.write_message_begin(&message_ident)?;" << endl; + f_gen_ << indent() << "thrift::Error::write_application_error_to_out_protocol(&" << err_var << ", o_prot)?;" << endl; + f_gen_ << indent() << "o_prot.write_message_end()?;" << endl; + f_gen_ << indent() << "o_prot.flush()" << endl; +} + +string t_rs_generator::handler_successful_return_struct(t_function* tfunc) { + int member_count = 0; + ostringstream return_struct; + + return_struct << service_call_result_struct_name(tfunc) << " { "; + + // actual return + if (!tfunc->get_returntype()->is_void()) { + return_struct << "result_value: Some(handler_return)"; + member_count++; + } + + // any user-defined exceptions + if (tfunc->get_xceptions() != NULL) { + t_struct* txceptions = tfunc->get_xceptions(); + const vector<t_field*> members = txceptions->get_sorted_members(); + vector<t_field*>::const_iterator members_iter; + for (members_iter = members.begin(); members_iter != members.end(); ++members_iter) { + t_field* xception_field = (*members_iter); + if (member_count > 0) { return_struct << ", "; } + return_struct << rust_field_name(xception_field) << ": None"; + member_count++; + } + } + + return_struct << " }"; + + return return_struct.str(); +} + +//----------------------------------------------------------------------------- +// +// Utility +// +//----------------------------------------------------------------------------- + +void t_rs_generator::render_type_comment(const string& type_name) { + f_gen_ << "//" << endl; + f_gen_ << "// " << type_name << endl; + f_gen_ << "//" << endl; + f_gen_ << endl; +} + +// NOTE: do *not* put in an extra newline after doc is generated. +// This is because rust docs have to abut the line they're documenting. +void t_rs_generator::render_rustdoc(t_doc* tdoc) { + if (!tdoc->has_doc()) { + return; + } + + generate_docstring_comment(f_gen_, "", "/// ", tdoc->get_doc(), ""); +} + +void t_rs_generator::render_thrift_error( + const string& error_kind, + const string& error_struct, + const string& sub_error_kind, + const string& error_message +) { + f_gen_ << indent() << "Err(" << endl; + indent_up(); + f_gen_ << indent() << "thrift::Error::" << error_kind << "(" << endl; + indent_up(); + render_thrift_error_struct(error_struct, sub_error_kind, error_message); + indent_down(); + f_gen_ << indent() << ")" << endl; + indent_down(); + f_gen_ << indent() << ")" << endl; +} + +void t_rs_generator::render_thrift_error_struct( + const string& error_struct, + const string& sub_error_kind, + const string& error_message +) { + f_gen_ << indent() << error_struct << "::new(" << endl; + indent_up(); + f_gen_ << indent() << sub_error_kind << "," << endl; + f_gen_ << indent() << error_message << endl; + indent_down(); + f_gen_ << indent() << ")" << endl; +} + +bool t_rs_generator::is_double(t_type* ttype) { + ttype = get_true_type(ttype); + if (ttype->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)ttype)->get_base(); + if (tbase == t_base_type::TYPE_DOUBLE) { + return true; + } + } + + return false; +} + +string t_rs_generator::to_rust_type(t_type* ttype, bool ordered_float) { + // ttype = get_true_type(ttype); <-- recurses through as many typedef layers as necessary + if (ttype->is_base_type()) { + t_base_type* tbase_type = ((t_base_type*)ttype); + switch (tbase_type->get_base()) { + case t_base_type::TYPE_VOID: + return "()"; + case t_base_type::TYPE_STRING: + if (tbase_type->is_binary()) { + return "Vec<u8>"; + } else { + return "String"; + } + case t_base_type::TYPE_BOOL: + return "bool"; + case t_base_type::TYPE_I8: + return "i8"; + case t_base_type::TYPE_I16: + return "i16"; + case t_base_type::TYPE_I32: + return "i32"; + case t_base_type::TYPE_I64: + return "i64"; + case t_base_type::TYPE_DOUBLE: + if (ordered_float) { + return "OrderedFloat<f64>"; + } else { + return "f64"; + } + } + } else if (ttype->is_typedef()) { + t_typedef* ttypedef = (t_typedef*)ttype; + string rust_type = rust_namespace(ttype) + ttypedef->get_symbolic(); + rust_type = ttypedef->is_forward_typedef() ? "Box<" + rust_type + ">" : rust_type; + return rust_type; + } else if (ttype->is_enum()) { + return rust_namespace(ttype) + rust_camel_case(ttype->get_name()); + } else if (ttype->is_struct() || ttype->is_xception()) { + return rust_namespace(ttype) + rust_camel_case(ttype->get_name()); + } else if (ttype->is_map()) { + t_map* tmap = (t_map*)ttype; + return "BTreeMap<" + to_rust_type(tmap->get_key_type()) + ", " + to_rust_type(tmap->get_val_type()) + ">"; + } else if (ttype->is_set()) { + t_set* tset = (t_set*)ttype; + return "BTreeSet<" + to_rust_type(tset->get_elem_type()) + ">"; + } else if (ttype->is_list()) { + t_list* tlist = (t_list*)ttype; + return "Vec<" + to_rust_type(tlist->get_elem_type()) + ">"; + } + + throw "cannot find rust type for " + ttype->get_name(); +} + +string t_rs_generator::to_rust_const_type(t_type* ttype, bool ordered_float) { + if (ttype->is_base_type()) { + t_base_type* tbase_type = ((t_base_type*)ttype); + if (tbase_type->get_base() == t_base_type::TYPE_STRING) { + if (tbase_type->is_binary()) { + return "&[u8]"; + } else { + return "&str"; + } + } + } + + return to_rust_type(ttype, ordered_float); +} + +string t_rs_generator::to_rust_field_type_enum(t_type* ttype) { + ttype = get_true_type(ttype); + if (ttype->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)ttype)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "will not generate protocol::TType for TYPE_VOID"; + case t_base_type::TYPE_STRING: // both strings and binary are actually encoded as TType::String + return "TType::String"; + case t_base_type::TYPE_BOOL: + return "TType::Bool"; + case t_base_type::TYPE_I8: + return "TType::I08"; + case t_base_type::TYPE_I16: + return "TType::I16"; + case t_base_type::TYPE_I32: + return "TType::I32"; + case t_base_type::TYPE_I64: + return "TType::I64"; + case t_base_type::TYPE_DOUBLE: + return "TType::Double"; + } + } else if (ttype->is_enum()) { + return "TType::I32"; + } else if (ttype->is_struct() || ttype->is_xception()) { + return "TType::Struct"; + } else if (ttype->is_map()) { + return "TType::Map"; + } else if (ttype->is_set()) { + return "TType::Set"; + } else if (ttype->is_list()) { + return "TType::List"; + } + + throw "cannot find TType for " + ttype->get_name(); +} + +string t_rs_generator::opt_in_req_out_value(t_type* ttype) { + ttype = get_true_type(ttype); + if (ttype->is_base_type()) { + t_base_type* tbase_type = ((t_base_type*)ttype); + switch (tbase_type->get_base()) { + case t_base_type::TYPE_VOID: + throw "cannot generate OPT_IN_REQ_OUT value for void"; + case t_base_type::TYPE_STRING: + if (tbase_type->is_binary()) { + return "Some(Vec::new())"; + } else { + return "Some(\"\".to_owned())"; + } + case t_base_type::TYPE_BOOL: + return "Some(false)"; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + return "Some(0)"; + case t_base_type::TYPE_DOUBLE: + return "Some(OrderedFloat::from(0.0))"; + } + + } else if (ttype->is_enum() || ttype->is_struct() || ttype->is_xception()) { + return "None"; + } else if (ttype->is_list()) { + return "Some(Vec::new())"; + } else if (ttype->is_set()) { + return "Some(BTreeSet::new())"; + } else if (ttype->is_map()) { + return "Some(BTreeMap::new())"; + } + + throw "cannot generate opt-in-req-out value for type " + ttype->get_name(); +} + +bool t_rs_generator::can_generate_simple_const(t_type* ttype) { + t_type* actual_type = get_true_type(ttype); + if (actual_type->is_base_type()) { + t_base_type* tbase_type = (t_base_type*)actual_type; + return !(tbase_type->get_base() == t_base_type::TYPE_DOUBLE); + } else { + return false; + } +} + +bool t_rs_generator::can_generate_const_holder(t_type* ttype) { + t_type* actual_type = get_true_type(ttype); + return !can_generate_simple_const(actual_type) && !actual_type->is_service(); +} + +bool t_rs_generator::is_void(t_type* ttype) { + return ttype->is_base_type() && ((t_base_type*)ttype)->get_base() == t_base_type::TYPE_VOID; +} + +bool t_rs_generator::is_optional(t_field::e_req req) { + return req == t_field::T_OPTIONAL || req == t_field::T_OPT_IN_REQ_OUT; +} + +t_field::e_req t_rs_generator::actual_field_req(t_field* tfield, t_rs_generator::e_struct_type struct_type) { + return struct_type == t_rs_generator::T_ARGS ? t_field::T_REQUIRED : tfield->get_req(); +} + +bool t_rs_generator::has_args(t_function* tfunc) { + return tfunc->get_arglist() != NULL && !tfunc->get_arglist()->get_sorted_members().empty(); +} + +bool t_rs_generator::has_non_void_args(t_function* tfunc) { + bool has_non_void_args = false; + + const vector<t_field*> args = tfunc->get_arglist()->get_sorted_members(); + vector<t_field*>::const_iterator args_iter; + for (args_iter = args.begin(); args_iter != args.end(); ++args_iter) { + t_field* tfield = (*args_iter); + if (!tfield->get_type()->is_void()) { + has_non_void_args = true; + break; + } + } + + return has_non_void_args; +} + +string t_rs_generator::visibility_qualifier(t_rs_generator::e_struct_type struct_type) { + switch(struct_type) { + case t_rs_generator::T_ARGS: + case t_rs_generator::T_RESULT: + return ""; + default: + return "pub "; + } +} + +string t_rs_generator::rust_namespace(t_service* tservice) { + if (tservice->get_program()->get_name() != get_program()->get_name()) { + return rust_snake_case(tservice->get_program()->get_name()) + "::"; + } else { + return ""; + } +} + +string t_rs_generator::rust_namespace(t_type* ttype) { + if (ttype->get_program()->get_name() != get_program()->get_name()) { + return rust_snake_case(ttype->get_program()->get_name()) + "::"; + } else { + return ""; + } +} + +bool t_rs_generator::is_reserved(const string& name) { + return RUST_RESERVED_WORDS_SET.find(name) != RUST_RESERVED_WORDS_SET.end(); +} + +string t_rs_generator::rust_struct_name(t_struct* tstruct) { + string base_struct_name(rust_camel_case(tstruct->get_name())); + return rust_safe_name(base_struct_name); +} + +string t_rs_generator::rust_field_name(t_field* tfield) { + string base_field_name(rust_snake_case(tfield->get_name())); + return rust_safe_name(base_field_name); +} + +string t_rs_generator::rust_union_field_name(t_field* tfield) { + string base_field_name(rust_camel_case(tfield->get_name())); + return rust_safe_name(base_field_name); +} + +string t_rs_generator::rust_safe_name(const string& name) { + if (is_reserved(name)) { + return name + "_"; + } else { + return name; + } +} + +string t_rs_generator::service_call_client_function_name(t_function* tfunc) { + return rust_snake_case(tfunc->get_name()); +} + +string t_rs_generator::service_call_handler_function_name(t_function* tfunc) { + return "handle_" + rust_snake_case(tfunc->get_name()); +} + +string t_rs_generator::service_call_args_struct_name(t_function* tfunc) { + // Thrift automatically appends `Args` to the arglist name. No need to do it here. + return rust_camel_case(service_name_) + rust_camel_case(tfunc->get_arglist()->get_name()); +} + +string t_rs_generator::service_call_result_struct_name(t_function* tfunc) { + return rust_camel_case(service_name_) + rust_camel_case(tfunc->get_name()) + RESULT_STRUCT_SUFFIX; +} + +string t_rs_generator::rust_sync_client_marker_trait_name(t_service* tservice) { + return "T" + rust_camel_case(tservice->get_name()) + "SyncClientMarker"; +} + +string t_rs_generator::rust_sync_client_trait_name(t_service* tservice) { + return "T" + rust_camel_case(tservice->get_name()) + "SyncClient"; +} + +string t_rs_generator::rust_sync_client_impl_name(t_service* tservice) { + return rust_camel_case(tservice->get_name()) + "SyncClient"; +} + +string t_rs_generator::rust_sync_handler_trait_name(t_service* tservice) { + return rust_camel_case(tservice->get_name()) + "SyncHandler"; +} + +string t_rs_generator::rust_sync_processor_name(t_service* tservice) { + return rust_camel_case(tservice->get_name()) + "SyncProcessor"; +} + +string t_rs_generator::rust_sync_processor_impl_name(t_service *tservice) { + return "T" + rust_camel_case(tservice->get_name()) + "ProcessFunctions"; +} + +string t_rs_generator::rust_enum_variant_name(const string &name) { + bool all_uppercase = true; + + for (char i : name) { + if (isalnum(i) && islower(i)) { + all_uppercase = false; + break; + } + } + + if (all_uppercase) { + return capitalize(camelcase(lowercase(name))); + } else { + return capitalize(camelcase(name)); + } +} + +string t_rs_generator::rust_upper_case(const string& name) { + string str(uppercase(underscore(name))); + string_replace(str, "__", "_"); + return str; +} + +string t_rs_generator::rust_snake_case(const string& name) { + string str(decapitalize(underscore(name))); + string_replace(str, "__", "_"); + return str; +} + +string t_rs_generator::rust_camel_case(const string& name) { + string str(capitalize(camelcase(name))); + string_replace(str, "_", ""); + return str; +} + +string t_rs_generator::rust_safe_field_id(int32_t id) { + string id_str = std::to_string(abs(id)); + if (id >= 0) { + return id_str; + } else { + string str("neg"); + str += id_str; + return str; + } +} + +void t_rs_generator::string_replace(string& target, const string& search_string, const string& replace_string) { + if (target.empty()) { + return; + } + + size_t match_len = search_string.length(); + size_t replace_len = replace_string.length(); + + size_t search_idx = 0; + size_t match_idx; + while ((match_idx = target.find(search_string, search_idx)) != string::npos) { + target.replace(match_idx, match_len, replace_string); + search_idx = match_idx + replace_len; + } +} + +THRIFT_REGISTER_GENERATOR( + rs, + "Rust", + "\n") // no Rust-generator-specific options diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_st_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_st_generator.cc new file mode 100644 index 000000000..bc95feb66 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_st_generator.cc @@ -0,0 +1,1056 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +#include <string> +#include <fstream> +#include <iostream> +#include <vector> + +#include <stdlib.h> +#include <time.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sstream> + +#include "thrift/platform.h" +#include "thrift/version.h" +#include "thrift/generate/t_oop_generator.h" + +using std::map; +using std::ofstream; +using std::ostringstream; +using std::string; +using std::stringstream; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes + +/** + * Smalltalk code generator. + * + */ +class t_st_generator : public t_oop_generator { +public: + t_st_generator(t_program* program, + const std::map<std::string, std::string>& parsed_options, + const std::string& option_string) + : t_oop_generator(program) { + (void)option_string; + temporary_var = 0; + std::map<std::string, std::string>::const_iterator iter; + + /* no options yet */ + for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + throw "unknown option st:" + iter->first; + } + + out_dir_base_ = "gen-st"; + } + + /** + * Init and close methods + */ + + void init_generator() override; + void close_generator() override; + + /** + * Program-level generation functions + */ + + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_const(t_const* tconst) override; + void generate_struct(t_struct* tstruct) override; + void generate_xception(t_struct* txception) override; + void generate_service(t_service* tservice) override; + void generate_class_side_definition(); + void generate_force_consts(); + + std::string render_const_value(t_type* type, t_const_value* value); + + /** + * Struct generation code + */ + + void generate_st_struct(std::ostream& out, t_struct* tstruct, bool is_exception); + void generate_accessors(std::ostream& out, t_struct* tstruct); + + /** + * Service-level generation functions + */ + + void generate_service_client(t_service* tservice); + + void generate_send_method(t_function* tfunction); + void generate_recv_method(t_function* tfunction); + + std::string map_reader(t_map* tmap); + std::string list_reader(t_list* tlist); + std::string set_reader(t_set* tset); + std::string struct_reader(t_struct* tstruct, std::string clsName); + + std::string map_writer(t_map* tmap, std::string name); + std::string list_writer(t_list* tlist, std::string name); + std::string set_writer(t_set* tset, std::string name); + std::string struct_writer(t_struct* tstruct, std::string fname); + + std::string write_val(t_type* t, std::string fname); + std::string read_val(t_type* t); + + /** + * Helper rendering functions + */ + + std::string st_autogen_comment(); + + void st_class_def(std::ostream& out, std::string name); + void st_method(std::ostream& out, std::string cls, std::string name); + void st_method(std::ostream& out, std::string cls, std::string name, std::string category); + void st_close_method(std::ostream& out); + void st_class_method(std::ostream& out, std::string cls, std::string name); + void st_class_method(std::ostream& out, std::string cls, std::string name, std::string category); + void st_setter(std::ostream& out, std::string cls, std::string name, std::string type); + void st_getter(std::ostream& out, std::string cls, std::string name); + void st_accessors(std::ostream& out, std::string cls, std::string name, std::string type); + + std::string class_name(); + static bool is_valid_namespace(const std::string& sub_namespace); + std::string client_class_name(); + std::string prefix(std::string name); + std::string declare_field(t_field* tfield); + std::string type_name(t_type* ttype); + + std::string function_signature(t_function* tfunction); + std::string argument_list(t_struct* tstruct); + std::string function_types_comment(t_function* fn); + + std::string type_to_enum(t_type* ttype); + std::string a_type(t_type* type); + bool is_vowel(char c); + std::string temp_name(); + std::string generated_category(); + +private: + /** + * File streams + */ + int temporary_var; + ofstream_with_content_based_conditional_update f_; +}; + +/** + * Prepares for file generation by opening up the necessary file output + * streams. + * + * @param tprogram The program to generate + */ +void t_st_generator::init_generator() { + // Make output directory + MKDIR(get_out_dir().c_str()); + + temporary_var = 0; + + // Make output file + string f_name = get_out_dir() + "/" + program_name_ + ".st"; + f_.open(f_name.c_str()); + + // Print header + f_ << st_autogen_comment() << endl; + + st_class_def(f_, program_name_); + generate_class_side_definition(); + + // Generate enums + vector<t_enum*> enums = program_->get_enums(); + vector<t_enum*>::iterator en_iter; + for (en_iter = enums.begin(); en_iter != enums.end(); ++en_iter) { + generate_enum(*en_iter); + } +} + +string t_st_generator::class_name() { + return capitalize(program_name_); +} + +bool t_st_generator::is_valid_namespace(const std::string& sub_namespace) { + return sub_namespace == "prefix" || sub_namespace == "category"; +} + +string t_st_generator::prefix(string class_name) { + string prefix = program_->get_namespace("smalltalk.prefix"); + string name = capitalize(class_name); + name = prefix.empty() ? name : (prefix + name); + return name; +} + +string t_st_generator::client_class_name() { + return capitalize(service_name_) + "Client"; +} + +/** + * Autogen'd comment + */ +string t_st_generator::st_autogen_comment() { + return std::string("'") + "Autogenerated by Thrift Compiler (" + THRIFT_VERSION + ")\n" + "\n" + + "DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n" + "'!\n"; +} + +void t_st_generator::generate_force_consts() { + f_ << prefix(class_name()) << " enums keysAndValuesDo: [:k :v | " << prefix(class_name()) + << " enums at: k put: v value].!" << endl; + + f_ << prefix(class_name()) << " constants keysAndValuesDo: [:k :v | " << prefix(class_name()) + << " constants at: k put: v value].!" << endl; +} + +void t_st_generator::close_generator() { + generate_force_consts(); + f_.close(); +} + +string t_st_generator::generated_category() { + string cat = program_->get_namespace("smalltalk.category"); + // For compatibility with the Thrift grammar, the category must + // be punctuated by dots. Replaces them with dashes here. + for (char & iter : cat) { + if (iter == '.') { + iter = '-'; + } + } + return cat.size() ? cat : "Generated-" + class_name(); +} + +/** + * Generates a typedef. This is not done in Smalltalk, types are all implicit. + * + * @param ttypedef The type definition + */ +void t_st_generator::generate_typedef(t_typedef* ttypedef) { + (void)ttypedef; +} + +void t_st_generator::st_class_def(std::ostream& out, string name) { + out << "Object subclass: #" << prefix(name) << endl; + indent_up(); + out << indent() << "instanceVariableNames: ''" << endl << indent() << "classVariableNames: ''" + << endl << indent() << "poolDictionaries: ''" << endl << indent() << "category: '" + << generated_category() << "'!" << endl << endl; +} + +void t_st_generator::st_method(std::ostream& out, string cls, string name) { + st_method(out, cls, name, "as yet uncategorized"); +} + +void t_st_generator::st_class_method(std::ostream& out, string cls, string name) { + st_method(out, cls + " class", name); +} + +void t_st_generator::st_class_method(std::ostream& out, string cls, string name, string category) { + st_method(out, cls, name, category); +} + +void t_st_generator::st_method(std::ostream& out, string cls, string name, string category) { + char timestr[50]; + time_t rawtime; + struct tm* tinfo; + + time(&rawtime); + tinfo = localtime(&rawtime); + strftime(timestr, 50, "%m/%d/%Y %H:%M", tinfo); + + out << "!" << prefix(cls) << " methodsFor: '" + category + "' stamp: 'thrift " << timestr + << "'!\n" << name << endl; + + indent_up(); + out << indent(); +} + +void t_st_generator::st_close_method(std::ostream& out) { + out << "! !" << endl << endl; + indent_down(); +} + +void t_st_generator::st_setter(std::ostream& out, + string cls, + string name, + string type = "anObject") { + st_method(out, cls, name + ": " + type); + out << name << " := " + type; + st_close_method(out); +} + +void t_st_generator::st_getter(std::ostream& out, string cls, string name) { + st_method(out, cls, name + ""); + out << "^ " << name; + st_close_method(out); +} + +void t_st_generator::st_accessors(std::ostream& out, + string cls, + string name, + string type = "anObject") { + st_setter(out, cls, name, type); + st_getter(out, cls, name); +} + +void t_st_generator::generate_class_side_definition() { + f_ << prefix(class_name()) << " class" << endl << "\tinstanceVariableNames: 'constants enums'!" + << endl << endl; + + st_accessors(f_, class_name() + " class", "enums"); + st_accessors(f_, class_name() + " class", "constants"); + + f_ << prefix(class_name()) << " enums: Dictionary new!" << endl; + f_ << prefix(class_name()) << " constants: Dictionary new!" << endl; + + f_ << endl; +} + +/** + * Generates code for an enumerated type. Done using a class to scope + * the values. + * + * @param tenum The enumeration + */ +void t_st_generator::generate_enum(t_enum* tenum) { + string cls_name = program_name_ + capitalize(tenum->get_name()); + + f_ << prefix(class_name()) << " enums at: '" << tenum->get_name() << "' put: [" + << "(Dictionary new " << endl; + + vector<t_enum_value*> constants = tenum->get_constants(); + vector<t_enum_value*>::iterator c_iter; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + int value = (*c_iter)->get_value(); + f_ << "\tat: '" << (*c_iter)->get_name() << "' put: " << value << ";" << endl; + } + + f_ << "\tyourself)]!" << endl << endl; +} + +/** + * Generate a constant value + */ +void t_st_generator::generate_const(t_const* tconst) { + t_type* type = tconst->get_type(); + string name = tconst->get_name(); + t_const_value* value = tconst->get_value(); + + f_ << prefix(class_name()) << " constants at: '" << name << "' put: [" + << render_const_value(type, value) << "]!" << endl << endl; +} + +/** + * Prints the value of a constant with the given type. Note that type checking + * is NOT performed in this function as it is always run beforehand using the + * validate_types method in main.cc + */ +string t_st_generator::render_const_value(t_type* type, t_const_value* value) { + type = get_true_type(type); + std::ostringstream out; + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + out << '"' << get_escaped_string(value) << '"'; + break; + case t_base_type::TYPE_BOOL: + out << (value->get_integer() > 0 ? "true" : "false"); + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + out << value->get_integer(); + break; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) { + out << value->get_integer(); + } else { + out << value->get_double(); + } + break; + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + indent(out) << value->get_integer(); + } else if (type->is_struct() || type->is_xception()) { + out << "(" << capitalize(type->get_name()) << " new " << endl; + indent_up(); + + const vector<t_field*>& fields = ((t_struct*)type)->get_members(); + vector<t_field*>::const_iterator f_iter; + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + t_type* field_type = NULL; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field_type = (*f_iter)->get_type(); + } + } + if (field_type == NULL) { + throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string(); + } + + out << indent() << v_iter->first->get_string() << ": " + << render_const_value(field_type, v_iter->second) << ";" << endl; + } + out << indent() << "yourself)"; + + indent_down(); + } else if (type->is_map()) { + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + out << "(Dictionary new" << endl; + indent_up(); + indent_up(); + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + out << indent() << indent(); + out << "at: " << render_const_value(ktype, v_iter->first); + out << " put: "; + out << render_const_value(vtype, v_iter->second); + out << ";" << endl; + } + out << indent() << indent() << "yourself)"; + indent_down(); + indent_down(); + } else if (type->is_list() || type->is_set()) { + t_type* etype; + if (type->is_list()) { + etype = ((t_list*)type)->get_elem_type(); + } else { + etype = ((t_set*)type)->get_elem_type(); + } + if (type->is_set()) { + out << "(Set new" << endl; + } else { + out << "(OrderedCollection new" << endl; + } + indent_up(); + indent_up(); + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + out << indent() << indent(); + out << "add: " << render_const_value(etype, *v_iter); + out << ";" << endl; + } + out << indent() << indent() << "yourself)"; + indent_down(); + indent_down(); + } else { + throw "CANNOT GENERATE CONSTANT FOR TYPE: " + type->get_name(); + } + return out.str(); +} + +/** + * Generates a Smalltalk struct + */ +void t_st_generator::generate_struct(t_struct* tstruct) { + generate_st_struct(f_, tstruct, false); +} + +/** + * Generates a struct definition for a thrift exception. Basically the same + * as a struct but extends the Exception class. + * + * @param txception The struct definition + */ +void t_st_generator::generate_xception(t_struct* txception) { + generate_st_struct(f_, txception, true); +} + +/** + * Generates a smalltalk class to represent a struct + */ +void t_st_generator::generate_st_struct(std::ostream& out, + t_struct* tstruct, + bool is_exception = false) { + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + if (is_exception) + out << "Error"; + else + out << "Object"; + + out << " subclass: #" << prefix(type_name(tstruct)) << endl << "\tinstanceVariableNames: '"; + + if (members.size() > 0) { + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if (m_iter != members.begin()) + out << " "; + out << camelcase((*m_iter)->get_name()); + } + } + + out << "'\n" + << "\tclassVariableNames: ''\n" + << "\tpoolDictionaries: ''\n" + << "\tcategory: '" << generated_category() << "'!\n\n"; + + generate_accessors(out, tstruct); +} + +bool t_st_generator::is_vowel(char c) { + switch (tolower(c)) { + case 'a': + case 'e': + case 'i': + case 'o': + case 'u': + return true; + } + return false; +} + +string t_st_generator::a_type(t_type* type) { + string prefix; + + if (is_vowel(type_name(type)[0])) + prefix = "an"; + else + prefix = "a"; + + return prefix + capitalize(type_name(type)); +} + +void t_st_generator::generate_accessors(std::ostream& out, t_struct* tstruct) { + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + string type; + string prefix; + + if (members.size() > 0) { + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + st_accessors(out, + capitalize(type_name(tstruct)), + camelcase((*m_iter)->get_name()), + a_type((*m_iter)->get_type())); + } + out << endl; + } +} + +/** + * Generates a thrift service. + * + * @param tservice The service definition + */ +void t_st_generator::generate_service(t_service* tservice) { + generate_service_client(tservice); + // generate_service_server(tservice); +} + +string t_st_generator::temp_name() { + std::ostringstream out; + out << "temp" << temporary_var++; + return out.str(); +} + +string t_st_generator::map_writer(t_map* tmap, string fname) { + std::ostringstream out; + string key = temp_name(); + string val = temp_name(); + + out << "[oprot writeMapBegin: (TMap new keyType: " << type_to_enum(tmap->get_key_type()) + << "; valueType: " << type_to_enum(tmap->get_val_type()) << "; size: " << fname << " size)." + << endl; + indent_up(); + + out << indent() << fname << " keysAndValuesDo: [:" << key << " :" << val << " |" << endl; + indent_up(); + + out << indent() << write_val(tmap->get_key_type(), key) << "." << endl << indent() + << write_val(tmap->get_val_type(), val); + indent_down(); + + out << "]." << endl << indent() << "oprot writeMapEnd] value"; + indent_down(); + + return out.str(); +} + +string t_st_generator::map_reader(t_map* tmap) { + std::ostringstream out; + string desc = temp_name(); + string val = temp_name(); + + out << "[|" << desc << " " << val << "| " << endl; + indent_up(); + + out << indent() << desc << " := iprot readMapBegin." << endl << indent() << val + << " := Dictionary new." << endl << indent() << desc << " size timesRepeat: [" << endl; + + indent_up(); + out << indent() << val << " at: " << read_val(tmap->get_key_type()) + << " put: " << read_val(tmap->get_val_type()); + indent_down(); + + out << "]." << endl << indent() << "iprot readMapEnd." << endl << indent() << val << "] value"; + indent_down(); + + return out.str(); +} + +string t_st_generator::list_writer(t_list* tlist, string fname) { + std::ostringstream out; + string val = temp_name(); + + out << "[oprot writeListBegin: (TList new elemType: " << type_to_enum(tlist->get_elem_type()) + << "; size: " << fname << " size)." << endl; + indent_up(); + + out << indent() << fname << " do: [:" << val << "|" << endl; + indent_up(); + + out << indent() << write_val(tlist->get_elem_type(), val) << endl; + indent_down(); + + out << "]." << endl << indent() << "oprot writeListEnd] value"; + indent_down(); + + return out.str(); +} + +string t_st_generator::list_reader(t_list* tlist) { + std::ostringstream out; + string desc = temp_name(); + string val = temp_name(); + + out << "[|" << desc << " " << val << "| " << desc << " := iprot readListBegin." << endl; + indent_up(); + + out << indent() << val << " := OrderedCollection new." << endl << indent() << desc + << " size timesRepeat: [" << endl; + + indent_up(); + out << indent() << val << " add: " << read_val(tlist->get_elem_type()); + indent_down(); + + out << "]." << endl << indent() << "iprot readListEnd." << endl << indent() << val << "] value"; + indent_down(); + + return out.str(); +} + +string t_st_generator::set_writer(t_set* tset, string fname) { + std::ostringstream out; + string val = temp_name(); + + out << "[oprot writeSetBegin: (TSet new elemType: " << type_to_enum(tset->get_elem_type()) + << "; size: " << fname << " size)." << endl; + indent_up(); + + out << indent() << fname << " do: [:" << val << "|" << endl; + indent_up(); + + out << indent() << write_val(tset->get_elem_type(), val) << endl; + indent_down(); + + out << "]." << endl << indent() << "oprot writeSetEnd] value"; + indent_down(); + + return out.str(); +} + +string t_st_generator::set_reader(t_set* tset) { + std::ostringstream out; + string desc = temp_name(); + string val = temp_name(); + + out << "[|" << desc << " " << val << "| " << desc << " := iprot readSetBegin." << endl; + indent_up(); + + out << indent() << val << " := Set new." << endl << indent() << desc << " size timesRepeat: [" + << endl; + + indent_up(); + out << indent() << val << " add: " << read_val(tset->get_elem_type()); + indent_down(); + + out << "]." << endl << indent() << "iprot readSetEnd." << endl << indent() << val << "] value"; + indent_down(); + + return out.str(); +} + +string t_st_generator::struct_writer(t_struct* tstruct, string sname) { + std::ostringstream out; + const vector<t_field*>& fields = tstruct->get_sorted_members(); + vector<t_field*>::const_iterator fld_iter; + + out << "[oprot writeStructBegin: " + << "(TStruct new name: '" + tstruct->get_name() + "')." << endl; + indent_up(); + + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + bool optional = (*fld_iter)->get_req() == t_field::T_OPTIONAL; + string fname = camelcase((*fld_iter)->get_name()); + string accessor = sname + " " + camelcase(fname); + + if (optional) { + out << indent() << accessor << " ifNotNil: [" << endl; + indent_up(); + } + + out << indent() << "oprot writeFieldBegin: (TField new name: '" << fname + << "'; type: " << type_to_enum((*fld_iter)->get_type()) + << "; id: " << (*fld_iter)->get_key() << ")." << endl; + + out << indent() << write_val((*fld_iter)->get_type(), accessor) << "." << endl << indent() + << "oprot writeFieldEnd"; + + if (optional) { + out << "]"; + indent_down(); + } + + out << "." << endl; + } + + out << indent() << "oprot writeFieldStop; writeStructEnd] value"; + indent_down(); + + return out.str(); +} + +string t_st_generator::struct_reader(t_struct* tstruct, string clsName = "") { + std::ostringstream out; + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator fld_iter; + string val = temp_name(); + string desc = temp_name(); + string found = temp_name(); + + if (clsName.size() == 0) { + clsName = tstruct->get_name(); + } + + out << "[|" << desc << " " << val << "|" << endl; + indent_up(); + + // This is nasty, but without it we'll break things by prefixing TResult. + string name = ((capitalize(clsName) == "TResult") ? capitalize(clsName) : prefix(clsName)); + out << indent() << val << " := " << name << " new." << endl; + + out << indent() << "iprot readStructBegin." << endl << indent() << "[" << desc + << " := iprot readFieldBegin." << endl << indent() << desc + << " type = TType stop] whileFalse: [|" << found << "|" << endl; + indent_up(); + + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + out << indent() << desc << " id = " << (*fld_iter)->get_key() << " ifTrue: [" << endl; + indent_up(); + + out << indent() << found << " := true." << endl << indent() << val << " " + << camelcase((*fld_iter)->get_name()) << ": " << read_val((*fld_iter)->get_type()); + indent_down(); + + out << "]." << endl; + } + + out << indent() << found << " ifNil: [iprot skip: " << desc << " type]]." << endl; + indent_down(); + + out << indent() << "oprot readStructEnd." << endl << indent() << val << "] value"; + indent_down(); + + return out.str(); +} + +string t_st_generator::write_val(t_type* t, string fname) { + t = get_true_type(t); + + if (t->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)t)->get_base(); + switch (tbase) { + case t_base_type::TYPE_DOUBLE: + return "iprot writeDouble: " + fname + " asFloat"; + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + return "iprot write" + capitalize(type_name(t)) + ": " + fname + " asInteger"; + default: + return "iprot write" + capitalize(type_name(t)) + ": " + fname; + } + } else if (t->is_map()) { + return map_writer((t_map*)t, fname); + } else if (t->is_struct() || t->is_xception()) { + return struct_writer((t_struct*)t, fname); + } else if (t->is_list()) { + return list_writer((t_list*)t, fname); + } else if (t->is_set()) { + return set_writer((t_set*)t, fname); + } else if (t->is_enum()) { + return "iprot writeI32: " + fname; + } else { + throw "Sorry, I don't know how to write this: " + type_name(t); + } +} + +string t_st_generator::read_val(t_type* t) { + t = get_true_type(t); + + if (t->is_base_type()) { + return "iprot read" + capitalize(type_name(t)); + } else if (t->is_map()) { + return map_reader((t_map*)t); + } else if (t->is_struct() || t->is_xception()) { + return struct_reader((t_struct*)t); + } else if (t->is_list()) { + return list_reader((t_list*)t); + } else if (t->is_set()) { + return set_reader((t_set*)t); + } else if (t->is_enum()) { + return "iprot readI32"; + } else { + throw "Sorry, I don't know how to read this: " + type_name(t); + } +} + +void t_st_generator::generate_send_method(t_function* function) { + string funname = function->get_name(); + string signature = function_signature(function); + t_struct* arg_struct = function->get_arglist(); + const vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator fld_iter; + + st_method(f_, client_class_name(), "send" + capitalize(signature)); + f_ << "oprot writeMessageBegin:" << endl; + indent_up(); + + f_ << indent() << "(TCallMessage new" << endl; + indent_up(); + + f_ << indent() << "name: '" << funname << "'; " << endl << indent() << "seqid: self nextSeqid)." + << endl; + indent_down(); + indent_down(); + + f_ << indent() << "oprot writeStructBegin: " + << "(TStruct new name: '" + capitalize(camelcase(funname)) + "_args')." << endl; + + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + string fname = camelcase((*fld_iter)->get_name()); + + f_ << indent() << "oprot writeFieldBegin: (TField new name: '" << fname + << "'; type: " << type_to_enum((*fld_iter)->get_type()) << "; id: " << (*fld_iter)->get_key() + << ")." << endl; + + f_ << indent() << write_val((*fld_iter)->get_type(), fname) << "." << endl << indent() + << "oprot writeFieldEnd." << endl; + } + + f_ << indent() << "oprot writeFieldStop; writeStructEnd; writeMessageEnd." << endl; + f_ << indent() << "oprot transport flush"; + + st_close_method(f_); +} + +// We only support receiving TResult structures (so this won't work on the server side) +void t_st_generator::generate_recv_method(t_function* function) { + string funname = camelcase(function->get_name()); + string signature = function_signature(function); + + t_struct result(program_, "TResult"); + t_field success(function->get_returntype(), "success", 0); + result.append(&success); + + t_struct* xs = function->get_xceptions(); + const vector<t_field*>& fields = xs->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + // duplicate the field, but call it "exception"... we don't need a dynamic name + t_field* exception = new t_field((*f_iter)->get_type(), "exception", (*f_iter)->get_key()); + result.append(exception); + } + + st_method(f_, client_class_name(), "recv" + capitalize(funname)); + f_ << "| f msg res | " << endl << indent() << "msg := oprot readMessageBegin." << endl << indent() + << "self validateRemoteMessage: msg." << endl << indent() + << "res := " << struct_reader(&result) << "." << endl << indent() << "oprot readMessageEnd." + << endl << indent() << "oprot transport flush." << endl << indent() + << "res exception ifNotNil: [res exception signal]." << endl << indent() << "^ res"; + st_close_method(f_); +} + +string t_st_generator::function_types_comment(t_function* fn) { + std::ostringstream out; + const vector<t_field*>& fields = fn->get_arglist()->get_members(); + vector<t_field*>::const_iterator f_iter; + + out << "\""; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + out << camelcase((*f_iter)->get_name()) << ": " << type_name((*f_iter)->get_type()); + if ((f_iter + 1) != fields.end()) { + out << ", "; + } + } + + out << "\""; + + return out.str(); +} + +/** + * Generates a service client definition. + * + * @param tservice The service to generate a server for. + */ +void t_st_generator::generate_service_client(t_service* tservice) { + string extends = ""; + string extends_client = "TClient"; + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + extends_client = extends + "Client"; + } + + f_ << extends_client << " subclass: #" << prefix(client_class_name()) << endl + << "\tinstanceVariableNames: ''\n" + << "\tclassVariableNames: ''\n" + << "\tpoolDictionaries: ''\n" + << "\tcategory: '" << generated_category() << "'!\n\n"; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + string funname = camelcase((*f_iter)->get_name()); + string signature = function_signature(*f_iter); + + st_method(f_, client_class_name(), signature); + f_ << function_types_comment(*f_iter) << endl << indent() << "self send" + << capitalize(signature) << "." << endl; + + if (!(*f_iter)->is_oneway()) { + f_ << indent() << "^ self recv" << capitalize(funname) << " success " << endl; + } + + st_close_method(f_); + + generate_send_method(*f_iter); + if (!(*f_iter)->is_oneway()) { + generate_recv_method(*f_iter); + } + } +} + +/** + * Renders a function signature of the form 'type name(args)' + * + * @param tfunction Function definition + * @return String of rendered function definition + */ +string t_st_generator::function_signature(t_function* tfunction) { + return camelcase(tfunction->get_name()) + capitalize(argument_list(tfunction->get_arglist())); +} + +/** + * Renders a field list + */ +string t_st_generator::argument_list(t_struct* tstruct) { + string result = ""; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + result += " "; + } + string name = camelcase((*f_iter)->get_name()); + result += name + ": " + name; + } + return result; +} + +string t_st_generator::type_name(t_type* ttype) { + string prefix = ""; + t_program* program = ttype->get_program(); + if (program != NULL && program != program_) { + if (!ttype->is_service()) { + prefix = program->get_name() + "_types."; + } + } + + string name = ttype->get_name(); + if (ttype->is_struct() || ttype->is_xception()) { + name = capitalize(ttype->get_name()); + } + + return prefix + name; +} + +/* Convert t_type to Smalltalk type code */ +string t_st_generator::type_to_enum(t_type* type) { + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + return "TType string"; + case t_base_type::TYPE_BOOL: + return "TType bool"; + case t_base_type::TYPE_I8: + return "TType byte"; + case t_base_type::TYPE_I16: + return "TType i16"; + case t_base_type::TYPE_I32: + return "TType i32"; + case t_base_type::TYPE_I64: + return "TType i64"; + case t_base_type::TYPE_DOUBLE: + return "TType double"; + } + } else if (type->is_enum()) { + return "TType i32"; + } else if (type->is_struct() || type->is_xception()) { + return "TType struct"; + } else if (type->is_map()) { + return "TType map"; + } else if (type->is_set()) { + return "TType set"; + } else if (type->is_list()) { + return "TType list"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +THRIFT_REGISTER_GENERATOR(st, "Smalltalk", "") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_swift_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_swift_generator.cc new file mode 100644 index 000000000..eb746c109 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_swift_generator.cc @@ -0,0 +1,3199 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <string> +#include <fstream> +#include <iostream> +#include <vector> +#include <set> + +#include <stdlib.h> +#include <sys/stat.h> +#include <sstream> +#include "thrift/platform.h" +#include "thrift/generate/t_oop_generator.h" + +using std::map; +using std::ostream; +using std::ostringstream; +using std::set; +using std::string; +using std::stringstream; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes + +/** + * Swift 3 code generator. + * + * Designed from the Swift/Cocoa code generator(s) + */ +class t_swift_generator : public t_oop_generator { +public: + t_swift_generator(t_program* program, + const map<string, string>& parsed_options, + const string& option_string) + : t_oop_generator(program) { + (void)option_string; + map<string, string>::const_iterator iter; + + log_unexpected_ = false; + async_clients_ = false; + debug_descriptions_ = false; + no_strict_ = false; + namespaced_ = false; + gen_cocoa_ = false; + promise_kit_ = false; + safe_enums_ = false; + + for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + if( iter->first.compare("log_unexpected") == 0) { + log_unexpected_ = true; + } else if( iter->first.compare("async_clients") == 0) { + async_clients_ = true; + } else if( iter->first.compare("no_strict") == 0) { + no_strict_ = true; + } else if( iter->first.compare("debug_descriptions") == 0) { + debug_descriptions_ = true; + } else if( iter->first.compare("namespaced") == 0) { + namespaced_ = true; + } else if( iter->first.compare("cocoa") == 0) { + gen_cocoa_ = true; + } else if( iter->first.compare("safe_enums") == 0) { + safe_enums_ = true; + } else if( iter->first.compare("promise_kit") == 0) { + if (gen_cocoa_ == false) { + throw "PromiseKit only available with Swift 2.x, use `cocoa` option" + iter->first; + } + promise_kit_ = true; + } else { + throw "unknown option swift:" + iter->first; + } + } + + out_dir_base_ = "gen-swift"; + } + + /** + * Init and close methods + */ + + void init_generator() override; + void close_generator() override; + + void generate_consts(vector<t_const*> consts) override; + + /** + * Program-level generation functions + */ + + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_struct(t_struct* tstruct) override; + void generate_xception(t_struct* txception) override; + void generate_service(t_service* tservice) override; + + + void render_const_value(ostream& out, + t_type* type, + t_const_value* value); + + void generate_swift_struct(ostream& out, + t_struct* tstruct, + bool is_private); + + void generate_swift_struct_init(ostream& out, + t_struct* tstruct, + bool all, + bool is_private); + + void generate_swift_struct_implementation(ostream& out, + t_struct* tstruct, + bool is_result, + bool is_private); + void generate_swift_struct_hashable_extension(ostream& out, + t_struct* tstruct, + bool is_private); + void generate_swift_struct_equatable_extension(ostream& out, + t_struct* tstruct, + bool is_private); + void generate_swift_struct_thrift_extension(ostream& out, + t_struct* tstruct, + bool is_result, + bool is_private); + void generate_swift_struct_reader(ostream& out, t_struct* tstruct, bool is_private); + + + void generate_swift_struct_printable_extension(ostream& out, t_struct* tstruct); + void generate_swift_union_reader(ostream& out, t_struct* tstruct); + + string function_result_helper_struct_type(t_service *tservice, t_function* tfunction); + string function_args_helper_struct_type(t_service* tservice, t_function* tfunction); + void generate_function_helpers(t_service *tservice, t_function* tfunction); + + /** + * Service-level generation functions + */ + + void generate_swift_service_protocol(ostream& out, t_service* tservice); + void generate_swift_service_protocol_async(ostream& out, t_service* tservice); + + void generate_swift_service_client(ostream& out, t_service* tservice); + void generate_swift_service_client_async(ostream& out, t_service* tservice); + + void generate_swift_service_client_send_function_implementation(ostream& out, + t_service* tservice, + t_function* tfunction, + bool needs_protocol); + void generate_swift_service_client_send_function_invocation(ostream& out, t_function* tfunction); + void generate_swift_service_client_send_async_function_invocation(ostream& out, + t_function* tfunction); + void generate_swift_service_client_recv_function_implementation(ostream& out, + t_service* tservice, + t_function* tfunction, + bool needs_protocol); + void generate_swift_service_client_implementation(ostream& out, t_service* tservice); + void generate_swift_service_client_async_implementation(ostream& out, t_service* tservice); + + void generate_swift_service_server(ostream& out, t_service* tservice); + void generate_swift_service_server_implementation(ostream& out, t_service* tservice); + void generate_swift_service_helpers(t_service* tservice); + + /** + * Helper rendering functions + */ + + string swift_imports(); + string swift_thrift_imports(); + string type_name(t_type* ttype, bool is_optional=false, bool is_forced=false); + string base_type_name(t_base_type* tbase); + string declare_property(t_field* tfield, bool is_private); + string function_signature(t_function* tfunction); + string async_function_signature(t_function* tfunction); + + + string argument_list(t_struct* tstruct, string protocol_name, bool is_internal); + string type_to_enum(t_type* ttype, bool qualified=false); + string maybe_escape_identifier(const string& identifier); + void populate_reserved_words(); + /** Swift 3 specific */ + string enum_case_name(t_enum_value* tenum_case, bool declaration); + string enum_const_name(string enum_identifier); + void function_docstring(ostream& out, t_function* tfunction); + void async_function_docstring(ostream& out, t_function* tfunction); + void generate_docstring(ostream& out, string& doc); + + /** Swift 2/Cocoa carryover */ + string promise_function_signature(t_function* tfunction); + string function_name(t_function* tfunction); + void generate_old_swift_struct_writer(ostream& out,t_struct* tstruct, bool is_private); + void generate_old_swift_struct_result_writer(ostream& out, t_struct* tstruct); + + /** Swift 2/Cocoa backwards compatibility*/ + void generate_old_enum(t_enum* tenum); + void generate_old_swift_struct(ostream& out, + t_struct* tstruct, + bool is_private); + void generate_old_swift_service_client_async_implementation(ostream& out, + t_service* tservice); + + static std::string get_real_swift_module(const t_program* program) { + std::string real_module = program->get_namespace("swift"); + if (real_module.empty()) { + return program->get_name(); + } + return real_module; + } +private: + + void block_open(ostream& out) { + out << " {" << endl; + indent_up(); + } + + void block_close(ostream& out, bool end_line=true) { + indent_down(); + indent(out) << "}"; + if (end_line) out << endl; + } + + bool field_is_optional(t_field* tfield) { + bool opt = tfield->get_req() == t_field::T_OPTIONAL; + if (tfield->annotations_.find("swift.nullable") != tfield->annotations_.end() && tfield->get_req() != t_field::T_REQUIRED) { + opt = true; + } + if (gen_cocoa_) { // Backwards compatibility, only if its actually "optional" + opt = tfield->get_req() == t_field::T_OPTIONAL; + } + return opt; + } + + bool struct_has_required_fields(t_struct* tstruct) { + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if (!field_is_optional(*m_iter)) { + return true; + } + } + return false; + } + + bool struct_has_optional_fields(t_struct* tstruct) { + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if (field_is_optional(*m_iter)) { + return true; + } + } + return false; + } + + string constants_declarations_; + + /** + * File streams + */ + + ofstream_with_content_based_conditional_update f_decl_; + ofstream_with_content_based_conditional_update f_impl_; + + bool log_unexpected_; + bool async_clients_; + + bool debug_descriptions_; + bool no_strict_; + bool namespaced_; + bool safe_enums_; + set<string> swift_reserved_words_; + + /** Swift 2/Cocoa compatibility */ + bool gen_cocoa_; + bool promise_kit_; + +}; + +/** + * Prepares for file generation by opening up the necessary file output + * streams. + */ +void t_swift_generator::init_generator() { + // Make output directory + string module = get_real_swift_module(program_); + string out_dir = get_out_dir(); + string module_path = out_dir; + string name = program_name_; + if (namespaced_ && !module.empty()) { + module_path = module_path + "/" + module; + name = module; + } + MKDIR(module_path.c_str()); + + populate_reserved_words(); + + // we have a .swift declarations file... + string f_decl_name = name + ".swift"; + string f_decl_fullname = module_path + "/" + f_decl_name; + f_decl_.open(f_decl_fullname.c_str()); + + f_decl_ << autogen_comment() << endl; + + f_decl_ << swift_imports() << swift_thrift_imports() << endl; + + // ...and a .swift implementation extensions file + string f_impl_name = name + "+Exts.swift"; + string f_impl_fullname = module_path + "/" + f_impl_name; + f_impl_.open(f_impl_fullname.c_str()); + + f_impl_ << autogen_comment() << endl; + + f_impl_ << swift_imports() << swift_thrift_imports() << endl; + +} + +/** + * Prints standard Cocoa imports + * + * @return List of imports for Cocoa libraries + */ +string t_swift_generator::swift_imports() { + + vector<string> includes_list; + includes_list.emplace_back("Foundation"); + + ostringstream includes; + + vector<string>::const_iterator i_iter; + for (i_iter=includes_list.begin(); i_iter!=includes_list.end(); ++i_iter) { + includes << "import " << *i_iter << endl; + } + + if (namespaced_) { + const vector<t_program*>& program_includes = program_->get_includes(); + for (auto program_include : program_includes) { + includes << ("import " + get_real_swift_module(program_include)) << endl; + } + } + includes << endl; + + return includes.str(); +} + +/** + * Prints Thrift runtime imports + * + * @return List of imports necessary for Thrift runtime + */ +string t_swift_generator::swift_thrift_imports() { + + vector<string> includes_list; + includes_list.emplace_back("Thrift"); + + if (gen_cocoa_ && promise_kit_) { + includes_list.emplace_back("PromiseKit"); + } + + ostringstream includes; + + vector<string>::const_iterator i_iter; + for (i_iter=includes_list.begin(); i_iter!=includes_list.end(); ++i_iter) { + includes << "import " << *i_iter << endl; + } + + includes << endl; + + return includes.str(); +} + +/** + * Finish up generation. + */ +void t_swift_generator::close_generator() { + // stick our constants declarations at the end of the header file + // since they refer to things we are defining. + f_decl_ << constants_declarations_ << endl; +} + +/** + * Generates a typedef. This is just a simple 1-liner in Swift + * + * @param ttypedef The type definition + */ +void t_swift_generator::generate_typedef(t_typedef* ttypedef) { + f_decl_ << indent() << "public typealias " << ttypedef->get_symbolic() + << " = " << type_name(ttypedef->get_type()) << endl; + f_decl_ << endl; +} + + +/** + * Generates code for an enumerated type. In Swift, this is + * essentially the same as the thrift definition itself, using + * Swift syntax. Conforms to TEnum which + * implementes read/write. + * + * @param tenum The enumeration + */ +void t_swift_generator::generate_enum(t_enum* tenum) { + if (gen_cocoa_) { + generate_old_enum(tenum); + return; + } + f_decl_ << indent() << "public enum " << tenum->get_name() << " : TEnum"; + block_open(f_decl_); + + vector<t_enum_value*> constants = tenum->get_constants(); + vector<t_enum_value*>::iterator c_iter; + + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + f_decl_ << indent() << "case " << enum_case_name((*c_iter), true) << endl; + } + + // unknown associated value case for safety and similar behavior to other languages + if (safe_enums_) { + f_decl_ << indent() << "case unknown(Int32)" << endl; + } + f_decl_ << endl; + + // TSerializable read(from:) + f_decl_ << indent() << "public static func read(from proto: TProtocol) throws -> " + << tenum->get_name(); + block_open(f_decl_); + f_decl_ << indent() << "let raw: Int32 = try proto.read()" << endl; + f_decl_ << indent() << "let new = " << tenum->get_name() << "(rawValue: raw)" << endl; + + f_decl_ << indent() << "if let unwrapped = new {" << endl; + indent_up(); + f_decl_ << indent() << "return unwrapped" << endl; + indent_down(); + f_decl_ << indent() << "} else {" << endl; + indent_up(); + f_decl_ << indent() << "throw TProtocolError(error: .invalidData," << endl; + f_decl_ << indent() << " message: \"Invalid enum value (\\(raw)) for \\(" + << tenum->get_name() << ".self)\")" << endl; + indent_down(); + f_decl_ << indent() << "}" << endl; + block_close(f_decl_); + + // empty init for TSerializable + f_decl_ << endl; + f_decl_ << indent() << "public init()"; + block_open(f_decl_); + + f_decl_ << indent() << "self = ." << enum_case_name(constants.front(), false) << endl; + block_close(f_decl_); + f_decl_ << endl; + + // rawValue getter + f_decl_ << indent() << "public var rawValue: Int32"; + block_open(f_decl_); + f_decl_ << indent() << "switch self {" << endl; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + f_decl_ << indent() << "case ." << enum_case_name((*c_iter), true) + << ": return " << (*c_iter)->get_value() << endl; + } + if (safe_enums_) { + f_decl_ << indent() << "case .unknown(let value): return value" << endl; + } + f_decl_ << indent() << "}" << endl; + block_close(f_decl_); + f_decl_ << endl; + + // convenience rawValue initalizer + f_decl_ << indent() << "public init?(rawValue: Int32)"; + block_open(f_decl_); + f_decl_ << indent() << "switch rawValue {" << endl;; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + f_decl_ << indent() << "case " << (*c_iter)->get_value() + << ": self = ." << enum_case_name((*c_iter), true) << endl; + } + if (!safe_enums_) { + f_decl_ << indent() << "default: return nil" << endl; + } else { + f_decl_ << indent() << "default: self = .unknown(rawValue)" << endl; + } + f_decl_ << indent() << "}" << endl; + block_close(f_decl_); + + + + + block_close(f_decl_); + f_decl_ << endl; +} + +/** + * Generates code for an enumerated type. This is for Swift 2.x/Cocoa + * backwards compatibility + * + * @param tenum The enumeration + */ +void t_swift_generator::generate_old_enum(t_enum* tenum) { + f_decl_ << indent() << "public enum " << tenum->get_name() << " : Int32"; + block_open(f_decl_); + + vector<t_enum_value*> constants = tenum->get_constants(); + vector<t_enum_value*>::iterator c_iter; + + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + f_decl_ << indent() << "case " << (*c_iter)->get_name() + << " = " << (*c_iter)->get_value() << endl; + } + + f_decl_ << endl; + f_decl_ << indent() << "public init() { self.init(rawValue: " << constants.front()->get_value() << ")! }" << endl; + + block_close(f_decl_); + f_decl_ << endl; + + f_impl_ << indent() << "extension " << tenum->get_name() << " : TEnum"; + block_open(f_impl_); + + f_impl_ << endl; + + f_impl_ << indent() << "public static func readValueFromProtocol(proto: TProtocol) throws -> " << tenum->get_name(); + block_open(f_impl_); + f_impl_ << indent() << "var raw = Int32()" << endl + << indent() << "try proto.readI32(&raw)" << endl + << indent() << "return " << tenum->get_name() << "(rawValue: raw)!" << endl; + block_close(f_impl_); + f_impl_ << endl; + + f_impl_ << indent() << "public static func writeValue(value: " << tenum->get_name() << ", toProtocol proto: TProtocol) throws"; + block_open(f_impl_); + f_impl_ << indent() << "try proto.writeI32(value.rawValue)" << endl; + block_close(f_impl_); + f_impl_ << endl; + + block_close(f_impl_); + f_impl_ << endl; +} + +string t_swift_generator::enum_case_name(t_enum_value* tenum_case, bool declaration) { + string name = tenum_case->get_name(); + // force to lowercase for Swift style, maybe escape if its a keyword + std::transform(name.begin(), name.end(), name.begin(), ::tolower); + if (declaration) { + name = maybe_escape_identifier(name); + } + return name; +} + +/** + * Renders a constant enum value by transforming the value portion to lowercase + * for Swift style. + */ +string t_swift_generator::enum_const_name(string enum_identifier) { + string::iterator it; + for (it = enum_identifier.begin(); it < enum_identifier.end(); ++it) { + if ((*it) == '.') { + break; + } + } + std::transform(it, enum_identifier.end(), it, ::tolower); + return enum_identifier; +} + +/** + * Generates public constants for all Thrift constants. + * + * @param consts Constants to generate + */ +void t_swift_generator::generate_consts(vector<t_const*> consts) { + + ostringstream const_interface; + + // Public constants for base types & strings + vector<t_const*>::iterator c_iter; + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + t_type* type = (*c_iter)->get_type(); + const_interface << "public let " << capitalize((*c_iter)->get_name()) << " : " << type_name(type) << " = "; + render_const_value(const_interface, type, (*c_iter)->get_value()); + const_interface << endl << endl; + } + + // this gets spit into the header file in ::close_generator + constants_declarations_ = const_interface.str(); + +} + +/** + * Generates a struct definition for a thrift data type. This is a struct + * with public members. Optional types are used for optional properties to + * allow them to be tested for availability. Separate inits are included for + * required properties & all properties. + * + * Generates extensions to provide conformance to TStruct, TSerializable, + * Hashable & Equatable + * + * @param tstruct The struct definition + */ +void t_swift_generator::generate_struct(t_struct* tstruct) { + generate_swift_struct(f_decl_, tstruct, false); + generate_swift_struct_implementation(f_impl_, tstruct, false, false); +} + +/** + * Exceptions are structs, but they conform to Error + * + * @param tstruct The struct definition + */ +void t_swift_generator::generate_xception(t_struct* txception) { + generate_swift_struct(f_decl_, txception, false); + generate_swift_struct_implementation(f_impl_, txception, false, false); +} + +void t_swift_generator::generate_docstring(ostream& out, string& doc) { + if (doc != "") { + std::vector<std::string> strings; + + std::string::size_type pos = 0; + std::string::size_type prev = 0; + while (((pos = doc.find("\n", prev)) != std::string::npos) + || ((pos = doc.find("\r", prev)) != std::string::npos) + || ((pos = doc.find("\r\n", prev)) != std::string::npos)) + { + strings.push_back(doc.substr(prev, pos - prev)); + prev = pos + 1; + } + + // To get the last substring (or only, if delimiter is not found) + strings.push_back(doc.substr(prev)); + + vector<string>::const_iterator d_iter; + for (d_iter = strings.begin(); d_iter != strings.end(); ++d_iter) { + if ((*d_iter) != "") { + out << indent() << "/// " << (*d_iter) << endl; + } + } + } +} + + + +/** + * Generate the interface for a struct. Only properties and + * init methods are included. + * + * @param tstruct The struct definition + * @param is_private + * Is the struct public or private + */ +void t_swift_generator::generate_swift_struct(ostream& out, + t_struct* tstruct, + bool is_private) { + + if (gen_cocoa_) { + generate_old_swift_struct(out, tstruct, is_private); + return; + } + string doc = tstruct->get_doc(); + generate_docstring(out, doc); + + + // properties + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + + if (tstruct->is_union()) { + // special, unions + out << indent() << "public enum " << tstruct->get_name(); + block_open(out); + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + out << endl; + string doc = (*m_iter)->get_doc(); + generate_docstring(out, doc); + out << indent() << "case " + << maybe_escape_identifier((*m_iter)->get_name()) << "(val: " + << type_name((*m_iter)->get_type(), false) << ")" << endl; + } + } else { + // Normal structs + + string visibility = is_private ? (gen_cocoa_ ? "private" : "fileprivate") : "public"; + + out << indent() << visibility << " final class " << tstruct->get_name(); + + if (tstruct->is_xception()) { + out << " : Swift.Error"; // Error seems to be a common exception name in thrift + } + + block_open(out); + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + out << endl; + // TODO: Defaults + + string doc = (*m_iter)->get_doc(); + generate_docstring(out, doc); + + out << indent() << declare_property(*m_iter, is_private) << endl; + } + + out << endl; + out << endl; + + if (!struct_has_required_fields(tstruct)) { + indent(out) << visibility << " init() { }" << endl; + } + if (struct_has_required_fields(tstruct)) { + generate_swift_struct_init(out, tstruct, false, is_private); + } + if (struct_has_optional_fields(tstruct)) { + generate_swift_struct_init(out, tstruct, true, is_private); + } + } + + block_close(out); + + out << endl; +} + +/** + * Legacy Swift2/Cocoa generator + * + * @param tstruct + * @param is_private + */ + + +void t_swift_generator::generate_old_swift_struct(ostream& out, + t_struct* tstruct, + bool is_private) { + string visibility = is_private ? "private" : "public"; + + out << indent() << visibility << " final class " << tstruct->get_name(); + + if (tstruct->is_xception()) { + out << " : ErrorType"; + } + + block_open(out); + + // properties + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + out << endl; + out << indent() << declare_property(*m_iter, is_private) << endl; + } + + out << endl; + + // init + + indent(out) << visibility << " init()"; + block_open(out); + block_close(out); + + out << endl; + + if (struct_has_required_fields(tstruct)) { + generate_swift_struct_init(out, tstruct, false, is_private); + } + if (struct_has_optional_fields(tstruct)) { + generate_swift_struct_init(out, tstruct, true, is_private); + } + + block_close(out); + + out << endl; +} + +/** + * Generate struct init for properties + * + * @param tstruct The structure definition + * @param all Generate init with all or just required properties + * @param is_private + * Is the initializer public or private + */ +void t_swift_generator::generate_swift_struct_init(ostream& out, + t_struct* tstruct, + bool all, + bool is_private) { + + string visibility = is_private ? (gen_cocoa_ ? "private" : "fileprivate") : "public"; + + indent(out) << visibility << " init("; + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + bool first=true; + for (m_iter = members.begin(); m_iter != members.end();) { + if (all || !field_is_optional(*m_iter)) { + if (first) { + first = false; + } + else { + out << ", "; + } + out << (*m_iter)->get_name() << ": " + << maybe_escape_identifier(type_name((*m_iter)->get_type(), field_is_optional(*m_iter))); + } + ++m_iter; + } + out << ")"; + + block_open(out); + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if (!gen_cocoa_) { + bool should_set = all; + should_set = should_set || !field_is_optional((*m_iter)); + if (should_set) { + out << indent() << "self." << maybe_escape_identifier((*m_iter)->get_name()) << " = " + << maybe_escape_identifier((*m_iter)->get_name()) << endl; + } + } else { + /** legacy Swift2/Cocoa */ + if (all || (*m_iter)->get_req() == t_field::T_REQUIRED || (*m_iter)->get_req() == t_field::T_OPT_IN_REQ_OUT) { + out << indent() << "self." << maybe_escape_identifier((*m_iter)->get_name()) << " = " + << maybe_escape_identifier((*m_iter)->get_name()) << endl; + } + } + } + + block_close(out); + + out << endl; +} + +/** + * Generate the hashable protocol implmentation + * + * @param tstruct The structure definition + * @param is_private + * Is the struct public or private + */ +void t_swift_generator::generate_swift_struct_hashable_extension(ostream& out, + t_struct* tstruct, + bool is_private) { + + string visibility = is_private ? (gen_cocoa_ ? "private" : "fileprivate") : "public"; + indent(out) << "extension " << tstruct->get_name() << " : Hashable"; + block_open(out); + out << endl; + indent(out) << visibility << " var hashValue : Int"; + block_open(out); + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + if (!members.empty()) { + indent(out) << "let prime = 31" << endl; + indent(out) << "var result = 1" << endl; + if (!tstruct->is_union()) { + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_field* tfield = *m_iter; + string accessor = field_is_optional(tfield) ? "?." : "."; + string defaultor = field_is_optional(tfield) ? " ?? 0" : ""; + indent(out) << "result = prime &* result &+ (" << maybe_escape_identifier(tfield->get_name()) << accessor + << "hashValue" << defaultor << ")" << endl; + } + } else { + indent(out) << "switch self {" << endl; + for (m_iter = members.begin(); m_iter != members.end(); m_iter++) { + t_field *tfield = *m_iter; + indent(out) << "case ." << tfield->get_name() << "(let val): result = prime &* val.hashValue" << endl; + } + indent(out) << "}" << endl << endl; + } + indent(out) << "return result" << endl; + } + else { + indent(out) << "return 31" << endl; + } + + block_close(out); + out << endl; + block_close(out); + out << endl; +} + +/** + * Generate the equatable protocol implementation + * + * @param tstruct The structure definition + * @param is_private + * Is the struct public or private + */ +void t_swift_generator::generate_swift_struct_equatable_extension(ostream& out, + t_struct* tstruct, + bool is_private) { + + string visibility = is_private ? (gen_cocoa_ ? "private" : "fileprivate") : "public"; + + indent(out) << visibility << " func ==(lhs: " << type_name(tstruct) << ", rhs: " + << type_name(tstruct) << ") -> Bool"; + block_open(out); + indent(out) << "return"; + + const vector<t_field*>& members = tstruct->get_members(); + vector<t_field*>::const_iterator m_iter; + + if (members.size()) { + if (!tstruct->is_union()) { + out << endl; + indent_up(); + + for (m_iter = members.begin(); m_iter != members.end();) { + t_field* tfield = *m_iter; + indent(out) << "(lhs." << maybe_escape_identifier(tfield->get_name()) + << (gen_cocoa_ ? " ?" : " ") << "== rhs." << maybe_escape_identifier(tfield->get_name()) << ")"; // swift 2 ?== operator not in 3? + if (++m_iter != members.end()) { + out << " &&"; + } + out << endl; + } + indent_down(); + } else { + block_open(out); + indent(out) << "switch (lhs, rhs) {" << endl; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_field* tfield = *m_iter; + indent(out) << "case (." << tfield->get_name() << "(let lval), ." + << tfield->get_name() << "(let rval)): return lval == rval" + << endl; + } + indent(out) << "default: return false" << endl; + indent(out) << "}" << endl; + indent_down(); + indent(out) << "}()" << endl; + } + } + else { + out << " true" << endl; + } + + block_close(out); + out << endl; +} + +/** + * Generate struct implementation. Produces extensions that + * fulfill the requisite protocols to complete the value. + * + * @param tstruct The struct definition + * @param is_result + * If this is a result it needs a different writer + * @param is_private + * Is the struct public or private + */ +void t_swift_generator::generate_swift_struct_implementation(ostream& out, + t_struct* tstruct, + bool is_result, + bool is_private) { + + generate_swift_struct_equatable_extension(out, tstruct, is_private); + + if (!is_private && !is_result && !gen_cocoa_) { // old compiler didn't use debug_descriptions, OR it with gen_cocoa_ so the flag doesn't matter w/ cocoa + generate_swift_struct_printable_extension(out, tstruct); + } + + generate_swift_struct_hashable_extension(out, tstruct, is_private); + generate_swift_struct_thrift_extension(out, tstruct, is_result, is_private); + + out << endl << endl; +} + +/** + * Generate the TStruct protocol implementation. + * + * @param tstruct The structure definition + * @param is_result + * Is the struct a result value + * @param is_private + * Is the struct public or private + */ +void t_swift_generator::generate_swift_struct_thrift_extension(ostream& out, + t_struct* tstruct, + bool is_result, + bool is_private) { + + indent(out) << "extension " << tstruct->get_name() << " : TStruct"; + + block_open(out); + + out << endl; + if (!gen_cocoa_) { + /** Swift 3, no writer we just write field ID's */ + string access = (is_private) ? (gen_cocoa_ ? "private" : "fileprivate") : "public"; + // generate fieldID's dictionary + out << indent() << access << " static var fieldIds: [String: Int32]"; + block_open(out); + out << indent() << "return ["; + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + bool wrote = false; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + wrote = true; + out << "\"" << (*f_iter)->get_name() << "\": " << (*f_iter)->get_key() << ", "; + } + if (!wrote) { + // pad a colon + out << ":"; + } + out << "]" << endl; + block_close(out); + out << endl; + out << indent() << access << " static var structName: String { return \"" + << tstruct->get_name() << "\" }" << endl << endl; + + if (tstruct->is_union()) { + generate_swift_union_reader(out, tstruct); + } else { + generate_swift_struct_reader(out, tstruct, is_private); + } + } else { + /** Legacy Swift2/Cocoa */ + + generate_swift_struct_reader(out, tstruct, is_private); + + if (is_result) { + generate_old_swift_struct_result_writer(out, tstruct); + } + else { + generate_old_swift_struct_writer(out, tstruct, is_private); + } + } + + block_close(out); + out << endl; +} + +void t_swift_generator::generate_swift_union_reader(ostream& out, t_struct* tstruct) { + indent(out) << "public static func read(from proto: TProtocol) throws -> " + << tstruct->get_name(); + block_open(out); + indent(out) << "_ = try proto.readStructBegin()" << endl; + + indent(out) << "var ret: " << tstruct->get_name() << "?"; + out << endl; + indent(out) << "fields: while true"; + block_open(out); + out << endl; + indent(out) << "let (_, fieldType, fieldID) = try proto.readFieldBegin()" << endl << endl; + indent(out) << "switch (fieldID, fieldType)"; + block_open(out); + indent(out) << "case (_, .stop): break fields" << endl; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + indent(out) << "case (" << (*f_iter)->get_key() << ", " << type_to_enum((*f_iter)->get_type()) << "):"; + string padding = ""; + + t_type* type = get_true_type((*f_iter)->get_type()); + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + case t_base_type::TYPE_DOUBLE: + padding = " "; + break; + + case t_base_type::TYPE_BOOL: + case t_base_type::TYPE_I8: + padding = " "; + break; + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + padding = " "; + break; + default: break; + } + } else if (type->is_enum() || type->is_set() || type->is_map()) { + padding = " "; + } else if (type->is_struct() || type->is_xception()) { + padding = " "; + } else if (type->is_list()) { + padding = " "; + } + + indent(out) << padding << "ret = " << tstruct->get_name() << "." + << (*f_iter)->get_name() << "(val: " << "try " + << type_name((*f_iter)->get_type(), false, false) + << ".read(from: proto))" << endl; + } + + indent(out) << "case let (_, unknownType): try proto.skip(type: unknownType)" << endl; + + block_close(out); + indent(out) << "try proto.readFieldEnd()" << endl; + + block_close(out); + out << endl; + + indent(out) << "try proto.readStructEnd()" << endl; + + indent(out) << "if let ret = ret"; + block_open(out); + indent(out) << "return ret" << endl; + block_close(out); + out << endl; + indent(out) << "throw TProtocolError(error: .unknown, message: \"Missing required value for type: " + << tstruct->get_name() << "\")"; + block_close(out); + out << endl; + +} + +/** + * Generates a function to read a struct from + * from a protocol. (TStruct compliance) + * + * @param tstruct The structure definition + * @param is_private + * Is the struct public or private + */ +void t_swift_generator::generate_swift_struct_reader(ostream& out, + t_struct* tstruct, + bool is_private) { + + if (!gen_cocoa_) { + /** Swift 3 case */ + string visibility = is_private ? "fileprivate" : "public"; + + indent(out) << visibility << " static func read(from proto: TProtocol) throws -> " + << tstruct->get_name(); + + block_open(out); + indent(out) << "_ = try proto.readStructBegin()" << endl; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + bool optional = field_is_optional(*f_iter); + indent(out) << "var " << maybe_escape_identifier((*f_iter)->get_name()) << ": " + << type_name((*f_iter)->get_type(), optional, !optional) << endl; + } + + out << endl; + + // Loop over reading in fields + indent(out) << "fields: while true"; + block_open(out); + out << endl; + + indent(out) << "let (_, fieldType, fieldID) = try proto.readFieldBegin()" << endl << endl; + indent(out) << "switch (fieldID, fieldType)"; + block_open(out); + indent(out) << "case (_, .stop): break fields" << endl; + + + // Generate deserialization code for known cases + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + indent(out) << "case (" << (*f_iter)->get_key() << ", " << type_to_enum((*f_iter)->get_type()) << "):"; + string padding = ""; + + t_type* type = get_true_type((*f_iter)->get_type()); + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + case t_base_type::TYPE_DOUBLE: + padding = " "; + break; + + case t_base_type::TYPE_BOOL: + case t_base_type::TYPE_I8: + padding = " "; + break; + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + padding = " "; + break; + default: break; + } + } else if (type->is_enum() || type->is_set() || type->is_map()) { + padding = " "; + } else if (type->is_struct() || type->is_xception()) { + padding = " "; + } else if (type->is_list()) { + padding = " "; + } + + out << padding << maybe_escape_identifier((*f_iter)->get_name()) << " = try " + << type_name((*f_iter)->get_type(), false, false) << ".read(from: proto)" << endl; + } + + indent(out) << "case let (_, unknownType): try proto.skip(type: unknownType)" << endl; + block_close(out); + out << endl; + + // Read field end marker + indent(out) << "try proto.readFieldEnd()" << endl; + block_close(out); + out << endl; + indent(out) << "try proto.readStructEnd()" << endl; + + if (struct_has_required_fields(tstruct)) { + // performs various checks (e.g. check that all required fields are set) + indent(out) << "// Required fields" << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (field_is_optional(*f_iter)) { + continue; + } + indent(out) << "try proto.validateValue(" << (*f_iter)->get_name() << ", " + << "named: \"" << (*f_iter)->get_name() << "\")" << endl; + } + } + + out << endl; + + indent(out) << "return " << tstruct->get_name() << "("; + for (f_iter = fields.begin(); f_iter != fields.end();) { + out << (*f_iter)->get_name() << ": " << maybe_escape_identifier((*f_iter)->get_name()); + if (++f_iter != fields.end()) { + out << ", "; + } + } + + } else { + /** Legacy Swif2/Cocoa case */ + string visibility = is_private ? "private" : "public"; + + indent(out) << visibility << " static func readValueFromProtocol(__proto: TProtocol) throws -> " + << tstruct->get_name(); + + block_open(out); + out << endl; + indent(out) << "try __proto.readStructBegin()" << endl << endl; + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + bool optional = field_is_optional(*f_iter); + indent(out) << "var " << maybe_escape_identifier((*f_iter)->get_name()) << " : " + << type_name((*f_iter)->get_type(), optional, !optional) << endl; + } + + out << endl; + + // Loop over reading in fields + indent(out) << "fields: while true"; + block_open(out); + out << endl; + + indent(out) << "let (_, fieldType, fieldID) = try __proto.readFieldBegin()" << endl << endl; + indent(out) << "switch (fieldID, fieldType)"; + + block_open(out); + + indent(out) << "case (_, .STOP):" << endl; + indent_up(); + indent(out) << "break fields" << endl << endl; + indent_down(); + + // Generate deserialization code for known cases + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + + indent(out) << "case (" << (*f_iter)->get_key() << ", " << type_to_enum((*f_iter)->get_type()) << "):" << endl; + indent_up(); + indent(out) << maybe_escape_identifier((*f_iter)->get_name()) << " = try __proto.readValue() as " + << type_name((*f_iter)->get_type()) << endl << endl; + indent_down(); + + } + + indent(out) << "case let (_, unknownType):" << endl; + indent_up(); + indent(out) << "try __proto.skipType(unknownType)" << endl; + indent_down(); + block_close(out); + out << endl; + + // Read field end marker + indent(out) << "try __proto.readFieldEnd()" << endl; + + block_close(out); + out << endl; + indent(out) << "try __proto.readStructEnd()" << endl; + out << endl; + + if (struct_has_required_fields(tstruct)) { + // performs various checks (e.g. check that all required fields are set) + indent(out) << "// Required fields" << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (field_is_optional(*f_iter)) { + continue; + } + indent(out) << "try __proto.validateValue(" << (*f_iter)->get_name() << ", " + << "named: \"" << (*f_iter)->get_name() << "\")" << endl; + } + } + + out << endl; + + indent(out) << "return " << tstruct->get_name() << "("; + for (f_iter = fields.begin(); f_iter != fields.end();) { + out << (*f_iter)->get_name() << ": " << maybe_escape_identifier((*f_iter)->get_name()); + if (++f_iter != fields.end()) { + out << ", "; + } + } + } + out << ")" << endl; + + block_close(out); + + out << endl; +} + +/** + * Generates a function to write a struct to + * a protocol. (TStruct compliance) ONLY FOR SWIFT2/COCOA + * + * @param tstruct The structure definition + * @param is_private + * Is the struct public or private + */ +void t_swift_generator::generate_old_swift_struct_writer(ostream& out, + t_struct* tstruct, + bool is_private) { + + string visibility = is_private ? "private" : "public"; + + indent(out) << visibility << " static func writeValue(__value: " << tstruct->get_name() + << ", toProtocol __proto: TProtocol) throws"; + block_open(out); + out << endl; + + string name = tstruct->get_name(); + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + indent(out) << "try __proto.writeStructBeginWithName(\"" << name << "\")" << endl; + out << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field *tfield = *f_iter; + + bool optional = field_is_optional(tfield); + if (optional) { + indent(out) << "if let " << maybe_escape_identifier(tfield->get_name()) + << " = __value." << maybe_escape_identifier(tfield->get_name()); + block_open(out); + } + + indent(out) << "try __proto.writeFieldValue(" + << (optional ? "" : "__value.") << maybe_escape_identifier(tfield->get_name()) << ", " + << "name: \"" << tfield->get_name() << "\", " + << "type: " << type_to_enum(tfield->get_type()) << ", " + << "id: " << tfield->get_key() << ")" << endl; + + if (optional) { + block_close(out); + } + + out << endl; + } + + indent(out) << "try __proto.writeFieldStop()" << endl << endl; + indent(out) << "try __proto.writeStructEnd()" << endl; + block_close(out); + out << endl; +} + +/** + * Generates a function to read a struct from + * from a protocol. (TStruct compliance) ONLY FOR SWIFT 2/COCOA + * + * This is specifically a function result. Only + * the first available field is written. + * + * @param tstruct The structure definition + */ +void t_swift_generator::generate_old_swift_struct_result_writer(ostream& out, t_struct* tstruct) { + + indent(out) << "private static func writeValue(__value: " << tstruct->get_name() + << ", toProtocol __proto: TProtocol) throws"; + block_open(out); + out << endl; + string name = tstruct->get_name(); + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + indent(out) << "try __proto.writeStructBeginWithName(\"" << name << "\")" << endl; + out << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field *tfield = *f_iter; + + indent(out) << "if let result = __value." << (*f_iter)->get_name(); + + block_open(out); + + indent(out) << "try __proto.writeFieldValue(result, " + << "name: \"" << tfield->get_name() << "\", " + << "type: " << type_to_enum(tfield->get_type()) << ", " + << "id: " << tfield->get_key() << ")" << endl; + + block_close(out); + } + // Write the struct map + indent(out) << "try __proto.writeFieldStop()" << endl << endl; + indent(out) << "try __proto.writeStructEnd()" << endl; + block_close(out); + out << endl; +} + +/** + * Generates a description method for the given struct + * + * @param tstruct The struct definition + */ +void t_swift_generator::generate_swift_struct_printable_extension(ostream& out, t_struct* tstruct) { + + // Allow use of debugDescription so the app can add description via a cateogory/extension + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + indent(out) << "extension " << tstruct->get_name() << " : " + << (debug_descriptions_ ? "CustomDebugStringConvertible" : "CustomStringConvertible"); + + block_open(out); + out << endl; + indent(out) << "public var description : String"; + block_open(out); + indent(out) << "var desc = \"" << tstruct->get_name(); + + if (!gen_cocoa_) { + if (!tstruct->is_union()) { + out << "(\"" << endl; + for (f_iter = fields.begin(); f_iter != fields.end();) { + indent(out) << "desc += \"" << (*f_iter)->get_name() + << "=\\(String(describing: self." << maybe_escape_identifier((*f_iter)->get_name()) << "))"; + if (++f_iter != fields.end()) { + out << ", "; + } + out << "\"" << endl; + } + } else { + out << ".\"" << endl; + indent(out) << "switch self {" << endl; + for (f_iter = fields.begin(); f_iter != fields.end();f_iter++) { + indent(out) << "case ." << (*f_iter)->get_name() << "(let val): " + << "desc += \"" << (*f_iter)->get_name() << "(val: \\(val))\"" + << endl; + } + indent(out) << "}" << endl; + } + } else { + out << "(\"" << endl; + for (f_iter = fields.begin(); f_iter != fields.end();) { + indent(out) << "desc += \"" << (*f_iter)->get_name() + << "=\\(self." << maybe_escape_identifier((*f_iter)->get_name()) << ")"; + if (++f_iter != fields.end()) { + out << ", "; + } + out << "\"" << endl; + } + indent(out) << "desc += \")\"" << endl; + } + + indent(out) << "return desc" << endl; + block_close(out); + out << endl; + block_close(out); + out << endl; +} + +/** + * Generates a thrift service. In Swift this consists of a + * protocol definition and a client (with it's implementation + * separated into exts file). + * + * @param tservice The service definition + */ +void t_swift_generator::generate_service(t_service* tservice) { + + generate_swift_service_protocol(f_decl_, tservice); + generate_swift_service_client(f_decl_, tservice); + if (async_clients_) { + generate_swift_service_protocol_async(f_decl_, tservice); + generate_swift_service_client_async(f_decl_, tservice); + } + generate_swift_service_server(f_decl_, tservice); + + generate_swift_service_helpers(tservice); + + generate_swift_service_client_implementation(f_impl_, tservice); + if (async_clients_) { + generate_swift_service_client_async_implementation(f_impl_, tservice); + } + generate_swift_service_server_implementation(f_impl_, tservice); +} + +/** + * Generates structs for all the service return types + * + * @param tservice The service + */ +void t_swift_generator::generate_swift_service_helpers(t_service* tservice) { + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + + t_struct* ts = (*f_iter)->get_arglist(); + + string qname = function_args_helper_struct_type(tservice, *f_iter); + + t_struct qname_ts = t_struct(ts->get_program(), qname); + + const vector<t_field*>& members = ts->get_members(); + vector<t_field*>::const_iterator m_iter; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + qname_ts.append(*m_iter); + } + + generate_swift_struct(f_impl_, &qname_ts, true); + generate_swift_struct_implementation(f_impl_, &qname_ts, false, true); + generate_function_helpers(tservice, *f_iter); + } +} + +string t_swift_generator::function_result_helper_struct_type(t_service *tservice, t_function* tfunction) { + if (tfunction->is_oneway()) { + return tservice->get_name() + "_" + tfunction->get_name(); + } else { + return tservice->get_name() + "_" + tfunction->get_name() + "_result"; + } +} + +string t_swift_generator::function_args_helper_struct_type(t_service *tservice, t_function* tfunction) { + return tservice->get_name() + "_" + tfunction->get_name() + "_args"; +} + +/** + * Generates a struct and helpers for a function. + * + * @param tfunction The function + */ +void t_swift_generator::generate_function_helpers(t_service *tservice, t_function* tfunction) { + if (tfunction->is_oneway()) { + return; + } + + // create a result struct with a success field of the return type, + // and a field for each type of exception thrown + t_struct result(program_, function_result_helper_struct_type(tservice, tfunction)); + if (!tfunction->get_returntype()->is_void()) { + t_field* success = new t_field(tfunction->get_returntype(), "success", 0); + success->set_req(t_field::T_OPTIONAL); + result.append(success); + } + + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& fields = xs->get_members(); + vector<t_field*>::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_field *x = *f_iter; + t_field *ox = new t_field(x->get_type(), x->get_name(), x->get_key()); + ox->set_req(t_field::T_OPTIONAL); + result.append(ox); + } + + // generate the result struct + generate_swift_struct(f_impl_, &result, true); + generate_swift_struct_implementation(f_impl_, &result, true, true); + + for (f_iter = result.get_members().begin(); f_iter != result.get_members().end(); ++f_iter) { + delete *f_iter; + } +} + +/** + * Generates a service protocol definition. + * + * @param tservice The service to generate a protocol definition for + */ +void t_swift_generator::generate_swift_service_protocol(ostream& out, t_service* tservice) { + if (!gen_cocoa_) { + string doc = tservice->get_doc(); + generate_docstring(out, doc); + + indent(out) << "public protocol " << tservice->get_name(); + t_service* parent = tservice->get_extends(); + if (parent != NULL) { + out << " : " << parent->get_name(); + } + block_open(out); + out << endl; + + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + function_docstring(out, *f_iter); + indent(out) << function_signature(*f_iter) << endl << endl; + } + + } else { + indent(out) << "public protocol " << tservice->get_name(); + block_open(out); + + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + out << endl; + indent(out) << function_signature(*f_iter) << " // exceptions: "; + t_struct* xs = (*f_iter)->get_xceptions(); + const vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + out << type_name((*x_iter)->get_type()) + ", "; + } + out << endl; + } + } + block_close(out); + out << endl; +} + +/** + * Generates an asynchronous service protocol definition. + * + * @param tservice The service to generate a protocol definition for + */ +void t_swift_generator::generate_swift_service_protocol_async(ostream& out, t_service* tservice) { + if (!gen_cocoa_) { + string doc = tservice->get_doc(); + generate_docstring(out, doc); + } + indent(out) << "public protocol " << tservice->get_name() << "Async"; + + block_open(out); + if (!gen_cocoa_) { out << endl; } + + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + + if (!gen_cocoa_) { + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + async_function_docstring(out, *f_iter); + indent(out) << async_function_signature(*f_iter) << endl << endl; + } + } else { + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + out << endl; + indent(out) << async_function_signature(*f_iter) << endl; + if (promise_kit_) { + indent(out) << promise_function_signature(*f_iter) << endl; + } // + out << endl; + } + } + block_close(out); + out << endl; +} + +/** + * Generates a service client interface definition. + * + * @param tservice The service to generate a client interface definition for + */ +void t_swift_generator::generate_swift_service_client(ostream& out, t_service* tservice) { + if (!gen_cocoa_) { + indent(out) << "open class " << tservice->get_name() << "Client";// : " + + // Inherit from ParentClient + t_service* parent = tservice->get_extends(); + out << " : " << ((parent == NULL) ? "TClient" : parent->get_name() + "Client"); + out << " /* , " << tservice->get_name() << " */"; + block_open(out); + out << endl; + } else { + // a + indent(out) << "public class " << tservice->get_name() << "Client /* : " << tservice->get_name() << " */"; + block_open(out); + out << endl; + + indent(out) << "let __inProtocol : TProtocol" << endl << endl; + indent(out) << "let __outProtocol : TProtocol" << endl << endl; + indent(out) << "public init(inoutProtocol: TProtocol)"; + block_open(out); + + indent(out) << "__inProtocol = inoutProtocol" << endl; + indent(out) << "__outProtocol = inoutProtocol" << endl; + block_close(out); + out << endl; + + indent(out) << "public init(inProtocol: TProtocol, outProtocol: TProtocol)"; + block_open(out); + indent(out) << "__inProtocol = inProtocol" << endl; + indent(out) << "__outProtocol = outProtocol" << endl; + block_close(out); + out << endl; + } + + block_close(out); + out << endl; +} + +/** + * Generates a service client interface definition. + * + * @param tservice The service to generate a client interface definition for + */ +void t_swift_generator::generate_swift_service_client_async(ostream& out, t_service* tservice) { + if (!gen_cocoa_) { + indent(out) << "open class " << tservice->get_name() + << "AsyncClient<Protocol: TProtocol, Factory: TAsyncTransportFactory>";// : " + + // Inherit from ParentClient + t_service* parent = tservice->get_extends(); + + out << " : " << ((parent == NULL) ? "T" : parent->get_name()) + "AsyncClient<Protocol, Factory>"; + out << " /* , " << tservice->get_name() << " */"; + + block_open(out); + out << endl; + } else { + indent(out) << "public class " << tservice->get_name() << "AsyncClient /* : " << tservice->get_name() << " */"; + block_open(out); + out << endl; + + indent(out) << "let __protocolFactory : TProtocolFactory" << endl << endl; + indent(out) << "let __transportFactory : TAsyncTransportFactory" << endl << endl; + indent(out) << "public init(protocolFactory: TProtocolFactory, transportFactory: TAsyncTransportFactory)"; + block_open(out); + + indent(out) << "__protocolFactory = protocolFactory" << endl; + indent(out) << "__transportFactory = transportFactory" << endl; + block_close(out); + out << endl; + } + block_close(out); + out << endl; +} + +/** + * Generates a service server interface definition. In other words, + * the TProcess implementation for the service definition. + * + * @param tservice The service to generate a client interface definition for + */ +void t_swift_generator::generate_swift_service_server(ostream& out, t_service* tservice) { + if (!gen_cocoa_) { + indent(out) << "open class " << tservice->get_name() << "Processor /* " << tservice->get_name() << " */"; + + block_open(out); + out << endl; + out << indent() << "typealias ProcessorHandlerDictionary = " + << "[String: (Int32, TProtocol, TProtocol, " << tservice->get_name() << ") throws -> Void]" << endl + << endl + << indent() << "public var service: " << tservice->get_name() << endl + << endl + << indent() << "public required init(service: " << tservice->get_name() << ")"; + } else { + indent(out) << "public class " << tservice->get_name() << "Processor : NSObject /* " + << tservice->get_name() << " */"; + block_open(out); + out << endl; + + out << indent() << "typealias ProcessorHandlerDictionary = " + << "[String: (Int, TProtocol, TProtocol, " << tservice->get_name() << ") throws -> Void]" << endl + << endl + << indent() << "let service : " << tservice->get_name() << endl + << endl + << indent() << "public init(service: " << tservice->get_name() << ")"; + } + + block_open(out); + indent(out) << "self.service = service" << endl; + block_close(out); + out << endl; + + block_close(out); + out << endl; +} + +/** + * Generates a function that will send the arguments + * for a service function via a protocol. + * + * @param tservice The service to generate + * @param tfunction The function to generate + * @param needs_protocol + * Wether the first parameter must be a protocol or if + * the protocol is to be assumed + */ +void t_swift_generator::generate_swift_service_client_send_function_implementation(ostream& out, + t_service *tservice, + t_function* tfunction, + bool needs_protocol) { + + string funname = tfunction->get_name(); + + t_function send_function(g_type_bool, + "send_" + tfunction->get_name(), + tfunction->get_arglist()); + + string argsname = function_args_helper_struct_type(tservice, tfunction); + t_struct* arg_struct = tfunction->get_arglist(); + + string proto = needs_protocol ? (gen_cocoa_ ? "__outProtocol" : "on outProtocol") : ""; + // Open function + indent(out) << "private func " << send_function.get_name() << "(" + << argument_list(tfunction->get_arglist(), proto, true) + << ") throws"; + block_open(out); + if (!gen_cocoa_) { + // Serialize the request + indent(out) << "try outProtocol.writeMessageBegin(name: \"" << funname << "\", " + << "type: " << (tfunction->is_oneway() ? ".oneway" : ".call") << ", " + << "sequenceID: 0)" << endl; + + indent(out) << "let args = " << argsname << "("; + + // write out function parameters + + const vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + + for (f_iter = fields.begin(); f_iter != fields.end();) { + t_field *tfield = (*f_iter); + out << tfield->get_name() << ": " << tfield->get_name(); + if (++f_iter != fields.end()) { + out << ", "; + } + } + out << ")" << endl; + indent(out) << "try args.write(to: outProtocol)" << endl; + indent(out) << "try outProtocol.writeMessageEnd()" << endl; + } else { + out << endl; + + // Serialize the request + indent(out) << "try __outProtocol.writeMessageBeginWithName(\"" << funname << "\", " + << "type: " << (tfunction->is_oneway() ? ".ONEWAY" : ".CALL") << ", " + << "sequenceID: 0)" << endl; + + out << endl; + + indent(out) << "let __args = " << argsname << "("; + + // write out function parameters + + const vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + + for (f_iter = fields.begin(); f_iter != fields.end();) { + t_field *tfield = (*f_iter); + out << tfield->get_name() << ": " << tfield->get_name(); + if (++f_iter != fields.end()) { + out << ", "; + } + } + out << ")" << endl; + indent(out) << "try " << argsname << ".writeValue(__args, toProtocol: __outProtocol)" << endl << endl; + indent(out) << "try __outProtocol.writeMessageEnd()" << endl; + } + + block_close(out); + out << endl; +} + +/** + * Generates a function that will recv the result for a + * service function via a protocol. + * + * @param tservice The service to generate + * @param tfunction The function to generate + * @param needs_protocol + * Wether the first parameter must be a protocol or if + * the protocol is to be assumed + */ +void t_swift_generator::generate_swift_service_client_recv_function_implementation(ostream& out, + t_service* tservice, + t_function* tfunction, + bool needs_protocol) { + + // Open function + indent(out) << "private func recv_" << tfunction->get_name() << "("; + if (!gen_cocoa_) { + if (needs_protocol) { + out << "on inProtocol: TProtocol"; + } + out << ") throws"; + if (!tfunction->get_returntype()->is_void()) { + out << " -> " << type_name(tfunction->get_returntype()); + } + + block_open(out); + + // check for an exception + + indent(out) << "try inProtocol.readResultMessageBegin() " << endl; + + string resultname = function_result_helper_struct_type(tservice, tfunction); + indent(out); + if (!tfunction->get_returntype()->is_void() || !tfunction->get_xceptions()->get_members().empty()) { + out << "let result = "; + } else { + out << "_ = "; + } + + string return_type_name = type_name(tfunction->get_returntype()); + out << "try " << resultname << ".read(from: inProtocol)" << endl; + + indent(out) << "try inProtocol.readMessageEnd()" << endl << endl; + + // Careful, only return _result if not a void function + if (!tfunction->get_returntype()->is_void()) { + indent(out) << "if let success = result.success"; + block_open(out); + indent(out) << "return success" << endl; + block_close(out); + } + + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + indent(out) << "if let " << (*x_iter)->get_name() << " = result." << (*x_iter)->get_name(); + block_open(out); + indent(out) << "throw " << (*x_iter)->get_name() << endl; + block_close(out); + } + + // If you get here it's an exception, unless a void function + if (!tfunction->get_returntype()->is_void()) { + indent(out) << "throw TApplicationError(error: .missingResult(methodName: \"" + << tfunction->get_name() << "\"))" << endl; + } + } else { + if (needs_protocol) { + out << "__inProtocol: TProtocol"; + } + + out << ") throws"; + + if (!tfunction->get_returntype()->is_void()) { + out << " -> " << type_name(tfunction->get_returntype()); + } + + block_open(out); + + // check for an exception + out << endl; + indent(out) << "try __inProtocol.readResultMessageBegin() " << endl << endl; + string resultname = function_result_helper_struct_type(tservice, tfunction); + indent(out); + if (!tfunction->get_returntype()->is_void() || !tfunction->get_xceptions()->get_members().empty()) { + out << "let __result = "; + } + out << "try " << resultname << ".readValueFromProtocol(__inProtocol)" << endl << endl; + + indent(out) << "try __inProtocol.readMessageEnd()" << endl << endl; + + // Careful, only return _result if not a void function + if (!tfunction->get_returntype()->is_void()) { + indent(out) << "if let __success = __result.success"; + block_open(out); + indent(out) << "return __success" << endl; + block_close(out); + } + + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + indent(out) << "if let " << (*x_iter)->get_name() << " = __result." << (*x_iter)->get_name(); + block_open(out); + indent(out) << "throw " << (*x_iter)->get_name() << endl; + block_close(out); + } + + // If you get here it's an exception, unless a void function + if (!tfunction->get_returntype()->is_void()) { + indent(out) << "throw NSError(" << endl; + indent_up(); + indent(out) << "domain: TApplicationErrorDomain, " << endl; + indent(out) << "code: Int(TApplicationError.MissingResult.rawValue)," << endl; + indent(out) << "userInfo: [TApplicationErrorMethodKey: \"" << tfunction->get_name() << "\"])" << endl; + indent_down(); + } + } + + // Close function + block_close(out); + out << endl; +} + +/** + * Generates an invocation of a given the send function for the + * service function. + * + * @param tfunction The service to generate an implementation for + */ +void t_swift_generator::generate_swift_service_client_send_function_invocation(ostream& out, + t_function* tfunction) { + + indent(out) << "try send_" << tfunction->get_name() << "("; + + t_struct* arg_struct = tfunction->get_arglist(); + + const vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + + for (f_iter = fields.begin(); f_iter != fields.end();) { + out << (*f_iter)->get_name() << ": " << (*f_iter)->get_name(); + if (++f_iter != fields.end()) { + out << ", "; + } + } + + out << ")" << endl; +} + +/** + * Generates an invocation of a given the send function for the + * service function. This is for asynchronous protocols. + * + * @param tfunction The service to generate an implementation for + */ +void t_swift_generator::generate_swift_service_client_send_async_function_invocation(ostream& out, + t_function* tfunction) { + + t_struct* arg_struct = tfunction->get_arglist(); + const vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + + if (!gen_cocoa_) { + indent(out) << "try send_" << tfunction->get_name() << "(on: proto"; + } else { + indent(out) << "try send_" << tfunction->get_name() << "(__protocol"; // + } + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + out << ", " << (*f_iter)->get_name() << ": " << (*f_iter)->get_name(); + } + + out << ")" << endl; +} + +/** + * Generates a service client protocol implementation via extension. + * + * @param tservice The service to generate an implementation for + */ +void t_swift_generator::generate_swift_service_client_implementation(ostream& out, + t_service* tservice) { + + string name = tservice->get_name() + "Client"; + indent(out) << "extension " << name << " : " << tservice->get_name(); + block_open(out); + out << endl; + + // generate client method implementations + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + + generate_swift_service_client_send_function_implementation(out, tservice, *f_iter, false); + + if (!(*f_iter)->is_oneway()) { + generate_swift_service_client_recv_function_implementation(out, tservice, *f_iter, false); + } + + // Open function + indent(out) << "public " << function_signature(*f_iter); + block_open(out); + + if (gen_cocoa_) { out << endl; } + + generate_swift_service_client_send_function_invocation(out, *f_iter); + if (!gen_cocoa_) { + indent(out) << "try outProtocol.transport.flush()" << endl; + } else { + out << endl; + indent(out) << "try __outProtocol.transport().flush()" << endl << endl; + } + + if (!(*f_iter)->is_oneway()) { + if ((*f_iter)->get_returntype()->is_void()) { + indent(out) << "try recv_" << (*f_iter)->get_name() << "()" << endl; + } else { + indent(out) << "return try recv_" << (*f_iter)->get_name() << "()" << endl; + } + } + block_close(out); + out << endl; + } + block_close(out); + out << endl; +} + +/** + * Generates a service asynchronous client protocol implementation via extension. + * + * @param tservice The service to generate an implementation for + */ +void t_swift_generator::generate_swift_service_client_async_implementation(ostream& out, t_service* tservice) { + if (gen_cocoa_) { + generate_old_swift_service_client_async_implementation(out, tservice); + return; + } + string name = tservice->get_name() + "AsyncClient"; + string protocol_name = tservice->get_name() + "Async"; + + indent(out) << "extension " << name << " : " << protocol_name; + block_open(out); + out << endl; + + // generate client method implementations + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + + generate_swift_service_client_send_function_implementation(out, tservice, *f_iter, true); + + if (!(*f_iter)->is_oneway()) { + generate_swift_service_client_recv_function_implementation(out, tservice, *f_iter, true); + } + + indent(out) << "public " << async_function_signature(*f_iter); + block_open(out); + out << endl; + out << indent() << "let transport = factory.newTransport()" << endl + << indent() << "let proto = Protocol(on: transport)" << endl + << endl; + + out << indent() << "do"; + block_open(out); + + generate_swift_service_client_send_async_function_invocation(out, *f_iter); + + indent_down(); + out << indent() << "} catch let error {" << endl; + indent_up(); + out << indent() << "completion(.error(error))" << endl; + block_close(out); + + out << endl; + + bool ret_is_void = (*f_iter)->get_returntype()->is_void(); + bool is_oneway = (*f_iter)->is_oneway(); + + string error_completion_call = "completion(.error(error))"; + indent(out) << "transport.flush"; + block_open(out); + out << indent() << "(trans, error) in" << endl << endl; + out << indent() << "if let error = error"; + block_open(out); + out << indent() << error_completion_call << endl; + block_close(out); + + if (!is_oneway) { + out << indent() << "do"; + block_open(out); + indent(out); + if (!ret_is_void) { + out << "let result = "; + } + out << "try self.recv_" << (*f_iter)->get_name() << "(on: proto)" << endl; + + out << indent() << (ret_is_void ? "completion(.success(Void()))" : "completion(.success(result))") << endl; + indent_down(); + out << indent() << "} catch let error {" << endl; + indent_up(); + out << indent() << error_completion_call << endl; + + block_close(out); + } else { + out << indent() << "completion(.success(Void()))" << endl; + } + block_close(out); + block_close(out); + + } + block_close(out); + out << endl; +} + +void t_swift_generator::generate_old_swift_service_client_async_implementation(ostream& out, + t_service* tservice) { + + string name = tservice->get_name() + "AsyncClient"; + string protocol_name = tservice->get_name() + "Async"; + + indent(out) << "extension " << name << " : " << protocol_name; + block_open(out); + out << endl; + + // generate client method implementations + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + + generate_swift_service_client_send_function_implementation(out, tservice, *f_iter, true); + + if (!(*f_iter)->is_oneway()) { + generate_swift_service_client_recv_function_implementation(out, tservice, *f_iter, true); + } + + indent(out) << "public " << async_function_signature(*f_iter); + block_open(out); + out << endl; + + out << indent() << "let __transport = __transportFactory.newTransport()" << endl + << indent() << "let __protocol = __protocolFactory.newProtocolOnTransport(__transport)" << endl + << endl; + + generate_swift_service_client_send_async_function_invocation(out, *f_iter); + out << endl; + + indent(out) << "__transport.flushWithCompletion("; + + if ((*f_iter)->is_oneway()) { + out << "success, failure: failure)" << endl; + } + else { + block_open(out); + indent(out) << "do"; + block_open(out); + + indent(out); + if (!(*f_iter)->get_returntype()->is_void()) { + out << "let result = "; + } + out << "try self.recv_" << (*f_iter)->get_name() << "(__protocol)" << endl; + + out << indent() << "success("; + if (!(*f_iter)->get_returntype()->is_void()) { + out << "result"; + } + out << ")" << endl; + + block_close(out); + indent(out) << "catch let error"; + block_open(out); + indent(out) << "failure(error as NSError)" << endl; + block_close(out); + block_close(out); + indent(out) << ", failure: failure)" << endl; + } + + block_close(out); + out << endl; + + // Promise function + if (promise_kit_) { + + indent(out) << "public " << promise_function_signature(*f_iter); + block_open(out); + + out << indent() << "let (__promise, __fulfill, __reject) = Promise<" << type_name((*f_iter)->get_returntype()) << ">.pendingPromise()" << endl << endl + << indent() << "let __transport = __transportFactory.newTransport()" << endl + << indent() << "let __protocol = __protocolFactory.newProtocolOnTransport(__transport)" << endl + << endl; + + generate_swift_service_client_send_async_function_invocation(out, *f_iter); + out << endl; + indent(out) << "__transport.flushWithCompletion("; + + if ((*f_iter)->is_oneway()) { + out << "{ __fulfill() }, failure: { __reject($0) })" << endl; + } + else { + block_open(out); + indent(out) << "do"; + block_open(out); + + indent(out); + if (!(*f_iter)->get_returntype()->is_void()) { + out << "let result = "; + } + out << "try self.recv_" << (*f_iter)->get_name() << "(__protocol)" << endl; + + out << indent() << "__fulfill("; + if (!(*f_iter)->get_returntype()->is_void()) { + out << "result"; + } + out << ")" << endl; + + block_close(out); + indent(out) << "catch let error"; + block_open(out); + indent(out) << "__reject(error)" << endl; + block_close(out); + block_close(out); + + indent(out) << ", failure: { error in " << endl; + indent_up(); + indent(out) << "__reject(error)" << endl; + indent_down(); + indent(out) << "})" << endl; + } + + indent(out) << "return __promise" << endl; + block_close(out); + out << endl; + + } + + } + block_close(out); + out << endl; +} + +/** + * Generates a service server implementation. + * + * Implemented by generating a block for each service function that + * handles the processing of that function. The blocks are stored in + * a map and looked up via function/message name. + * + * @param tservice The service to generate an implementation for + */ +void t_swift_generator::generate_swift_service_server_implementation(ostream& out, + t_service* tservice) { + + string name = tservice->get_name() + "Processor"; + + indent(out) << "extension " << name << " : TProcessor"; + block_open(out); + out << endl; + indent(out) << "static let processorHandlers" << (gen_cocoa_ ? " " : "") << ": ProcessorHandlerDictionary ="; + block_open(out); + + out << endl; + out << indent() << "var processorHandlers = ProcessorHandlerDictionary()" << endl << endl; + + // generate method map for routing incoming calls + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::const_iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + + t_function* tfunction = *f_iter; + + string args_type = function_args_helper_struct_type(tservice, *f_iter); + + out << indent() << "processorHandlers[\"" << tfunction->get_name() << "\"] = { sequenceID, inProtocol, outProtocol, handler in" << endl + << endl; + + indent_up(); + if (!gen_cocoa_) { + out << indent() << "let args = try " << args_type << ".read(from: inProtocol)" << endl + << endl + << indent() << "try inProtocol.readMessageEnd()" << endl + << endl; + } else { + out << indent() << "let args = try " << args_type << ".readValueFromProtocol(inProtocol)" << endl + << endl + << indent() << "try inProtocol.readMessageEnd()" << endl + << endl; + } + + if (!tfunction->is_oneway() ) { + string result_type = function_result_helper_struct_type(tservice, tfunction); + indent(out) << "var result = " << result_type << "()" << endl; + + indent(out) << "do"; + block_open(out); + + indent(out); + if (!tfunction->get_returntype()->is_void()) { + out << "result.success = "; + } + out << "try handler." << (gen_cocoa_ ? function_name(tfunction) : tfunction->get_name()) << "("; + + t_struct* arg_struct = tfunction->get_arglist(); + const vector<t_field*>& fields = arg_struct->get_members(); + vector<t_field*>::const_iterator f_iter; + + for (f_iter = fields.begin(); f_iter != fields.end();) { + string fieldName = (*f_iter)->get_name(); + if (!gen_cocoa_ || f_iter != fields.begin()) { + out << fieldName << ": "; + } + + out << "args." << fieldName; + if (++f_iter != fields.end()) { + out << ", "; + } + } + + out << ")" << endl; + block_close(out); + + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& xfields = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + + if (!gen_cocoa_) { + for (x_iter = xfields.begin(); x_iter != xfields.end(); ++x_iter) { + indent(out) << "catch let error as "; + + t_program* program = (*x_iter)->get_type()->get_program(); + if ((*x_iter)->get_type()->get_name() == "Error" && namespaced_ && program != program_) { + out << get_real_swift_module(program) << "."; + } + out << (*x_iter)->get_type()->get_name(); + + out << " { result." << (*x_iter)->get_name() << " = error }" << endl; + } + + indent(out) << "catch let error { throw error }" << endl; + out << endl; + + if (!tfunction->is_oneway()) { + out << indent() << "try outProtocol.writeMessageBegin(name: \"" << tfunction->get_name() << "\", type: .reply, sequenceID: sequenceID)" << endl + << indent() << "try result.write(to: outProtocol)" << endl + << indent() << "try outProtocol.writeMessageEnd()" << endl; + } + } else { + for (x_iter = xfields.begin(); x_iter != xfields.end(); ++x_iter) { + indent(out) << "catch let error as " << (*x_iter)->get_type()->get_name(); + block_open(out); + indent(out) << "result." << (*x_iter)->get_name() << " = error" << endl; + block_close(out); + } + + indent(out) << "catch let error"; + block_open(out); + out << indent() << "throw error" << endl; + block_close(out); + + out << endl; + + if (!tfunction->is_oneway()) { + out << indent() << "try outProtocol.writeMessageBeginWithName(\"" << tfunction->get_name() << "\", type: .REPLY, sequenceID: sequenceID)" << endl + << indent() << "try " << result_type << ".writeValue(result, toProtocol: outProtocol)" << endl + << indent() << "try outProtocol.writeMessageEnd()" << endl; + } + } + } + block_close(out); + + } + + indent(out) << "return processorHandlers" << endl; + + block_close(out,false); + out << "()" << endl; + out << endl; + + if (!gen_cocoa_) { + indent(out) << "public func process(on inProtocol: TProtocol, outProtocol: TProtocol) throws"; + } else { + indent(out) << "public func processOnInputProtocol(inProtocol: TProtocol, outputProtocol outProtocol: TProtocol) throws"; + } + block_open(out); + + out << endl; + out << indent() << "let (messageName, _, sequenceID) = try inProtocol.readMessageBegin()" << endl + << endl + << indent() << "if let processorHandler = " << name << ".processorHandlers[messageName]"; + block_open(out); + out << indent() << "do"; + block_open(out); + out << indent() << "try processorHandler(sequenceID, inProtocol, outProtocol, service)" << endl; + block_close(out); + if (!gen_cocoa_) { + out << indent() << "catch let error as TApplicationError"; + block_open(out); + out << indent() << "try outProtocol.writeException(messageName: messageName, sequenceID: sequenceID, ex: error)" << endl; + block_close(out); + block_close(out); + out << indent() << "else"; + block_open(out); + out << indent() << "try inProtocol.skip(type: .struct)" << endl + << indent() << "try inProtocol.readMessageEnd()" << endl + << indent() << "let ex = TApplicationError(error: .unknownMethod(methodName: messageName))" << endl + << indent() << "try outProtocol.writeException(messageName: messageName, " + << "sequenceID: sequenceID, ex: ex)" << endl; + } else { + out << indent() << "catch let error as NSError"; + block_open(out); + out << indent() << "try outProtocol.writeExceptionForMessageName(messageName, sequenceID: sequenceID, ex: error)" << endl; + block_close(out); + block_close(out); + out << indent() << "else"; + block_open(out); + out << indent() << "try inProtocol.skipType(.STRUCT)" << endl + << indent() << "try inProtocol.readMessageEnd()" << endl + << indent() << "try outProtocol.writeExceptionForMessageName(messageName," << endl; + indent_up(); + out << indent() << "sequenceID: sequenceID," << endl + << indent() << "ex: NSError(" << endl; + indent_up(); + out << indent() << "domain: TApplicationErrorDomain, " << endl + << indent() << "code: Int(TApplicationError.UnknownMethod.rawValue), " << endl + << indent() << "userInfo: [TApplicationErrorMethodKey: messageName]))" << endl; + indent_down(); + indent_down(); + } + + block_close(out); + block_close(out); + block_close(out); + out << endl; +} + +/** + * Returns an Swift name + * + * @param ttype The type + * @param class_ref Do we want a Class reference istead of a type reference? + * @return Swift type name, i.e. Dictionary<Key,Value> + */ +string t_swift_generator::type_name(t_type* ttype, bool is_optional, bool is_forced) { + string result = ""; + + if (ttype->is_base_type()) { + result += base_type_name((t_base_type*)ttype); + } else if (ttype->is_map()) { + t_map *map = (t_map *)ttype; + result += "TMap<" + type_name(map->get_key_type()) + ", " + type_name(map->get_val_type()) + ">"; + } else if (ttype->is_set()) { + t_set *set = (t_set *)ttype; + result += "TSet<" + type_name(set->get_elem_type()) + ">"; + } else if (ttype->is_list()) { + t_list *list = (t_list *)ttype; + result += "TList<" + type_name(list->get_elem_type()) + ">"; + } + else { + t_program* program = ttype->get_program(); + if (namespaced_ && program != program_) { + result += get_real_swift_module(program) + "."; + } + result += ttype->get_name(); + } + + if (is_optional) { + result += "?"; + } + if (is_forced) { + result += "!"; + } + + return result; +} + +/** + * Returns the Swift type that corresponds to the thrift type. + * + * @param tbase The base type + */ +string t_swift_generator::base_type_name(t_base_type* type) { + t_base_type::t_base tbase = type->get_base(); + + switch (tbase) { + case t_base_type::TYPE_VOID: + return "Void"; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + return gen_cocoa_ ? "TBinary" : "Data"; + } else { + return "String"; + } + case t_base_type::TYPE_BOOL: + return "Bool"; + case t_base_type::TYPE_I8: + return "Int8"; + case t_base_type::TYPE_I16: + return "Int16"; + case t_base_type::TYPE_I32: + return "Int32"; + case t_base_type::TYPE_I64: + return "Int64"; + case t_base_type::TYPE_DOUBLE: + return "Double"; + default: + throw "compiler error: no Swift name for base type " + t_base_type::t_base_name(tbase); + } +} + +/** + * Renders full constant value (as would be seen after an '=') + * + */ +void t_swift_generator::render_const_value(ostream& out, + t_type* type, + t_const_value* value) { + type = get_true_type(type); + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + out << "\"" << get_escaped_string(value) << "\""; + break; + case t_base_type::TYPE_BOOL: + out << ((value->get_integer() > 0) ? "true" : "false"); + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + out << type_name(type) << "(" << value->get_integer() << ")"; + break; + case t_base_type::TYPE_DOUBLE: + out << type_name(type) << "("; + if (value->get_type() == t_const_value::CV_INTEGER) { + out << value->get_integer(); + } else { + out << value->get_double(); + } + out << ")"; + break; + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << (gen_cocoa_ ? value->get_identifier() : enum_const_name(value->get_identifier())); // Swift2/Cocoa compatibility + } else if (type->is_struct() || type->is_xception()) { + + out << type_name(type) << "("; + + const vector<t_field*>& fields = ((t_struct*)type)->get_members(); + vector<t_field*>::const_iterator f_iter; + + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + + for (f_iter = fields.begin(); f_iter != fields.end();) { + t_field* tfield = *f_iter; + t_const_value* value = NULL; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + if (tfield->get_name() == v_iter->first->get_string()) { + value = v_iter->second; + } + } + + if (value) { + out << tfield->get_name() << ": "; + render_const_value(out, tfield->get_type(), value); + } + else if (!field_is_optional(tfield)) { + throw "constant error: required field " + type->get_name() + "." + tfield->get_name() + " has no value"; + } + + if (++f_iter != fields.end()) { + out << ", "; + } + } + + out << ")"; + + } else if (type->is_map()) { + + out << "["; + + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + + for (v_iter = val.begin(); v_iter != val.end();) { + + render_const_value(out, ktype, v_iter->first); + out << ": "; + render_const_value(out, vtype, v_iter->second); + + if (++v_iter != val.end()) { + out << ", "; + } + } + + out << "]"; + + } else if (type->is_list()) { + + out << "["; + + t_type* etype = ((t_list*)type)->get_elem_type(); + + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + + for (v_iter = val.begin(); v_iter != val.end();) { + + render_const_value(out, etype, v_iter->first); + + if (++v_iter != val.end()) { + out << ", "; + } + } + + out << "]"; + + } else if (type->is_set()) { + + out << "["; + + t_type* etype = ((t_set*)type)->get_elem_type(); + + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + + for (v_iter = val.begin(); v_iter != val.end();) { + + render_const_value(out, etype, v_iter->first); + + if (++v_iter != val.end()) { + out << ", "; + } + } + + out << "]"; + + } else { + throw "compiler error: no const of type " + type->get_name(); + } + +} + +/** + * Declares an Swift property. + * + * @param tfield The field to declare a property for + */ +string t_swift_generator::declare_property(t_field* tfield, bool is_private) { + + string visibility = is_private ? (gen_cocoa_ ? "private" : "fileprivate") : "public"; + + ostringstream render; + + render << visibility << " var " << maybe_escape_identifier(tfield->get_name()); + + if (field_is_optional(tfield)) { + render << (gen_cocoa_ ? " " : "") << ": " << type_name(tfield->get_type(), true); + } + else { + if (!gen_cocoa_) { + render << ": " << type_name(tfield->get_type(), false); + } else { + // Swift2/Cocoa backward compat, Bad, default init + render << " = " << type_name(tfield->get_type(), false) << "()"; + } + } + + return render.str(); +} + +/** + * Renders a function signature + * + * @param tfunction Function definition + * @return String of rendered function definition + */ +string t_swift_generator::function_signature(t_function* tfunction) { + + string result = "func " + (gen_cocoa_ ? function_name(tfunction) : tfunction->get_name()); + + result += "(" + argument_list(tfunction->get_arglist(), "", false) + ") throws"; /// argsreview + + t_type* ttype = tfunction->get_returntype(); + if (!ttype->is_void()) { + result += " -> " + type_name(ttype); + } + + return result; +} + +/** + * Renders a function docstring + * + * @param tfunction Function definition + * @return String of rendered function definition + */ +void t_swift_generator::function_docstring(ostream& out, t_function* tfunction) { + + // Generate docstring with following format: + // /// <Description> + // /// <empty line> + // /// - Parameters: + // /// - <parameter>: <parameter docstring> + // /// - Returns: <return type> (Thrift has no docstring on return val) + // /// - Throws: <exception types> + + // Description + string doc = tfunction->get_doc(); + generate_docstring(out, doc); + indent(out) << "///" << endl; + + // Parameters + const vector<t_field*>& fields = tfunction->get_arglist()->get_members(); + vector<t_field*>::const_iterator f_iter; + if (!fields.empty()) { + indent(out) << "/// - Parameters:" << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + indent(out) << "/// - " << (*f_iter)->get_name() << ": "; + string doc = (*f_iter)->get_doc(); + if (!doc.empty() && doc[doc.length()-1] == '\n') { + doc.erase(doc.length()-1); + } + out << doc << endl; + } + } + + // Returns + t_type* ttype = tfunction->get_returntype(); + if (!ttype->is_void()) { + indent(out) << "/// - Returns: " << type_name(ttype) << endl; + } + + // Throws + indent(out) << "/// - Throws: "; + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + out << type_name((*x_iter)->get_type()); + if (*x_iter != xceptions.back()) { + out << ", "; + } } + out << endl; +} + +/** + * Renders a function docstring + * + * @param tfunction Function definition + * @return String of rendered function definition + */ +void t_swift_generator::async_function_docstring(ostream& out, t_function* tfunction) { + // Generate docstring with following format: + // /// <Description> + // /// <empty line> + // /// - Parameters: + // /// - <parameter>: <parameter docstring> + // /// - callback: <callback types> + + // Description + string doc = tfunction->get_doc(); + generate_docstring(out, doc); + indent(out) << "///" << endl; + + // Parameters + const vector<t_field*>& fields = tfunction->get_arglist()->get_members(); + vector<t_field*>::const_iterator f_iter; + if (!fields.empty()) { + indent(out) << "/// - Parameters:" << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + indent(out) << "/// - " << (*f_iter)->get_name() << ": "; + string doc = (*f_iter)->get_doc(); + if (!doc.empty() && doc[doc.length()-1] == '\n') { + doc.erase(doc.length()-1); + } + out << doc << endl; + } + } + + // completion + indent(out) << "/// - completion: TAsyncResult<" << type_name(tfunction->get_returntype()) + << "> wrapping return and following Exceptions: "; + t_struct* xs = tfunction->get_xceptions(); + const vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + out << type_name((*x_iter)->get_type()); + if (*x_iter != xceptions.back()) { + out << ", "; + } + } + out << endl; +} + +/** + * Renders a function signature that returns asynchronously via blocks. + * + * @param tfunction Function definition + * @return String of rendered function definition + */ +string t_swift_generator::async_function_signature(t_function* tfunction) { + t_type* ttype = tfunction->get_returntype(); + t_struct* targlist = tfunction->get_arglist(); + string result = "func " + (gen_cocoa_ ? function_name(tfunction) : tfunction->get_name()); + + if (!gen_cocoa_) { + string response_string = "(TAsyncResult<"; + response_string += (ttype->is_void()) ? "Void" : type_name(ttype); + response_string += ">) -> Void"; + result += "(" + argument_list(tfunction->get_arglist(), "", false) + + (targlist->get_members().size() ? ", " : "") + + "completion: @escaping " + response_string + ")"; + } else { + string response_param = "(" + ((ttype->is_void()) ? "" : type_name(ttype)) + ") -> Void"; + result += "(" + argument_list(tfunction->get_arglist(), "", false) + + (targlist->get_members().size() ? ", " : "") + + "success: " + response_param + ", " + + "failure: (NSError) -> Void) throws"; + } + return result; +} + +/** + * Renders a function signature that returns asynchronously via promises. + * ONLY FOR Swift2/Cocoa BACKWARDS COMPATIBILITY + * + * @param tfunction Function definition + * @return String of rendered function definition + */ +string t_swift_generator::promise_function_signature(t_function* tfunction) { + return "func " + function_name(tfunction) + "(" + argument_list(tfunction->get_arglist(), "", false) + ") throws " + + "-> Promise<" + type_name(tfunction->get_returntype()) + ">"; +} + +/** + * Renders a verbose function name suitable for a Swift method. ONLY FOR Swift2/Cocoa BACKWARDS COMPATIBILITY + */ +string t_swift_generator::function_name(t_function* tfunction) { + string name = tfunction->get_name(); + if (!tfunction->get_arglist()->get_members().empty()) { + string first_arg = tfunction->get_arglist()->get_members().front()->get_name(); + if (name.size() < first_arg.size() || + lowercase(name.substr(name.size()-first_arg.size())) != lowercase(first_arg)) { + name += "With" + capitalize(tfunction->get_arglist()->get_members()[0]->get_name()); + } + } + return name; +} + +/** + * Renders a Swift method argument list + */ +string t_swift_generator::argument_list(t_struct* tstruct, string protocol_name, bool is_internal) { + string result = ""; + bool include_protocol = !protocol_name.empty(); + + const vector<t_field*>& fields = tstruct->get_members(); + vector<t_field*>::const_iterator f_iter; + + if (include_protocol) { + result += protocol_name + ": TProtocol"; + if (!fields.empty()) { + result += ", "; + } + } else if (!fields.empty() && is_internal && gen_cocoa_) { + // Force first argument to be named, Swift2/Cocoa backwards compat + result += fields.front()->get_name() + " "; + } + + for (f_iter = fields.begin(); f_iter != fields.end();) { + t_field* arg = *f_iter; + + if (!gen_cocoa_) { + // optional args not usually permitted for some reason, even though dynamic langs handle it + // use annotation "swift.nullable" to achieve + result += arg->get_name() + ": " + type_name(arg->get_type(), field_is_optional(arg)); + } else { + result += arg->get_name() + ": " + type_name(arg->get_type()); + } + + if (++f_iter != fields.end()) { + result += ", "; + } + } + return result; +} + +/** + * https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html + * + */ + +void t_swift_generator::populate_reserved_words() { + if (!gen_cocoa_) { + swift_reserved_words_.insert("__COLUMN__"); + swift_reserved_words_.insert("__FILE__"); + swift_reserved_words_.insert("__FUNCTION__"); + swift_reserved_words_.insert("__LINE__"); + swift_reserved_words_.insert("Any"); + swift_reserved_words_.insert("as"); + swift_reserved_words_.insert("associatedtype"); + swift_reserved_words_.insert("associativity"); + swift_reserved_words_.insert("break"); + swift_reserved_words_.insert("case"); + swift_reserved_words_.insert("catch"); + swift_reserved_words_.insert("class"); + swift_reserved_words_.insert("continue"); + swift_reserved_words_.insert("convenience"); + swift_reserved_words_.insert("default"); + swift_reserved_words_.insert("defer"); + swift_reserved_words_.insert("deinit"); + swift_reserved_words_.insert("didSet"); + swift_reserved_words_.insert("do"); + swift_reserved_words_.insert("dynamic"); + swift_reserved_words_.insert("dynamicType"); + swift_reserved_words_.insert("else"); + swift_reserved_words_.insert("enum"); + swift_reserved_words_.insert("extension"); + swift_reserved_words_.insert("fallthrough"); + swift_reserved_words_.insert("false"); + swift_reserved_words_.insert("fileprivate"); + swift_reserved_words_.insert("final"); + swift_reserved_words_.insert("for"); + swift_reserved_words_.insert("func"); + swift_reserved_words_.insert("get"); + swift_reserved_words_.insert("guard"); + swift_reserved_words_.insert("if"); + swift_reserved_words_.insert("import"); + swift_reserved_words_.insert("in"); + swift_reserved_words_.insert("indirect"); + swift_reserved_words_.insert("infix"); + swift_reserved_words_.insert("init"); + swift_reserved_words_.insert("inout"); + swift_reserved_words_.insert("internal"); + swift_reserved_words_.insert("is"); + swift_reserved_words_.insert("lazy"); + swift_reserved_words_.insert("left"); + swift_reserved_words_.insert("let"); + swift_reserved_words_.insert("mutating"); + swift_reserved_words_.insert("nil"); + swift_reserved_words_.insert("none"); + swift_reserved_words_.insert("nonmutating"); + swift_reserved_words_.insert("open"); + swift_reserved_words_.insert("operator"); + swift_reserved_words_.insert("optional"); + swift_reserved_words_.insert("override"); + swift_reserved_words_.insert("postfix"); + swift_reserved_words_.insert("precedence"); + swift_reserved_words_.insert("prefix"); + swift_reserved_words_.insert("private"); + swift_reserved_words_.insert("protocol"); + swift_reserved_words_.insert("Protocol"); + swift_reserved_words_.insert("public"); + swift_reserved_words_.insert("repeat"); + swift_reserved_words_.insert("required"); + swift_reserved_words_.insert("rethrows"); + swift_reserved_words_.insert("return"); + swift_reserved_words_.insert("right"); + swift_reserved_words_.insert("self"); + swift_reserved_words_.insert("Self"); + swift_reserved_words_.insert("set"); + swift_reserved_words_.insert("static"); + swift_reserved_words_.insert("struct"); + swift_reserved_words_.insert("subscript"); + swift_reserved_words_.insert("super"); + swift_reserved_words_.insert("switch"); + swift_reserved_words_.insert("throw"); + swift_reserved_words_.insert("throws"); + swift_reserved_words_.insert("true"); + swift_reserved_words_.insert("try"); + swift_reserved_words_.insert("Type"); + swift_reserved_words_.insert("typealias"); + swift_reserved_words_.insert("unowned"); + swift_reserved_words_.insert("var"); + swift_reserved_words_.insert("weak"); + swift_reserved_words_.insert("where"); + swift_reserved_words_.insert("while"); + swift_reserved_words_.insert("willSet"); + } else { + swift_reserved_words_.insert("Self"); + swift_reserved_words_.insert("associatedtype"); + swift_reserved_words_.insert("defer"); + swift_reserved_words_.insert("deinit"); + swift_reserved_words_.insert("dynamicType"); + swift_reserved_words_.insert("enum"); + swift_reserved_words_.insert("extension"); + swift_reserved_words_.insert("fallthrough"); + swift_reserved_words_.insert("false"); + swift_reserved_words_.insert("func"); + swift_reserved_words_.insert("guard"); + swift_reserved_words_.insert("init"); + swift_reserved_words_.insert("inout"); + swift_reserved_words_.insert("internal"); + swift_reserved_words_.insert("let"); + swift_reserved_words_.insert("operator"); + swift_reserved_words_.insert("protocol"); + swift_reserved_words_.insert("repeat"); + swift_reserved_words_.insert("rethrows"); + swift_reserved_words_.insert("struct"); + swift_reserved_words_.insert("subscript"); + swift_reserved_words_.insert("throws"); + swift_reserved_words_.insert("true"); + swift_reserved_words_.insert("typealias"); + swift_reserved_words_.insert("where"); + } +} + +string t_swift_generator::maybe_escape_identifier(const string& identifier) { + if (swift_reserved_words_.find(identifier) != swift_reserved_words_.end()) { + return "`" + identifier + "`"; + } + return identifier; +} + +/** + * Converts the parse type to a Swift TType enumeration. + */ +string t_swift_generator::type_to_enum(t_type* type, bool qualified) { + type = get_true_type(type); + + string result = qualified ? "TType." : "."; + if (!gen_cocoa_) { + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + return result + "string"; + case t_base_type::TYPE_BOOL: + return result + "bool"; + case t_base_type::TYPE_I8: + return result + "i8"; + case t_base_type::TYPE_I16: + return result + "i16"; + case t_base_type::TYPE_I32: + return result + "i32"; + case t_base_type::TYPE_I64: + return result + "i64"; + case t_base_type::TYPE_DOUBLE: + return result + "double"; + } + } else if (type->is_enum()) { + return result + "i32"; + } else if (type->is_struct() || type->is_xception()) { + return result + "struct"; + } else if (type->is_map()) { + return result + "map"; + } else if (type->is_set()) { + return result + "set"; + } else if (type->is_list()) { + return result + "list"; + } + } else { + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + return result + "STRING"; + case t_base_type::TYPE_BOOL: + return result + "BOOL"; + case t_base_type::TYPE_I8: + return result + "BYTE"; + case t_base_type::TYPE_I16: + return result + "I16"; + case t_base_type::TYPE_I32: + return result + "I32"; + case t_base_type::TYPE_I64: + return result + "I64"; + case t_base_type::TYPE_DOUBLE: + return result + "DOUBLE"; + } + } else if (type->is_enum()) { + return result + "I32"; + } else if (type->is_struct() || type->is_xception()) { + return result + "STRUCT"; + } else if (type->is_map()) { + return result + "MAP"; + } else if (type->is_set()) { + return result + "SET"; + } else if (type->is_list()) { + return result + "LIST"; + } + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + + +THRIFT_REGISTER_GENERATOR( + swift, + "Swift 3.0", + " log_unexpected: Log every time an unexpected field ID or type is encountered.\n" + " debug_descriptions:\n" + " Allow use of debugDescription so the app can add description via a cateogory/extension\n" + " async_clients: Generate clients which invoke asynchronously via block syntax.\n" + " namespaced: Generate source in Module scoped output directories for Swift Namespacing.\n" + " cocoa: Generate Swift 2.x code compatible with the Thrift/Cocoa library\n" + " promise_kit: Generate clients which invoke asynchronously via promises (only use with cocoa flag)\n" + " safe_enums: Generate enum types with an unknown case to handle unspecified values rather than throw a serialization error\n") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_xml_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_xml_generator.cc new file mode 100644 index 000000000..b6692938f --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_xml_generator.cc @@ -0,0 +1,692 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <fstream> +#include <iostream> +#include <sstream> +#include <limits> + +#include <stdlib.h> +#include <sys/stat.h> +#include <sstream> + +#include "thrift/platform.h" +#include "thrift/generate/t_generator.h" + +using std::map; +using std::ofstream; +using std::ostream; +using std::ostringstream; +using std::string; +using std::stringstream; +using std::vector; +using std::stack; +using std::set; + +static const string endl = "\n"; +static const string quot = "\""; + +static const string default_ns_prefix = "http://thrift.apache.org/xml/ns/"; + +/** + * This generator creates an XML model of the parsed IDL tree, and is designed + * to make it easy to use this file as the input for other template engines, + * such as XSLT. To this end, the generated XML is slightly more verbose than + * you might expect... for example, references to "id" types (such as structs, + * unions, etc) always specify the name of the IDL document, even if the type + * is defined in the same document as the reference. + */ +class t_xml_generator : public t_generator { +public: + t_xml_generator( t_program* program, + const std::map<std::string, std::string>& parsed_options, + const std::string& option_string) + : t_generator(program) { + (void)option_string; + std::map<std::string, std::string>::const_iterator iter; + + should_merge_includes_ = false; + should_use_default_ns_ = true; + should_use_namespaces_ = true; + for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + if( iter->first.compare("merge") == 0) { + should_merge_includes_ = true; + } else if( iter->first.compare("no_default_ns") == 0) { + should_use_default_ns_ = false; + } else if( iter->first.compare("no_namespaces") == 0) { + should_use_namespaces_ = false; + } else { + throw "unknown option xml:" + iter->first; + } + } + + out_dir_base_ = "gen-xml"; + } + + ~t_xml_generator() override = default; + + void init_generator() override; + void close_generator() override; + void generate_program() override; + + void iterate_program(t_program* program); + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_function(t_function* tfunc); + void generate_field(t_field* field); + + void generate_service(t_service* tservice) override; + void generate_struct(t_struct* tstruct) override; + + void generate_annotations(std::map<std::string, std::string> annotations); + +private: + bool should_merge_includes_; + bool should_use_default_ns_; + bool should_use_namespaces_; + + ofstream_with_content_based_conditional_update f_xml_; + + std::set<string> programs_; + std::stack<string> elements_; + bool top_element_is_empty; + bool top_element_is_open; + + string target_namespace(t_program* program); + void write_element_start(const string name); + void close_top_element(); + void write_element_end(); + void write_attribute(string key, string val); + void write_int_attribute(string key, int val); + string escape_xml_string(const string& input); + + void write_xml_comment(string msg); + + void write_type(t_type* ttype); + void write_doc(t_doc* tdoc); + + template <typename T> + string number_to_string(T t) { + std::ostringstream out; + out.imbue(std::locale::classic()); + out.precision(std::numeric_limits<T>::digits10); + out << t; + return out.str(); + } + + template <typename T> + void write_number(T n) { + f_xml_ << number_to_string(n); + } + + template <typename T> + void write_element_number(string name, T n) { + write_element_string(name, number_to_string(n)); + } + + string get_type_name(t_type* ttype); + + void generate_constant(t_const* con); + + void write_element_string(string name, string value); + void write_value(t_type* tvalue); + void write_const_value(t_const_value* value); + virtual std::string xml_autogen_comment() { + return std::string("\n") + " * Autogenerated by Thrift Compiler (" + THRIFT_VERSION + ")\n" + + " *\n" + " * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n"; + } +}; + +void t_xml_generator::init_generator() { + MKDIR(get_out_dir().c_str()); + + string f_xml_name = get_out_dir() + program_->get_name() + ".xml"; + f_xml_.open(f_xml_name.c_str()); + + top_element_is_open = false; +} + +string t_xml_generator::target_namespace(t_program* program) { + std::map<std::string, std::string> map; + std::map<std::string, std::string>::iterator iter; + map = program->get_namespace_annotations("xml"); + if ((iter = map.find("targetNamespace")) != map.end()) { + return iter->second; + } + map = program->get_namespaces(); + if ((iter = map.find("xml")) != map.end()) { + return default_ns_prefix + iter->second; + } + map = program->get_namespace_annotations("*"); + if ((iter = map.find("xml.targetNamespace")) != map.end()) { + return iter->second; + } + map = program->get_namespaces(); + if ((iter = map.find("*")) != map.end()) { + return default_ns_prefix + iter->second; + } + return default_ns_prefix + program->get_name(); +} + +void t_xml_generator::write_xml_comment(string msg) { + close_top_element(); + // TODO: indent any EOLs that may occur with msg + // TODO: proper msg escaping needed? + f_xml_ << indent() << "<!-- " << msg << " -->" << endl; + top_element_is_empty = false; +} + +void t_xml_generator::close_top_element() { + if( top_element_is_open) { + top_element_is_open = false; + if (elements_.size() > 0 && top_element_is_empty) { + f_xml_ << ">" << endl; + } + } +} + +void t_xml_generator::write_element_start(string name) { + if (should_use_namespaces_ && !should_use_default_ns_) { + name = "idl:" + name; + } + close_top_element(); + f_xml_ << indent() << "<" << name; + elements_.push(name); + top_element_is_empty = true; + top_element_is_open = true; + indent_up(); +} + +void t_xml_generator::write_element_end() { + indent_down(); + if (top_element_is_empty && top_element_is_open) { + f_xml_ << " />" << endl; + } else { + f_xml_ << indent() << "</" << elements_.top() << ">" << endl; + } + top_element_is_empty = false; + elements_.pop(); +} + +void t_xml_generator::write_attribute(string key, string val) { + f_xml_ << " " << key << "=\"" << escape_xml_string(val) << "\""; +} + +void t_xml_generator::write_int_attribute(string key, int val) { + write_attribute(key, number_to_string(val)); +} + +void t_xml_generator::write_element_string(string name, string val) { + if (should_use_namespaces_ && !should_use_default_ns_) { + name = "idl:" + name; + } + close_top_element(); + top_element_is_empty = false; + f_xml_ << indent() + << "<" << name << ">" << escape_xml_string(val) << "</" << name << ">" + << endl; +} + +string t_xml_generator::escape_xml_string(const string& input) { + std::ostringstream ss; + for (char iter : input) { + switch (iter) { + case '&': + ss << "&"; + break; + case '"': + ss << """; + break; + case '\'': + ss << "'"; + break; + case '<': + ss << "<"; + break; + case '>': + ss << ">"; + break; + default: + ss << iter; + break; + } + } + return ss.str(); +} + +void t_xml_generator::close_generator() { + f_xml_.close(); +} + +void t_xml_generator::generate_program() { + + init_generator(); + + write_element_start("idl"); + if (should_use_namespaces_) { + if (should_use_default_ns_) { + write_attribute("xmlns", "http://thrift.apache.org/xml/idl"); + } + write_attribute("xmlns:idl", "http://thrift.apache.org/xml/idl"); + } + + write_xml_comment( xml_autogen_comment()); + + iterate_program(program_); + + write_element_end(); + + close_generator(); + +} + +void t_xml_generator::iterate_program(t_program* program) { + + write_element_start("document"); + write_attribute("name", program->get_name()); + if (should_use_namespaces_) { + const string targetNamespace = target_namespace(program); + write_attribute("targetNamespace", targetNamespace); + write_attribute("xmlns:" + program->get_name(), targetNamespace); + } + write_doc(program); + + const vector<t_program*> includes = program->get_includes(); + vector<t_program*>::const_iterator inc_it; + for (inc_it = includes.begin(); inc_it != includes.end(); ++inc_it) { + write_element_start("include"); + write_attribute("name", (*inc_it)->get_name()); + write_element_end(); + } + + const map<string, string>& namespaces = program->get_namespaces(); + map<string, string>::const_iterator ns_it; + for (ns_it = namespaces.begin(); ns_it != namespaces.end(); ++ns_it) { + write_element_start("namespace"); + write_attribute("name", ns_it->first); + write_attribute("value", ns_it->second); + generate_annotations(program->get_namespace_annotations(ns_it->first)); + write_element_end(); + } + + // TODO: can constants have annotations? + vector<t_const*> consts = program->get_consts(); + vector<t_const*>::iterator c_iter; + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + generate_constant(*c_iter); + } + + vector<t_typedef*> typedefs = program->get_typedefs(); + vector<t_typedef*>::iterator td_iter; + for (td_iter = typedefs.begin(); td_iter != typedefs.end(); ++td_iter) { + generate_typedef(*td_iter); + } + + vector<t_enum*> enums = program->get_enums(); + vector<t_enum*>::iterator en_iter; + for (en_iter = enums.begin(); en_iter != enums.end(); ++en_iter) { + generate_enum(*en_iter); + } + + vector<t_struct*> objects = program->get_objects(); + vector<t_struct*>::iterator o_iter; + for (o_iter = objects.begin(); o_iter != objects.end(); ++o_iter) { + if ((*o_iter)->is_xception()) { + generate_xception(*o_iter); + } else { + generate_struct(*o_iter); + } + } + + vector<t_service*> services = program->get_services(); + vector<t_service*>::iterator sv_iter; + for (sv_iter = services.begin(); sv_iter != services.end(); ++sv_iter) { + generate_service(*sv_iter); + } + + write_element_end(); + + if (should_merge_includes_) { + programs_.insert(program->get_name()); + const vector<t_program*> programs = program->get_includes(); + vector<t_program*>::const_iterator prog_it; + for (prog_it = programs.begin(); prog_it != programs.end(); ++prog_it) { + if (!programs_.count((*prog_it)->get_name())) { + iterate_program(*prog_it); + } + } + } + +} + +void t_xml_generator::generate_typedef(t_typedef* ttypedef) { + write_element_start("typedef"); + write_attribute("name", ttypedef->get_name()); + write_doc(ttypedef); + write_type(ttypedef->get_true_type()); + generate_annotations(ttypedef->annotations_); + write_element_end(); + return; +} + +void t_xml_generator::write_type(t_type* ttype) { + const string type = get_type_name(ttype); + write_attribute("type", type); + if (type == "id") { + write_attribute("type-module", ttype->get_program()->get_name()); + write_attribute("type-id", ttype->get_name()); + } else if (type == "list") { + t_type* etype = ((t_list*)ttype)->get_elem_type(); + write_element_start("elemType"); + write_type(etype); + write_element_end(); + } else if (type == "set") { + t_type* etype = ((t_set*)ttype)->get_elem_type(); + write_element_start("elemType"); + write_type(etype); + write_element_end(); + } else if (type == "map") { + t_type* ktype = ((t_map*)ttype)->get_key_type(); + write_element_start("keyType"); + write_type(ktype); + write_element_end(); + t_type* vtype = ((t_map*)ttype)->get_val_type(); + write_element_start("valueType"); + write_type(vtype); + write_element_end(); + } +} + +void t_xml_generator::write_doc(t_doc* tdoc) { + if (tdoc->has_doc()) { + string doc = tdoc->get_doc(); + // for some reason there always seems to be a trailing newline on doc + // comments; loop below naively tries to strip off trailing cr/lf + int n = 0; + for (string::reverse_iterator i = doc.rbegin(); i != doc.rend(); i++,n++) { + if (*i != '\n' || *i == '\r') { + if (n > 0) { + doc.erase(doc.length() - n); + } + break; + } + } + write_attribute("doc", doc); + } +} + +void t_xml_generator::generate_annotations( + std::map<std::string, std::string> annotations) { + std::map<std::string, std::string>::iterator iter; + for (iter = annotations.begin(); iter != annotations.end(); ++iter) { + write_element_start("annotation"); + write_attribute("key", iter->first); + write_attribute("value", iter->second); + write_element_end(); + } +} + +void t_xml_generator::generate_constant(t_const* con) { + write_element_start("const"); + write_attribute("name", con->get_name()); + write_doc(con); + write_type(con->get_type()); + write_const_value(con->get_value()); + write_element_end(); +} + +void t_xml_generator::write_const_value(t_const_value* value) { + + switch (value->get_type()) { + + case t_const_value::CV_IDENTIFIER: + case t_const_value::CV_INTEGER: + write_element_number("int", value->get_integer()); + break; + + case t_const_value::CV_DOUBLE: + write_element_number("double", value->get_double()); + break; + + case t_const_value::CV_STRING: + write_element_string("string", value->get_string()); + break; + + case t_const_value::CV_LIST: { + write_element_start("list"); + std::vector<t_const_value*> list = value->get_list(); + std::vector<t_const_value*>::iterator lit; + for (lit = list.begin(); lit != list.end(); ++lit) { + write_element_start("entry"); + write_const_value(*lit); + write_element_end(); + } + write_element_end(); + break; + } + + case t_const_value::CV_MAP: { + write_element_start("map"); + std::map<t_const_value*, t_const_value*, t_const_value::value_compare> map = value->get_map(); + std::map<t_const_value*, t_const_value*, t_const_value::value_compare>::iterator mit; + for (mit = map.begin(); mit != map.end(); ++mit) { + write_element_start("entry"); + write_element_start("key"); + write_const_value(mit->first); + write_element_end(); + write_element_start("value"); + write_const_value(mit->second); + write_element_end(); + write_element_end(); + } + write_element_end(); + break; + } + + default: + indent_up(); + f_xml_ << indent() << "<null />" << endl; + indent_down(); + break; + } + +} + +void t_xml_generator::generate_enum(t_enum* tenum) { + + write_element_start("enum"); + write_attribute("name", tenum->get_name()); + write_doc(tenum); + + vector<t_enum_value*> values = tenum->get_constants(); + vector<t_enum_value*>::iterator val_iter; + for (val_iter = values.begin(); val_iter != values.end(); ++val_iter) { + t_enum_value* val = (*val_iter); + write_element_start("member"); + write_attribute("name", val->get_name()); + write_int_attribute("value", val->get_value()); + write_doc(val); + generate_annotations(val->annotations_); + write_element_end(); + } + + generate_annotations(tenum->annotations_); + + write_element_end(); + +} + +void t_xml_generator::generate_struct(t_struct* tstruct) { + + string tagname = "struct"; + if (tstruct->is_union()) { + tagname = "union"; + } else if (tstruct->is_xception()) { + tagname = "exception"; + } + + write_element_start(tagname); + write_attribute("name", tstruct->get_name()); + write_doc(tstruct); + vector<t_field*> members = tstruct->get_members(); + vector<t_field*>::iterator mem_iter; + for (mem_iter = members.begin(); mem_iter != members.end(); mem_iter++) { + write_element_start("field"); + generate_field(*mem_iter); + write_element_end(); + } + + generate_annotations(tstruct->annotations_); + + write_element_end(); + +} + +void t_xml_generator::generate_field(t_field* field) { + write_attribute("name", field->get_name()); + write_int_attribute("field-id", field->get_key()); + write_doc(field); + string requiredness; + switch (field->get_req()) { + case t_field::T_REQUIRED: + requiredness = "required"; + break; + case t_field::T_OPTIONAL: + requiredness = "optional"; + break; + default: + requiredness = ""; + break; + } + if (requiredness != "") { + write_attribute("required", requiredness); + } + write_type(field->get_type()); + if (field->get_value()) { + write_element_start("default"); + write_const_value(field->get_value()); + write_element_end(); + } + generate_annotations(field->annotations_); +} + +void t_xml_generator::generate_service(t_service* tservice) { + + write_element_start("service"); + write_attribute("name", tservice->get_name()); + + if (should_use_namespaces_) { + string prog_ns = target_namespace(tservice->get_program()); + if (*prog_ns.rbegin() != '/') { + prog_ns.push_back('/'); + } + const string tns = prog_ns + tservice->get_name(); + write_attribute("targetNamespace", tns); + write_attribute("xmlns:tns", tns); + } + + if (tservice->get_extends()) { + const t_service* extends = tservice->get_extends(); + write_attribute("parent-module", extends->get_program()->get_name()); + write_attribute("parent-id", extends->get_name()); + } + + write_doc(tservice); + + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator fn_iter = functions.begin(); + for (; fn_iter != functions.end(); fn_iter++) { + generate_function(*fn_iter); + } + + generate_annotations(tservice->annotations_); + + write_element_end(); + +} + +void t_xml_generator::generate_function(t_function* tfunc) { + + write_element_start("method"); + + write_attribute("name", tfunc->get_name()); + if (tfunc->is_oneway()) { + write_attribute("oneway", "true"); + } + + write_doc(tfunc); + + write_element_start("returns"); + write_type(tfunc->get_returntype()); + write_element_end(); + + vector<t_field*> members = tfunc->get_arglist()->get_members(); + vector<t_field*>::iterator mem_iter = members.begin(); + for (; mem_iter != members.end(); mem_iter++) { + write_element_start("arg"); + generate_field(*mem_iter); + write_element_end(); + } + + vector<t_field*> excepts = tfunc->get_xceptions()->get_members(); + vector<t_field*>::iterator ex_iter = excepts.begin(); + for (; ex_iter != excepts.end(); ex_iter++) { + write_element_start("throws"); + generate_field(*ex_iter); + write_element_end(); + } + + generate_annotations(tfunc->annotations_); + + write_element_end(); + +} + +string t_xml_generator::get_type_name(t_type* ttype) { + if (ttype->is_list()) { + return "list"; + } + if (ttype->is_set()) { + return "set"; + } + if (ttype->is_map()) { + return "map"; + } + if ((ttype->is_enum() )|| + (ttype->is_struct() )|| + (ttype->is_typedef() )|| + (ttype->is_xception())){ + return "id"; + } + if (ttype->is_base_type()) { + t_base_type* tbasetype = (t_base_type*)ttype; + if (tbasetype->is_binary() ) { + return "binary"; + } + return t_base_type::t_base_name(tbasetype->get_base()); + } + return "(unknown)"; +} + +THRIFT_REGISTER_GENERATOR( + xml, + "XML", + " merge: Generate output with included files merged\n" + " no_default_ns: Omit default xmlns and add idl: prefix to all elements\n" + " no_namespaces: Do not add namespace definitions to the XML model\n") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_xsd_generator.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_xsd_generator.cc new file mode 100644 index 000000000..379e5a448 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/t_xsd_generator.cc @@ -0,0 +1,376 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <fstream> +#include <iostream> +#include <sstream> + +#include <stdlib.h> +#include <sys/stat.h> +#include <sstream> +#include "thrift/version.h" +#include "thrift/platform.h" +#include "thrift/generate/t_generator.h" + +using std::map; +using std::ofstream; +using std::ostream; +using std::ostringstream; +using std::string; +using std::stringstream; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes + +/** + * XSD generator, creates an XSD for the base types etc. + * + */ +class t_xsd_generator : public t_generator { +public: + t_xsd_generator(t_program* program, + const std::map<std::string, std::string>& parsed_options, + const std::string& option_string) + : t_generator(program) { + (void)option_string; + std::map<std::string, std::string>::const_iterator iter; + + /* no options yet */ + for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + throw "unknown option xsd:" + iter->first; + } + + out_dir_base_ = "gen-xsd"; + } + + ~t_xsd_generator() override = default; + + /** + * Init and close methods + */ + + void init_generator() override; + void close_generator() override; + + /** + * Program-level generation functions + */ + + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override { (void)tenum; } + + void generate_service(t_service* tservice) override; + void generate_struct(t_struct* tstruct) override; + +private: + void generate_element(std::ostream& out, + std::string name, + t_type* ttype, + t_struct* attrs = NULL, + bool optional = false, + bool nillable = false, + bool list_element = false); + + std::string ns(std::string in, std::string ns) { return ns + ":" + in; } + + std::string xsd(std::string in) { return ns(in, "xsd"); } + + std::string type_name(t_type* ttype); + std::string base_type_name(t_base_type::t_base tbase); + + virtual std::string xml_autogen_comment() { + return std::string("<!--\n") + " * Autogenerated by Thrift Compiler (" + THRIFT_VERSION + ")\n" + + " *\n" + " * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n" + + " -->\n"; + } + + /** + * Output xsd/php file + */ + ofstream_with_content_based_conditional_update f_xsd_; + ofstream_with_content_based_conditional_update f_php_; + + /** + * Output string stream + */ + std::ostringstream s_xsd_types_; +}; + +void t_xsd_generator::init_generator() { + // Make output directory + MKDIR(get_out_dir().c_str()); + + // Make output file + string f_php_name = get_out_dir() + program_->get_name() + "_xsd.php"; + f_php_.open(f_php_name.c_str()); + + f_php_ << "<?php" << endl + << autogen_comment() << endl; +} + +void t_xsd_generator::close_generator() { + f_php_ << "?>" << endl; + f_php_.close(); +} + +void t_xsd_generator::generate_typedef(t_typedef* ttypedef) { + indent(s_xsd_types_) << "<xsd:simpleType name=\"" << ttypedef->get_name() << "\">" << endl; + indent_up(); + if (ttypedef->get_type()->is_string() && ((t_base_type*)ttypedef->get_type())->is_string_enum()) { + indent(s_xsd_types_) << "<xsd:restriction base=\"" << type_name(ttypedef->get_type()) << "\">" + << endl; + indent_up(); + const vector<string>& values = ((t_base_type*)ttypedef->get_type())->get_string_enum_vals(); + vector<string>::const_iterator v_iter; + for (v_iter = values.begin(); v_iter != values.end(); ++v_iter) { + indent(s_xsd_types_) << "<xsd:enumeration value=\"" << (*v_iter) << "\" />" << endl; + } + indent_down(); + indent(s_xsd_types_) << "</xsd:restriction>" << endl; + } else { + indent(s_xsd_types_) << "<xsd:restriction base=\"" << type_name(ttypedef->get_type()) << "\" />" + << endl; + } + indent_down(); + indent(s_xsd_types_) << "</xsd:simpleType>" << endl << endl; +} + +void t_xsd_generator::generate_struct(t_struct* tstruct) { + vector<t_field*>::const_iterator m_iter; + const vector<t_field*>& members = tstruct->get_members(); + bool xsd_all = tstruct->get_xsd_all(); + + indent(s_xsd_types_) << "<xsd:complexType name=\"" << tstruct->get_name() << "\">" << endl; + indent_up(); + if (xsd_all) { + indent(s_xsd_types_) << "<xsd:all>" << endl; + } else { + indent(s_xsd_types_) << "<xsd:sequence>" << endl; + } + indent_up(); + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + generate_element(s_xsd_types_, + (*m_iter)->get_name(), + (*m_iter)->get_type(), + (*m_iter)->get_xsd_attrs(), + (*m_iter)->get_xsd_optional() || xsd_all, + (*m_iter)->get_xsd_nillable()); + } + + indent_down(); + if (xsd_all) { + indent(s_xsd_types_) << "</xsd:all>" << endl; + } else { + indent(s_xsd_types_) << "</xsd:sequence>" << endl; + } + indent_down(); + indent(s_xsd_types_) << "</xsd:complexType>" << endl << endl; +} + +void t_xsd_generator::generate_element(ostream& out, + string name, + t_type* ttype, + t_struct* attrs, + bool optional, + bool nillable, + bool list_element) { + string sminOccurs = (optional || list_element) ? " minOccurs=\"0\"" : ""; + string smaxOccurs = list_element ? " maxOccurs=\"unbounded\"" : ""; + string soptional = sminOccurs + smaxOccurs; + string snillable = nillable ? " nillable=\"true\"" : ""; + + if (ttype->is_void() || ttype->is_list()) { + indent(out) << "<xsd:element name=\"" << name << "\"" << soptional << snillable << ">" << endl; + indent_up(); + if (attrs == NULL && ttype->is_void()) { + indent(out) << "<xsd:complexType />" << endl; + } else { + indent(out) << "<xsd:complexType>" << endl; + indent_up(); + if (ttype->is_list()) { + indent(out) << "<xsd:sequence minOccurs=\"0\" maxOccurs=\"unbounded\">" << endl; + indent_up(); + string subname; + t_type* subtype = ((t_list*)ttype)->get_elem_type(); + if (subtype->is_base_type() || subtype->is_container()) { + subname = name + "_elt"; + } else { + subname = type_name(subtype); + } + f_php_ << "$GLOBALS['" << program_->get_name() << "_xsd_elt_" << name << "'] = '" << subname + << "';" << endl; + generate_element(out, subname, subtype, NULL, false, false, true); + indent_down(); + indent(out) << "</xsd:sequence>" << endl; + indent(out) << "<xsd:attribute name=\"list\" type=\"xsd:boolean\" />" << endl; + } + if (attrs != NULL) { + const vector<t_field*>& members = attrs->get_members(); + vector<t_field*>::const_iterator a_iter; + for (a_iter = members.begin(); a_iter != members.end(); ++a_iter) { + indent(out) << "<xsd:attribute name=\"" << (*a_iter)->get_name() << "\" type=\"" + << type_name((*a_iter)->get_type()) << "\" />" << endl; + } + } + indent_down(); + indent(out) << "</xsd:complexType>" << endl; + } + indent_down(); + indent(out) << "</xsd:element>" << endl; + } else { + if (attrs == NULL) { + indent(out) << "<xsd:element name=\"" << name << "\"" + << " type=\"" << type_name(ttype) << "\"" << soptional << snillable << " />" + << endl; + } else { + // Wow, all this work for a SIMPLE TYPE with attributes?!?!?! + indent(out) << "<xsd:element name=\"" << name << "\"" << soptional << snillable << ">" + << endl; + indent_up(); + indent(out) << "<xsd:complexType>" << endl; + indent_up(); + indent(out) << "<xsd:complexContent>" << endl; + indent_up(); + indent(out) << "<xsd:extension base=\"" << type_name(ttype) << "\">" << endl; + indent_up(); + const vector<t_field*>& members = attrs->get_members(); + vector<t_field*>::const_iterator a_iter; + for (a_iter = members.begin(); a_iter != members.end(); ++a_iter) { + indent(out) << "<xsd:attribute name=\"" << (*a_iter)->get_name() << "\" type=\"" + << type_name((*a_iter)->get_type()) << "\" />" << endl; + } + indent_down(); + indent(out) << "</xsd:extension>" << endl; + indent_down(); + indent(out) << "</xsd:complexContent>" << endl; + indent_down(); + indent(out) << "</xsd:complexType>" << endl; + indent_down(); + indent(out) << "</xsd:element>" << endl; + } + } +} + + +void t_xsd_generator::generate_service(t_service* tservice) { + // Make output file + string f_xsd_name = get_out_dir() + tservice->get_name() + ".xsd"; + f_xsd_.open(f_xsd_name.c_str()); + + string ns = program_->get_namespace("xsd"); + const std::map<std::string, std::string> annot = program_->get_namespace_annotations("xsd"); + const std::map<std::string, std::string>::const_iterator uri = annot.find("uri"); + if (uri != annot.end()) { + ns = uri->second; + } + if (ns.size() > 0) { + ns = " targetNamespace=\"" + ns + "\" xmlns=\"" + ns + "\" " + + "elementFormDefault=\"qualified\""; + } + + // Print the XSD header + f_xsd_ << "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" << endl + << "<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"" << ns << ">" << endl + << xml_autogen_comment() + << endl; + + // Print out the type definitions + indent(f_xsd_) << s_xsd_types_.str(); + + // Keep a list of all the possible exceptions that might get thrown + map<string, t_struct*> all_xceptions; + + // List the elements that you might actually get + vector<t_function*> functions = tservice->get_functions(); + vector<t_function*>::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + string elemname = (*f_iter)->get_name() + "_response"; + t_type* returntype = (*f_iter)->get_returntype(); + generate_element(f_xsd_, elemname, returntype); + f_xsd_ << endl; + + t_struct* xs = (*f_iter)->get_xceptions(); + const std::vector<t_field*>& xceptions = xs->get_members(); + vector<t_field*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + all_xceptions[(*x_iter)->get_name()] = (t_struct*)((*x_iter)->get_type()); + } + } + + map<string, t_struct*>::iterator ax_iter; + for (ax_iter = all_xceptions.begin(); ax_iter != all_xceptions.end(); ++ax_iter) { + generate_element(f_xsd_, ax_iter->first, ax_iter->second); + } + + // Close the XSD document + f_xsd_ << endl << "</xsd:schema>" << endl; + f_xsd_.close(); +} + +string t_xsd_generator::type_name(t_type* ttype) { + if (ttype->is_typedef()) { + return ttype->get_name(); + } + + if (ttype->is_base_type()) { + return xsd(base_type_name(((t_base_type*)ttype)->get_base())); + } + + if (ttype->is_enum()) { + return xsd("int"); + } + + if (ttype->is_struct() || ttype->is_xception()) { + return ttype->get_name(); + } + + return "container"; +} + +/** + * Returns the XSD type that corresponds to the thrift type. + * + * @param tbase The base type + * @return Explicit XSD type, i.e. xsd:string + */ +string t_xsd_generator::base_type_name(t_base_type::t_base tbase) { + switch (tbase) { + case t_base_type::TYPE_VOID: + return "void"; + case t_base_type::TYPE_STRING: + return "string"; + case t_base_type::TYPE_BOOL: + return "boolean"; + case t_base_type::TYPE_I8: + return "byte"; + case t_base_type::TYPE_I16: + return "short"; + case t_base_type::TYPE_I32: + return "int"; + case t_base_type::TYPE_I64: + return "long"; + case t_base_type::TYPE_DOUBLE: + return "decimal"; + default: + throw "compiler error: no XSD base type name for base type " + t_base_type::t_base_name(tbase); + } +} + +THRIFT_REGISTER_GENERATOR(xsd, "XSD", "") diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/thrift-t_php_generator.o-a60a38e9 b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/thrift-t_php_generator.o-a60a38e9 new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/generate/thrift-t_php_generator.o-a60a38e9 diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/globals.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/globals.h new file mode 100644 index 000000000..961c6ef8a --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/globals.h @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef T_GLOBALS_H +#define T_GLOBALS_H + +#include <set> +#include <queue> +#include <stack> +#include <vector> +#include <string> + +/** + * This module contains all the global variables (slap on the wrist) that are + * shared throughout the program. The reason for this is to facilitate simple + * interaction between the parser and the rest of the program. Before calling + * yyparse(), the main.cc program will make necessary adjustments to these + * global variables such that the parser does the right thing and puts entries + * into the right containers, etc. + * + */ + +/** + * Hooray for forward declaration of types! + */ + +class t_program; +class t_scope; +class t_type; + +/** + * Parsing mode, two passes up in this gin rummy! + */ + +enum PARSE_MODE { INCLUDES = 1, PROGRAM = 2 }; + +/** + * Strictness level + */ +extern int g_strict; + +/** + * The master program parse tree. This is accessed from within the parser code + * to build up the program elements. + */ +extern t_program* g_program; + +/** + * The scope that we are currently parsing into + */ +extern t_scope* g_scope; + +/** + * The parent scope to also load symbols into + */ +extern t_scope* g_parent_scope; + +/** + * The prefix for the parent scope entries + */ +extern std::string g_parent_prefix; + +/** + * The parsing pass that we are on. We do different things on each pass. + */ +extern PARSE_MODE g_parse_mode; + +/** + * Global time string, used in formatting error messages etc. + */ +extern char* g_time_str; + +/** + * The last parsed doctext comment. + */ +extern char* g_doctext; + +/** + * The location of the last parsed doctext comment. + */ +extern int g_doctext_lineno; + +/** + * Status of program level doctext candidate + */ +enum PROGDOCTEXT_STATUS { + INVALID = 0, + STILL_CANDIDATE = 1, // the text may or may not be the program doctext + ALREADY_PROCESSED = 2, // doctext has been used and is no longer available + ABSOLUTELY_SURE = 3, // this is the program doctext + NO_PROGRAM_DOCTEXT = 4 // there is no program doctext +}; + +/** + * The program level doctext. Stored separately to make parsing easier. + */ +extern char* g_program_doctext_candidate; +extern int g_program_doctext_lineno; +extern PROGDOCTEXT_STATUS g_program_doctext_status; + +/** + * Whether or not negative field keys are accepted. + * + * When a field does not have a user-specified key, thrift automatically + * assigns a negative value. However, this is fragile since changes to the + * file may unintentionally change the key numbering, resulting in a new + * protocol that is not backwards compatible. + * + * When g_allow_neg_field_keys is enabled, users can explicitly specify + * negative keys. This way they can write a .thrift file with explicitly + * specified keys that is still backwards compatible with older .thrift files + * that did not specify key values. + */ +extern int g_allow_neg_field_keys; + +/** + * Whether or not 64-bit constants will generate a warning. + * + * Some languages don't support 64-bit constants, but many do, so we can + * suppress this warning for projects that don't use any non-64-bit-safe + * languages. + */ +extern int g_allow_64bit_consts; + +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/logging.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/logging.cc new file mode 100644 index 000000000..4811c4ef1 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/logging.cc @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "thrift/logging.h" +#include "thrift/globals.h" +#include <cstdarg> +#include <cstdio> +#include <cstdlib> + +int g_debug = 0; +int g_warn = 1; +int g_verbose = 0; + +void pdebug(const char* fmt, ...) { + if (g_debug == 0) { + return; + } + va_list args; + // printf("[PARSE:%d] ", yylineno); + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + printf("\n"); +} + +void pverbose(const char* fmt, ...) { + if (g_verbose == 0) { + return; + } + va_list args; + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); +} + +void pwarning(int level, const char* fmt, ...) { + if (g_warn < level) { + return; + } + va_list args; + // printf("[WARNING:%s:%d] ", g_curpath.c_str(), yylineno); + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + printf("\n"); +} + +void failure(const char* fmt, ...) { + va_list args; + // fprintf(stderr, "[FAILURE:%s:%d] ", g_curpath.c_str(), yylineno); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + printf("\n"); + exit(1); +} diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/logging.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/logging.h new file mode 100644 index 000000000..ebefbf229 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/logging.h @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef T_LOGGING_H +#define T_LOGGING_H + +extern int g_debug; +extern int g_warn; +extern int g_verbose; + +/** + * Parse debugging output, used to print helpful info + */ +void pdebug(const char* fmt, ...); + +/** + * Parser warning + */ +void pwarning(int level, const char* fmt, ...); + +/** + * Print verbose output message + */ +void pverbose(const char* fmt, ...); + +/** + * Failure! + */ +void failure(const char* fmt, ...); + +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/main.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/main.cc new file mode 100644 index 000000000..03e0d6f48 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/main.cc @@ -0,0 +1,1290 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * thrift - a lightweight cross-language rpc/serialization tool + * + * This file contains the main compiler engine for Thrift, which invokes the + * scanner/parser to build the thrift object tree. The interface generation + * code for each language lives in a file by the language name under the + * generate/ folder, and all parse structures live in parse/ + * + */ + +#include <cassert> +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <time.h> +#include <string> +#include <algorithm> +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <limits.h> + +#ifdef _WIN32 +#include <windows.h> /* for GetFullPathName */ +#endif + +// Careful: must include globals first for extern definitions +#include "thrift/common.h" +#include "thrift/globals.h" + +#include "thrift/platform.h" +#include "thrift/main.h" +#include "thrift/parse/t_program.h" +#include "thrift/parse/t_scope.h" +#include "thrift/generate/t_generator.h" +#include "thrift/audit/t_audit.h" + +#include "thrift/version.h" + +using namespace std; + +/** + * Global program tree + */ +t_program* g_program; + +/** + * Global scope + */ +t_scope* g_scope; + +/** + * Parent scope to also parse types + */ +t_scope* g_parent_scope; + +/** + * Prefix for putting types in parent scope + */ +string g_parent_prefix; + +/** + * Parsing pass + */ +PARSE_MODE g_parse_mode; + +/** + * Current directory of file being parsed + */ +string g_curdir; + +/** + * Current file being parsed + */ +string g_curpath; + +/** + * Search path for inclusions + */ +vector<string> g_incl_searchpath; + +/** + * Global debug state + */ +int g_debug = 0; + +/** + * Strictness level + */ +int g_strict = 127; + +/** + * Warning level + */ +int g_warn = 1; + +/** + * Verbose output + */ +int g_verbose = 0; + +/** + * Global time string + */ +char* g_time_str; + +/** + * The last parsed doctext comment. + */ +char* g_doctext; + +/** + * The First doctext comment + */ +char* g_program_doctext_candidate; + +/** + * Whether or not negative field keys are accepted. + */ +int g_allow_neg_field_keys; + +/** + * Whether or not 64-bit constants will generate a warning. + */ +int g_allow_64bit_consts = 0; + +/** + * Flags to control code generation + */ +bool gen_recurse = false; + +/** + * Flags to control thrift audit + */ +bool g_audit = false; + +/** + * Flag to control return status + */ +bool g_return_failure = false; +bool g_audit_fatal = true; +bool g_generator_failure = false; + +/** + * Win32 doesn't have realpath, so use fallback implementation in that case, + * otherwise this just calls through to realpath + */ +char* saferealpath(const char* path, char* resolved_path) { +#ifdef _WIN32 + char buf[MAX_PATH]; + char* basename; + DWORD len = GetFullPathNameA(path, MAX_PATH, buf, &basename); + if (len == 0 || len > MAX_PATH - 1) { + strcpy(resolved_path, path); + } else { + strcpy(resolved_path, buf); + } + + // Replace backslashes with forward slashes so the + // rest of the code behaves correctly. + size_t resolved_len = strlen(resolved_path); + for (size_t i = 0; i < resolved_len; i++) { + if (resolved_path[i] == '\\') { + resolved_path[i] = '/'; + } + } + return resolved_path; +#else + return realpath(path, resolved_path); +#endif +} + +bool check_is_directory(const char* dir_name) { +#ifdef _WIN32 + DWORD attributes = ::GetFileAttributesA(dir_name); + if (attributes == INVALID_FILE_ATTRIBUTES) { + fprintf(stderr, + "Output directory %s is unusable: GetLastError() = %ld\n", + dir_name, + GetLastError()); + return false; + } + if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != FILE_ATTRIBUTE_DIRECTORY) { + fprintf(stderr, "Output directory %s exists but is not a directory\n", dir_name); + return false; + } + return true; +#else + struct stat sb; + if (stat(dir_name, &sb) < 0) { + fprintf(stderr, "Output directory %s is unusable: %s\n", dir_name, strerror(errno)); + return false; + } + if (!S_ISDIR(sb.st_mode)) { + fprintf(stderr, "Output directory %s exists but is not a directory\n", dir_name); + return false; + } + return true; +#endif +} + +/** + * Report an error to the user. This is called yyerror for historical + * reasons (lex and yacc expect the error reporting routine to be called + * this). Call this function to report any errors to the user. + * yyerror takes printf style arguments. + * + * @param fmt C format string followed by additional arguments + */ +void yyerror(const char* fmt, ...) { + va_list args; + fprintf(stderr, "[ERROR:%s:%d] (last token was '%s')\n", g_curpath.c_str(), yylineno, yytext); + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + + fprintf(stderr, "\n"); +} + +/** + * Prints a debug message from the parser. + * + * @param fmt C format string followed by additional arguments + */ +void pdebug(const char* fmt, ...) { + if (g_debug == 0) { + return; + } + va_list args; + printf("[PARSE:%d] ", yylineno); + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + printf("\n"); +} + +/** + * Prints a verbose output mode message + * + * @param fmt C format string followed by additional arguments + */ +void pverbose(const char* fmt, ...) { + if (g_verbose == 0) { + return; + } + va_list args; + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); +} + +/** + * Prints a warning message + * + * @param fmt C format string followed by additional arguments + */ +void pwarning(int level, const char* fmt, ...) { + if (g_warn < level) { + return; + } + va_list args; + printf("[WARNING:%s:%d] ", g_curpath.c_str(), yylineno); + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + printf("\n"); +} + +/** + * Prints a failure message and exits + * + * @param fmt C format string followed by additional arguments + */ +void failure(const char* fmt, ...) { + va_list args; + fprintf(stderr, "[FAILURE:%s:%d] ", g_curpath.c_str(), yylineno); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + printf("\n"); + exit(1); +} + +/** + * Converts a string filename into a thrift program name + */ +string program_name(string filename) { + string::size_type slash = filename.rfind("/"); + if (slash != string::npos) { + filename = filename.substr(slash + 1); + } + string::size_type dot = filename.rfind("."); + if (dot != string::npos) { + filename = filename.substr(0, dot); + } + return filename; +} + +/** + * Gets the directory path of a filename + */ +string directory_name(string filename) { + string::size_type slash = filename.rfind("/"); + // No slash, just use the current directory + if (slash == string::npos) { + return "."; + } + return filename.substr(0, slash); +} + +/** + * Finds the appropriate file path for the given filename + */ +string include_file(string filename) { + // Absolute path? Just try that + if (filename[0] == '/') { + // Realpath! + char rp[THRIFT_PATH_MAX]; + // cppcheck-suppress uninitvar + if (saferealpath(filename.c_str(), rp) == NULL) { + pwarning(0, "Cannot open include file %s\n", filename.c_str()); + return std::string(); + } + + // Stat this file + struct stat finfo; + if (stat(rp, &finfo) == 0) { + return rp; + } + } else { // relative path, start searching + // new search path with current dir global + vector<string> sp = g_incl_searchpath; + sp.insert(sp.begin(), g_curdir); + + // iterate through paths + vector<string>::iterator it; + for (it = sp.begin(); it != sp.end(); it++) { + string sfilename = *(it) + "/" + filename; + + // Realpath! + char rp[THRIFT_PATH_MAX]; + // cppcheck-suppress uninitvar + if (saferealpath(sfilename.c_str(), rp) == NULL) { + continue; + } + + // Stat this files + struct stat finfo; + if (stat(rp, &finfo) == 0) { + return rp; + } + } + } + + // Uh oh + pwarning(0, "Could not find include file %s\n", filename.c_str()); + return std::string(); +} + +/** + * Clears any previously stored doctext string. + * Also prints a warning if we are discarding information. + */ +void clear_doctext() { + if (g_doctext != NULL) { + pwarning(2, "Uncaptured doctext at on line %d.", g_doctext_lineno); + } + free(g_doctext); + g_doctext = NULL; +} + +/** + * Reset program doctext information after processing a file + */ +void reset_program_doctext_info() { + if (g_program_doctext_candidate != NULL) { + free(g_program_doctext_candidate); + g_program_doctext_candidate = NULL; + } + g_program_doctext_lineno = 0; + g_program_doctext_status = INVALID; + pdebug("%s", "program doctext set to INVALID"); +} + +/** + * We are sure the program doctext candidate is really the program doctext. + */ +void declare_valid_program_doctext() { + if ((g_program_doctext_candidate != NULL) && (g_program_doctext_status == STILL_CANDIDATE)) { + g_program_doctext_status = ABSOLUTELY_SURE; + pdebug("%s", "program doctext set to ABSOLUTELY_SURE"); + } else { + g_program_doctext_status = NO_PROGRAM_DOCTEXT; + pdebug("%s", "program doctext set to NO_PROGRAM_DOCTEXT"); + } +} + +/** + * Cleans up text commonly found in doxygen-like comments + * + * Warning: if you mix tabs and spaces in a non-uniform way, + * you will get what you deserve. + */ +char* clean_up_doctext(char* doctext) { + // Convert to C++ string, and remove Windows's carriage returns. + string docstring = doctext; + docstring.erase(remove(docstring.begin(), docstring.end(), '\r'), docstring.end()); + + // Separate into lines. + vector<string> lines; + string::size_type pos = string::npos; + string::size_type last; + while (true) { + last = (pos == string::npos) ? 0 : pos + 1; + pos = docstring.find('\n', last); + if (pos == string::npos) { + // First bit of cleaning. If the last line is only whitespace, drop it. + string::size_type nonwhite = docstring.find_first_not_of(" \t", last); + if (nonwhite != string::npos) { + lines.push_back(docstring.substr(last)); + } + break; + } + lines.push_back(docstring.substr(last, pos - last)); + } + + // A very profound docstring. + if (lines.empty()) { + return NULL; + } + + // Clear leading whitespace from the first line. + pos = lines.front().find_first_not_of(" \t"); + lines.front().erase(0, pos); + + // If every nonblank line after the first has the same number of spaces/tabs, + // then a star, remove them. + bool have_prefix = true; + bool found_prefix = false; + string::size_type prefix_len = 0; + vector<string>::iterator l_iter; + for (l_iter = lines.begin() + 1; l_iter != lines.end(); ++l_iter) { + if (l_iter->empty()) { + continue; + } + + pos = l_iter->find_first_not_of(" \t"); + if (!found_prefix) { + if (pos != string::npos) { + if (l_iter->at(pos) == '*') { + found_prefix = true; + prefix_len = pos; + } else { + have_prefix = false; + break; + } + } else { + // Whitespace-only line. Truncate it. + l_iter->clear(); + } + } else if (l_iter->size() > pos && l_iter->at(pos) == '*' && pos == prefix_len) { + // Business as usual. + } else if (pos == string::npos) { + // Whitespace-only line. Let's truncate it for them. + l_iter->clear(); + } else { + // The pattern has been broken. + have_prefix = false; + break; + } + } + + // If our prefix survived, delete it from every line. + if (have_prefix) { + // Get the star too. + prefix_len++; + for (l_iter = lines.begin() + 1; l_iter != lines.end(); ++l_iter) { + l_iter->erase(0, prefix_len); + } + } + + // Now delete the minimum amount of leading whitespace from each line. + prefix_len = string::npos; + for (l_iter = lines.begin() + 1; l_iter != lines.end(); ++l_iter) { + if (l_iter->empty()) { + continue; + } + pos = l_iter->find_first_not_of(" \t"); + if (pos != string::npos && (prefix_len == string::npos || pos < prefix_len)) { + prefix_len = pos; + } + } + + // If our prefix survived, delete it from every line. + if (prefix_len != string::npos) { + for (l_iter = lines.begin() + 1; l_iter != lines.end(); ++l_iter) { + l_iter->erase(0, prefix_len); + } + } + + // Remove trailing whitespace from every line. + for (l_iter = lines.begin(); l_iter != lines.end(); ++l_iter) { + pos = l_iter->find_last_not_of(" \t"); + if (pos != string::npos && pos != l_iter->length() - 1) { + l_iter->erase(pos + 1); + } + } + + // If the first line is empty, remove it. + // Don't do this earlier because a lot of steps skip the first line. + if (lines.front().empty()) { + lines.erase(lines.begin()); + } + + // Now rejoin the lines and copy them back into doctext. + docstring.clear(); + for (l_iter = lines.begin(); l_iter != lines.end(); ++l_iter) { + docstring += *l_iter; + docstring += '\n'; + } + + // assert(docstring.length() <= strlen(doctext)); may happen, see THRIFT-1755 + if (docstring.length() <= strlen(doctext)) { + strcpy(doctext, docstring.c_str()); + } else { + free(doctext); // too short + doctext = strdup(docstring.c_str()); + } + return doctext; +} + +/** Set to true to debug docstring parsing */ +static bool dump_docs = false; + +/** + * Dumps docstrings to stdout + * Only works for top-level definitions and the whole program doc + * (i.e., not enum constants, struct fields, or functions. + */ +void dump_docstrings(t_program* program) { + string progdoc = program->get_doc(); + if (!progdoc.empty()) { + printf("Whole program doc:\n%s\n", progdoc.c_str()); + } + const vector<t_typedef*>& typedefs = program->get_typedefs(); + vector<t_typedef*>::const_iterator t_iter; + for (t_iter = typedefs.begin(); t_iter != typedefs.end(); ++t_iter) { + t_typedef* td = *t_iter; + if (td->has_doc()) { + printf("typedef %s:\n%s\n", td->get_name().c_str(), td->get_doc().c_str()); + } + } + const vector<t_enum*>& enums = program->get_enums(); + vector<t_enum*>::const_iterator e_iter; + for (e_iter = enums.begin(); e_iter != enums.end(); ++e_iter) { + t_enum* en = *e_iter; + if (en->has_doc()) { + printf("enum %s:\n%s\n", en->get_name().c_str(), en->get_doc().c_str()); + } + } + const vector<t_const*>& consts = program->get_consts(); + vector<t_const*>::const_iterator c_iter; + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + t_const* co = *c_iter; + if (co->has_doc()) { + printf("const %s:\n%s\n", co->get_name().c_str(), co->get_doc().c_str()); + } + } + const vector<t_struct*>& structs = program->get_structs(); + vector<t_struct*>::const_iterator s_iter; + for (s_iter = structs.begin(); s_iter != structs.end(); ++s_iter) { + t_struct* st = *s_iter; + if (st->has_doc()) { + printf("struct %s:\n%s\n", st->get_name().c_str(), st->get_doc().c_str()); + } + } + const vector<t_struct*>& xceptions = program->get_xceptions(); + vector<t_struct*>::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + t_struct* xn = *x_iter; + if (xn->has_doc()) { + printf("xception %s:\n%s\n", xn->get_name().c_str(), xn->get_doc().c_str()); + } + } + const vector<t_service*>& services = program->get_services(); + vector<t_service*>::const_iterator v_iter; + for (v_iter = services.begin(); v_iter != services.end(); ++v_iter) { + t_service* sv = *v_iter; + if (sv->has_doc()) { + printf("service %s:\n%s\n", sv->get_name().c_str(), sv->get_doc().c_str()); + } + } +} + +/** + * Emits a warning on list<byte>, binary type is typically a much better choice. + */ +void check_for_list_of_bytes(t_type* list_elem_type) { + if ((g_parse_mode == PROGRAM) && (list_elem_type != NULL) && list_elem_type->is_base_type()) { + t_base_type* tbase = (t_base_type*)list_elem_type; + if (tbase->get_base() == t_base_type::TYPE_I8) { + pwarning(1, "Consider using the more efficient \"binary\" type instead of \"list<byte>\"."); + } + } +} + +static bool g_byte_warning_emitted = false; + +/** + * Emits a one-time warning on byte type, promoting the new i8 type instead + */ +void emit_byte_type_warning() { + if (!g_byte_warning_emitted) { + pwarning(1, + "The \"byte\" type is a compatibility alias for \"i8\". Use \"i8\" to emphasize the " + "signedness of this type.\n"); + g_byte_warning_emitted = true; + } +} + +/** + * Prints deprecation notice for old NS declarations that are no longer supported + * If new_form is NULL, old_form is assumed to be a language identifier, such as "cpp" + * If new_form is not NULL, both arguments are used exactly as given + */ +void error_unsupported_namespace_decl(const char* old_form, const char* new_form) { + const char* remainder = ""; + if( new_form == NULL) { + new_form = old_form; + remainder = "_namespace"; + } + failure("Unsupported declaration '%s%s'. Use 'namespace %s' instead.", old_form, remainder, new_form); +} + +/** + * Prints the version number + */ +void version() { + printf("Thrift version %s\n", THRIFT_VERSION); +} + +/** + * Display the usage message and then exit with an error code. + */ +void usage() { + fprintf(stderr, "Usage: thrift [options] file\n\n"); + fprintf(stderr, "Use thrift -help for a list of options\n"); + exit(1); +} + +/** + * Diplays the help message and then exits with an error code. + */ +void help() { + fprintf(stderr, "Usage: thrift [options] file\n"); + fprintf(stderr, "Options:\n"); + fprintf(stderr, " -version Print the compiler version\n"); + fprintf(stderr, " -o dir Set the output directory for gen-* packages\n"); + fprintf(stderr, " (default: current directory)\n"); + fprintf(stderr, " -out dir Set the ouput location for generated files.\n"); + fprintf(stderr, " (no gen-* folder will be created)\n"); + fprintf(stderr, " -I dir Add a directory to the list of directories\n"); + fprintf(stderr, " searched for include directives\n"); + fprintf(stderr, " -nowarn Suppress all compiler warnings (BAD!)\n"); + fprintf(stderr, " -strict Strict compiler warnings on\n"); + fprintf(stderr, " -v[erbose] Verbose mode\n"); + fprintf(stderr, " -r[ecurse] Also generate included files\n"); + fprintf(stderr, " -debug Parse debug trace to stdout\n"); + fprintf(stderr, + " --allow-neg-keys Allow negative field keys (Used to " + "preserve protocol\n"); + fprintf(stderr, " compatibility with older .thrift files)\n"); + fprintf(stderr, " --allow-64bit-consts Do not print warnings about using 64-bit constants\n"); + fprintf(stderr, " --gen STR Generate code with a dynamically-registered generator.\n"); + fprintf(stderr, " STR has the form language[:key1=val1[,key2[,key3=val3]]].\n"); + fprintf(stderr, " Keys and values are options passed to the generator.\n"); + fprintf(stderr, " Many options will not require values.\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "Options related to audit operation\n"); + fprintf(stderr, " --audit OldFile Old Thrift file to be audited with 'file'\n"); + fprintf(stderr, " -Iold dir Add a directory to the list of directories\n"); + fprintf(stderr, " searched for include directives for old thrift file\n"); + fprintf(stderr, " -Inew dir Add a directory to the list of directories\n"); + fprintf(stderr, " searched for include directives for new thrift file\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "Available generators (and options):\n"); + + t_generator_registry::gen_map_t gen_map = t_generator_registry::get_generator_map(); + t_generator_registry::gen_map_t::iterator iter; + for (iter = gen_map.begin(); iter != gen_map.end(); ++iter) { + fprintf(stderr, + " %s (%s):\n", + iter->second->get_short_name().c_str(), + iter->second->get_long_name().c_str()); + fprintf(stderr, "%s", iter->second->get_documentation().c_str()); + } + exit(1); +} + +/** + * You know, when I started working on Thrift I really thought it wasn't going + * to become a programming language because it was just a generator and it + * wouldn't need runtime type information and all that jazz. But then we + * decided to add constants, and all of a sudden that means runtime type + * validation and inference, except the "runtime" is the code generator + * runtime. + */ +void validate_const_rec(std::string name, t_type* type, t_const_value* value) { + if (type->is_void()) { + throw "type error: cannot declare a void const: " + name; + } + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + if (value->get_type() != t_const_value::CV_STRING) { + throw "type error: const \"" + name + "\" was declared as string"; + } + break; + case t_base_type::TYPE_BOOL: + if (value->get_type() != t_const_value::CV_INTEGER) { + throw "type error: const \"" + name + "\" was declared as bool"; + } + break; + case t_base_type::TYPE_I8: + if (value->get_type() != t_const_value::CV_INTEGER) { + throw "type error: const \"" + name + "\" was declared as byte"; + } + break; + case t_base_type::TYPE_I16: + if (value->get_type() != t_const_value::CV_INTEGER) { + throw "type error: const \"" + name + "\" was declared as i16"; + } + break; + case t_base_type::TYPE_I32: + if (value->get_type() != t_const_value::CV_INTEGER) { + throw "type error: const \"" + name + "\" was declared as i32"; + } + break; + case t_base_type::TYPE_I64: + if (value->get_type() != t_const_value::CV_INTEGER) { + throw "type error: const \"" + name + "\" was declared as i64"; + } + break; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() != t_const_value::CV_INTEGER + && value->get_type() != t_const_value::CV_DOUBLE) { + throw "type error: const \"" + name + "\" was declared as double"; + } + break; + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase) + name; + } + } else if (type->is_enum()) { + if (value->get_type() != t_const_value::CV_IDENTIFIER) { + throw "type error: const \"" + name + "\" was declared as enum"; + } + + // see if there's a dot in the identifier + std::string name_portion = value->get_identifier_name(); + + const vector<t_enum_value*>& enum_values = ((t_enum*)type)->get_constants(); + vector<t_enum_value*>::const_iterator c_iter; + bool found = false; + + for (c_iter = enum_values.begin(); c_iter != enum_values.end(); ++c_iter) { + if ((*c_iter)->get_name() == name_portion) { + found = true; + break; + } + } + if (!found) { + throw "type error: const " + name + " was declared as type " + type->get_name() + + " which is an enum, but " + value->get_identifier() + + " is not a valid value for that enum"; + } + } else if (type->is_struct() || type->is_xception()) { + if (value->get_type() != t_const_value::CV_MAP) { + throw "type error: const \"" + name + "\" was declared as struct/xception"; + } + const vector<t_field*>& fields = ((t_struct*)type)->get_members(); + vector<t_field*>::const_iterator f_iter; + + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + if (v_iter->first->get_type() != t_const_value::CV_STRING) { + throw "type error: " + name + " struct key must be string"; + } + t_type* field_type = NULL; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field_type = (*f_iter)->get_type(); + } + } + if (field_type == NULL) { + throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string(); + } + + validate_const_rec(name + "." + v_iter->first->get_string(), field_type, v_iter->second); + } + } else if (type->is_map()) { + t_type* k_type = ((t_map*)type)->get_key_type(); + t_type* v_type = ((t_map*)type)->get_val_type(); + const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map(); + map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + validate_const_rec(name + "<key>", k_type, v_iter->first); + validate_const_rec(name + "<val>", v_type, v_iter->second); + } + } else if (type->is_list() || type->is_set()) { + t_type* e_type; + if (type->is_list()) { + e_type = ((t_list*)type)->get_elem_type(); + } else { + e_type = ((t_set*)type)->get_elem_type(); + } + const vector<t_const_value*>& val = value->get_list(); + vector<t_const_value*>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + validate_const_rec(name + "<elem>", e_type, *v_iter); + } + } +} + +/** + * Check simple identifier names + * It's easier to do it this way instead of rewriting the whole grammar etc. + */ +void validate_simple_identifier(const char* identifier) { + string name(identifier); + if (name.find(".") != string::npos) { + yyerror("Identifier %s can't have a dot.", identifier); + exit(1); + } +} + +/** + * Check the type of the parsed const information against its declared type + */ +void validate_const_type(t_const* c) { + validate_const_rec(c->get_name(), c->get_type(), c->get_value()); +} + +/** + * Check the type of a default value assigned to a field. + */ +void validate_field_value(t_field* field, t_const_value* cv) { + validate_const_rec(field->get_name(), field->get_type(), cv); +} + +/** + * Check that all the elements of a throws block are actually exceptions. + */ +bool validate_throws(t_struct* throws) { + const vector<t_field*>& members = throws->get_members(); + vector<t_field*>::const_iterator m_iter; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if (!t_generator::get_true_type((*m_iter)->get_type())->is_xception()) { + return false; + } + } + return true; +} + +/** + * Skips UTF-8 BOM if there is one + */ +bool skip_utf8_bom(FILE* f) { + + // pretty straightforward, but works + if (fgetc(f) == 0xEF) { + if (fgetc(f) == 0xBB) { + if (fgetc(f) == 0xBF) { + return true; + } + } + } + + rewind(f); + return false; +} + +/** + * Parses a program + */ +void parse(t_program* program, t_program* parent_program) { + // Get scope file path + string path = program->get_path(); + + // Set current dir global, which is used in the include_file function + g_curdir = directory_name(path); + g_curpath = path; + + // Open the file + // skip UTF-8 BOM if there is one + yyin = fopen(path.c_str(), "r"); + if (yyin == 0) { + failure("Could not open input file: \"%s\"", path.c_str()); + } + if (skip_utf8_bom(yyin)) + pverbose("Skipped UTF-8 BOM at %s\n", path.c_str()); + + // Create new scope and scan for includes + pverbose("Scanning %s for includes\n", path.c_str()); + g_parse_mode = INCLUDES; + g_program = program; + g_scope = program->scope(); + try { + yylineno = 1; + if (yyparse() != 0) { + failure("Parser error during include pass."); + } + } catch (string x) { + failure(x.c_str()); + } + fclose(yyin); + + // Recursively parse all the include programs + vector<t_program*>& includes = program->get_includes(); + vector<t_program*>::iterator iter; + for (iter = includes.begin(); iter != includes.end(); ++iter) { + parse(*iter, program); + } + + // reset program doctext status before parsing a new file + reset_program_doctext_info(); + + // Parse the program file + g_parse_mode = PROGRAM; + g_program = program; + g_scope = program->scope(); + g_parent_scope = (parent_program != NULL) ? parent_program->scope() : NULL; + g_parent_prefix = program->get_name() + "."; + g_curpath = path; + + // Open the file + // skip UTF-8 BOM if there is one + yyin = fopen(path.c_str(), "r"); + if (yyin == 0) { + failure("Could not open input file: \"%s\"", path.c_str()); + } + if (skip_utf8_bom(yyin)) + pverbose("Skipped UTF-8 BOM at %s\n", path.c_str()); + + pverbose("Parsing %s for types\n", path.c_str()); + yylineno = 1; + try { + if (yyparse() != 0) { + failure("Parser error during types pass."); + } + } catch (string x) { + failure(x.c_str()); + } + fclose(yyin); +} + +/** + * Generate code + */ +void generate(t_program* program, const vector<string>& generator_strings) { + // Oooohh, recursive code generation, hot!! + if (gen_recurse) { + program->set_recursive(true); + const vector<t_program*>& includes = program->get_includes(); + for (auto include : includes) { + // Propagate output path from parent to child programs + include->set_out_path(program->get_out_path(), program->is_out_path_absolute()); + + generate(include, generator_strings); + } + } + + // Generate code! + try { + pverbose("Program: %s\n", program->get_path().c_str()); + + if (dump_docs) { + dump_docstrings(program); + } + + vector<string>::const_iterator iter; + for (iter = generator_strings.begin(); iter != generator_strings.end(); ++iter) { + t_generator* generator = t_generator_registry::get_generator(program, *iter); + + if (generator == NULL) { + pwarning(1, "Unable to get a generator for \"%s\".\n", iter->c_str()); + g_generator_failure = true; + } else if (generator) { + generator->validate_input(); + pverbose("Generating \"%s\"\n", iter->c_str()); + generator->generate_program(); + delete generator; + } + } + } catch (string s) { + failure("Error: %s\n", s.c_str()); + } catch (const char* exc) { + failure("Error: %s\n", exc); + } catch (const std::invalid_argument& invalid_argument_exception) { + failure("Error: %s\n", invalid_argument_exception.what()); + } +} + +void audit(t_program* new_program, + t_program* old_program, + string new_thrift_include_path, + string old_thrift_include_path) { + vector<string> temp_incl_searchpath = g_incl_searchpath; + if (!old_thrift_include_path.empty()) { + g_incl_searchpath.push_back(old_thrift_include_path); + } + + parse(old_program, NULL); + + g_incl_searchpath = temp_incl_searchpath; + if (!new_thrift_include_path.empty()) { + g_incl_searchpath.push_back(new_thrift_include_path); + } + + parse(new_program, NULL); + + compare_namespace(new_program, old_program); + compare_services(new_program->get_services(), old_program->get_services()); + compare_enums(new_program->get_enums(), old_program->get_enums()); + compare_structs(new_program->get_structs(), old_program->get_structs()); + compare_structs(new_program->get_xceptions(), old_program->get_xceptions()); + compare_consts(new_program->get_consts(), old_program->get_consts()); +} + +/** + * Parse it up.. then spit it back out, in pretty much every language. Alright + * not that many languages, but the cool ones that we care about. + */ +int main(int argc, char** argv) { + int i; + std::string out_path; + bool out_path_is_absolute = false; + + // Setup time string + time_t now = time(NULL); + g_time_str = ctime(&now); + + // Check for necessary arguments, you gotta have at least a filename and + // an output language flag + if (argc < 2) { + usage(); + } + + vector<string> generator_strings; + string old_thrift_include_path; + string new_thrift_include_path; + string old_input_file; + + // Set the current path to a dummy value to make warning messages clearer. + g_curpath = "arguments"; + + // Hacky parameter handling... I didn't feel like using a library sorry! + for (i = 1; i < argc - 1; i++) { + char* arg; + + arg = strtok(argv[i], " "); + while (arg != NULL) { + // Treat double dashes as single dashes + if (arg[0] == '-' && arg[1] == '-') { + ++arg; + } + + if (strcmp(arg, "-help") == 0) { + help(); + } else if (strcmp(arg, "-version") == 0) { + version(); + exit(0); + } else if (strcmp(arg, "-debug") == 0) { + g_debug = 1; + } else if (strcmp(arg, "-nowarn") == 0) { + g_warn = 0; + } else if (strcmp(arg, "-strict") == 0) { + g_strict = 255; + g_warn = 2; + } else if (strcmp(arg, "-v") == 0 || strcmp(arg, "-verbose") == 0) { + g_verbose = 1; + } else if (strcmp(arg, "-r") == 0 || strcmp(arg, "-recurse") == 0) { + gen_recurse = true; + } else if (strcmp(arg, "-allow-neg-keys") == 0) { + g_allow_neg_field_keys = true; + } else if (strcmp(arg, "-allow-64bit-consts") == 0) { + g_allow_64bit_consts = true; + } else if (strcmp(arg, "-gen") == 0) { + arg = argv[++i]; + if (arg == NULL) { + fprintf(stderr, "Missing generator specification\n"); + usage(); + } + generator_strings.emplace_back(arg); + } else if (strcmp(arg, "-I") == 0) { + // An argument of "-I\ asdf" is invalid and has unknown results + arg = argv[++i]; + + if (arg == NULL) { + fprintf(stderr, "Missing Include directory\n"); + usage(); + } + g_incl_searchpath.emplace_back(arg); + } else if ((strcmp(arg, "-o") == 0) || (strcmp(arg, "-out") == 0)) { + out_path_is_absolute = (strcmp(arg, "-out") == 0) ? true : false; + arg = argv[++i]; + if (arg == NULL) { + fprintf(stderr, "-o: missing output directory\n"); + usage(); + } + out_path = arg; + +#ifdef _WIN32 + // strip out trailing \ on Windows + std::string::size_type last = out_path.length() - 1; + if (out_path[last] == '\\') { + out_path.erase(last); + } +#endif + if (!check_is_directory(out_path.c_str())) + return -1; + } else if (strcmp(arg, "-audit") == 0) { + g_audit = true; + arg = argv[++i]; + if (arg == NULL) { + fprintf(stderr, "Missing old thrift file name for audit operation\n"); + usage(); + } + char old_thrift_file_rp[THRIFT_PATH_MAX]; + + // cppcheck-suppress uninitvar + if (saferealpath(arg, old_thrift_file_rp) == NULL) { + failure("Could not open input file with realpath: %s", arg); + } + old_input_file = string(old_thrift_file_rp); + } else if (strcmp(arg, "-audit-nofatal") == 0) { + g_audit_fatal = false; + } else if (strcmp(arg, "-Iold") == 0) { + arg = argv[++i]; + if (arg == NULL) { + fprintf(stderr, "Missing Include directory for old thrift file\n"); + usage(); + } + old_thrift_include_path = string(arg); + } else if (strcmp(arg, "-Inew") == 0) { + arg = argv[++i]; + if (arg == NULL) { + fprintf(stderr, "Missing Include directory for new thrift file\n"); + usage(); + } + new_thrift_include_path = string(arg); + } else { + fprintf(stderr, "Unrecognized option: %s\n", arg); + usage(); + } + + // Tokenize more + arg = strtok(NULL, " "); + } + } + + // display help + if ((strcmp(argv[argc - 1], "-help") == 0) || (strcmp(argv[argc - 1], "--help") == 0)) { + help(); + } + + // if you're asking for version, you have a right not to pass a file + if ((strcmp(argv[argc - 1], "-version") == 0) || (strcmp(argv[argc - 1], "--version") == 0)) { + version(); + exit(0); + } + + // Initialize global types + initGlobals(); + + if (g_audit) { + // Audit operation + + if (old_input_file.empty()) { + fprintf(stderr, "Missing file name of old thrift file for audit\n"); + usage(); + } + + char new_thrift_file_rp[THRIFT_PATH_MAX]; + if (argv[i] == NULL) { + fprintf(stderr, "Missing file name of new thrift file for audit\n"); + usage(); + } + // cppcheck-suppress uninitvar + if (saferealpath(argv[i], new_thrift_file_rp) == NULL) { + failure("Could not open input file with realpath: %s", argv[i]); + } + string new_input_file(new_thrift_file_rp); + + t_program new_program(new_input_file); + t_program old_program(old_input_file); + + audit(&new_program, &old_program, new_thrift_include_path, old_thrift_include_path); + + } else { + // Generate options + + // You gotta generate something! + if (generator_strings.empty()) { + fprintf(stderr, "No output language(s) specified\n"); + usage(); + } + + // Real-pathify it + char rp[THRIFT_PATH_MAX]; + if (argv[i] == NULL) { + fprintf(stderr, "Missing file name\n"); + usage(); + } + // cppcheck-suppress uninitvar + if (saferealpath(argv[i], rp) == NULL) { + failure("Could not open input file with realpath: %s", argv[i]); + } + string input_file(rp); + + // Instance of the global parse tree + t_program* program = new t_program(input_file); + if (out_path.size()) { + program->set_out_path(out_path, out_path_is_absolute); + } + + // Compute the cpp include prefix. + // infer this from the filename passed in + string input_filename = argv[i]; + string include_prefix; + + string::size_type last_slash = string::npos; + if ((last_slash = input_filename.rfind("/")) != string::npos) { + include_prefix = input_filename.substr(0, last_slash); + } + + program->set_include_prefix(include_prefix); + + // Parse it! + parse(program, NULL); + + // The current path is not really relevant when we are doing generation. + // Reset the variable to make warning messages clearer. + g_curpath = "generation"; + // Reset yylineno for the heck of it. Use 1 instead of 0 because + // That is what shows up during argument parsing. + yylineno = 1; + + // Generate it! + generate(program, generator_strings); + delete program; + } + + // Clean up. Who am I kidding... this program probably orphans heap memory + // all over the place, but who cares because it is about to exit and it is + // all referenced and used by this wacky parse tree up until now anyways. + clearGlobals(); + + // Finished + if (g_return_failure && g_audit_fatal) { + exit(2); + } + if (g_generator_failure) { + exit(3); + } + // Finished + return 0; +} diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/main.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/main.h new file mode 100644 index 000000000..163b02e4b --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/main.h @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef T_MAIN_H +#define T_MAIN_H + +#include <string> +#include <cstdio> + +#include "thrift/logging.h" + +#include "thrift/parse/t_const.h" +#include "thrift/parse/t_field.h" + +/** + * Defined in the flex library + */ + +extern "C" { int yylex(void); } + +int yyparse(void); + +/** + * Expected to be defined by Flex/Bison + */ +void yyerror(const char* fmt, ...); + +/** + * Check simple identifier names + */ +void validate_simple_identifier(const char* identifier); + +/** + * Check constant types + */ +void validate_const_type(t_const* c); + +/** + * Check constant types + */ +void validate_field_value(t_field* field, t_const_value* cv); + +/** + * Check members of a throws block + */ +bool validate_throws(t_struct* throws); + +/** + * Converts a string filename into a thrift program name + */ +std::string program_name(std::string filename); + +/** + * Gets the directory path of a filename + */ +std::string directory_name(std::string filename); + +/** + * Get the absolute path for an include file + */ +std::string include_file(std::string filename); + +/** + * Clears any previously stored doctext string. + */ +void clear_doctext(); + +/** + * Cleans up text commonly found in doxygen-like comments + */ +char* clean_up_doctext(char* doctext); + +/** + * We are sure the program doctext candidate is really the program doctext. + */ +void declare_valid_program_doctext(); + +/** + * Emits a warning on list<byte>, binary type is typically a much better choice. + */ +void check_for_list_of_bytes(t_type* list_elem_type); + +/** + * Emits a one-time warning on byte type, promoting the new i8 type instead + */ +void emit_byte_type_warning(); + +/** + * Prints deprecation notice for old NS declarations that are no longer supported + * If new_form is NULL, old_form is assumed to be a language identifier, such as "cpp" + * If new_form is not NULL, both arguments are used exactly as given + */ +void error_unsupported_namespace_decl(const char* old_form, const char* new_form = nullptr); + +/** + * Flex utilities + */ + +extern int yylineno; +extern char yytext[]; +extern std::FILE* yyin; + +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/parse.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/parse.cc new file mode 100644 index 000000000..23db61ccb --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/parse.cc @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "thrift/parse/t_type.h" +#include "thrift/parse/t_typedef.h" + +#include "thrift/main.h" + +t_type* t_type::get_true_type() { + return const_cast<t_type*>(const_cast<const t_type*>(this)->get_true_type()); +} + +const t_type* t_type::get_true_type() const { + const t_type* type = this; + while (type->is_typedef()) { + type = ((t_typedef*)type)->get_type(); + } + return type; +} diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_base_type.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_base_type.h new file mode 100644 index 000000000..ca2b0f6ef --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_base_type.h @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef T_BASE_TYPE_H +#define T_BASE_TYPE_H + +#include <cstdlib> +#include "thrift/parse/t_type.h" + +/** + * A thrift base type, which must be one of the defined enumerated types inside + * this definition. + * + */ +class t_base_type : public t_type { +public: + /** + * Enumeration of thrift base types + */ + enum t_base { + TYPE_VOID, + TYPE_STRING, + TYPE_BOOL, + TYPE_I8, + TYPE_I16, + TYPE_I32, + TYPE_I64, + TYPE_DOUBLE + }; + + t_base_type(std::string name, t_base base) + : t_type(name), base_(base), string_list_(false), binary_(false), string_enum_(false) {} + + t_base get_base() const { return base_; } + + bool is_void() const override { return base_ == TYPE_VOID; } + + bool is_string() const override { return base_ == TYPE_STRING; } + + bool is_bool() const override { return base_ == TYPE_BOOL; } + + void set_string_list(bool val) { string_list_ = val; } + + bool is_string_list() const { return string_list_ && (base_ == TYPE_STRING); } + + void set_binary(bool val) { binary_ = val; } + + bool is_binary() const override { return binary_ && (base_ == TYPE_STRING); } + + void set_string_enum(bool val) { string_enum_ = val; } + + bool is_string_enum() const { return string_enum_ && base_ == TYPE_STRING; } + + void add_string_enum_val(std::string val) { string_enum_vals_.push_back(val); } + + const std::vector<std::string>& get_string_enum_vals() const { return string_enum_vals_; } + + bool is_base_type() const override { return true; } + + static std::string t_base_name(t_base tbase) { + switch (tbase) { + case TYPE_VOID: + return "void"; + break; + case TYPE_STRING: + return "string"; + break; + case TYPE_BOOL: + return "bool"; + break; + case TYPE_I8: + return "i8"; + break; + case TYPE_I16: + return "i16"; + break; + case TYPE_I32: + return "i32"; + break; + case TYPE_I64: + return "i64"; + break; + case TYPE_DOUBLE: + return "double"; + break; + default: + return "(unknown)"; + break; + } + } + +private: + t_base base_; + + bool string_list_; + bool binary_; + bool string_enum_; + std::vector<std::string> string_enum_vals_; +}; + +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_const.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_const.h new file mode 100644 index 000000000..3c08e0dec --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_const.h @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef T_CONST_H +#define T_CONST_H + +#include "thrift/parse/t_type.h" +#include "thrift/parse/t_const_value.h" + +/** + * A const is a constant value defined across languages that has a type and + * a value. The trick here is that the declared type might not match the type + * of the value object, since that is not determined until after parsing the + * whole thing out. + * + */ +class t_const : public t_doc { +public: + t_const(t_type* type, std::string name, t_const_value* value) + : type_(type), name_(name), value_(value) {} + + t_type* get_type() const { return type_; } + + std::string get_name() const { return name_; } + + t_const_value* get_value() const { return value_; } + +private: + t_type* type_; + std::string name_; + t_const_value* value_; +}; + +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_const_value.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_const_value.h new file mode 100644 index 000000000..5b8156f1a --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_const_value.h @@ -0,0 +1,204 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef T_CONST_VALUE_H +#define T_CONST_VALUE_H + +#include "thrift/parse/t_enum.h" +#include <stdint.h> +#include <map> +#include <vector> +#include <string> + +/** + * A const value is something parsed that could be a map, set, list, struct + * or whatever. + * + */ +class t_const_value { +public: + /** + * Comparator to sort fields in ascending order by key. + * Make this a functor instead of a function to help GCC inline it. + */ + struct value_compare { + public: + bool operator()(t_const_value const* const& left, t_const_value const* const& right) const { + return *left < *right; + } + }; + + enum t_const_value_type { CV_INTEGER, CV_DOUBLE, CV_STRING, CV_MAP, CV_LIST, CV_IDENTIFIER, CV_UNKNOWN }; + + t_const_value() : intVal_(0), doubleVal_(0.0f), enum_((t_enum*)nullptr), valType_(CV_UNKNOWN) {} + + t_const_value(int64_t val) : doubleVal_(0.0f), enum_((t_enum*)nullptr), valType_(CV_UNKNOWN) { set_integer(val); } + + t_const_value(std::string val) : intVal_(0), doubleVal_(0.0f), enum_((t_enum*)nullptr), valType_(CV_UNKNOWN) { set_string(val); } + + void set_string(std::string val) { + valType_ = CV_STRING; + stringVal_ = val; + } + + std::string get_string() const { return stringVal_; } + + void set_integer(int64_t val) { + valType_ = CV_INTEGER; + intVal_ = val; + } + + int64_t get_integer() const { + if (valType_ == CV_IDENTIFIER) { + if (enum_ == nullptr) { + throw "have identifier \"" + get_identifier() + "\", but unset enum on line!"; + } + std::string identifier = get_identifier(); + std::string::size_type dot = identifier.rfind('.'); + if (dot != std::string::npos) { + identifier = identifier.substr(dot + 1); + } + t_enum_value* val = enum_->get_constant_by_name(identifier); + if (val == nullptr) { + throw "Unable to find enum value \"" + identifier + "\" in enum \"" + enum_->get_name() + + "\""; + } + return val->get_value(); + } else { + return intVal_; + } + } + + void set_double(double val) { + valType_ = CV_DOUBLE; + doubleVal_ = val; + } + + double get_double() const { return doubleVal_; } + + void set_map() { valType_ = CV_MAP; } + + void add_map(t_const_value* key, t_const_value* val) { mapVal_[key] = val; } + + const std::map<t_const_value*, t_const_value*, t_const_value::value_compare>& get_map() const { return mapVal_; } + + void set_list() { valType_ = CV_LIST; } + + void add_list(t_const_value* val) { listVal_.push_back(val); } + + const std::vector<t_const_value*>& get_list() const { return listVal_; } + + void set_identifier(std::string val) { + valType_ = CV_IDENTIFIER; + identifierVal_ = val; + } + + std::string get_identifier() const { return identifierVal_; } + + std::string get_identifier_name() const { + std::string ret = get_identifier(); + size_t s = ret.find('.'); + if (s == std::string::npos) { + throw "error: identifier " + ret + " is unqualified!"; + } + ret = ret.substr(s + 1); + s = ret.find('.'); + if (s != std::string::npos) { + ret = ret.substr(s + 1); + } + return ret; + } + + std::string get_identifier_with_parent() const { + std::string ret = get_identifier(); + size_t s = ret.find('.'); + if (s == std::string::npos) { + throw "error: identifier " + ret + " is unqualified!"; + } + size_t s2 = ret.find('.', s + 1); + if (s2 != std::string::npos) { + ret = ret.substr(s + 1); + } + return ret; + } + + void set_enum(t_enum* tenum) { enum_ = tenum; } + + t_const_value_type get_type() const { if (valType_ == CV_UNKNOWN) { throw std::string("unknown t_const_value"); } return valType_; } + + /** + * Comparator to sort map fields in ascending order by key and then value. + * This is used for map comparison in lexicographic order. + */ + struct map_entry_compare { + private: + typedef std::pair<t_const_value*, t_const_value*> ConstPair; + public: + bool operator()(ConstPair left, ConstPair right) const { + if (*(left.first) < *(right.first)) { + return true; + } else { + if (*(right.first) < *(left.first)) { + return false; + } else { + return *(left.second) < *(right.second); + } + } + } + }; + + bool operator < (const t_const_value& that) const { + ::t_const_value::t_const_value_type t1 = get_type(); + ::t_const_value::t_const_value_type t2 = that.get_type(); + if (t1 != t2) + return t1 < t2; + switch (t1) { + case ::t_const_value::CV_INTEGER: + return intVal_ < that.intVal_; + case ::t_const_value::CV_DOUBLE: + return doubleVal_ < that.doubleVal_; + case ::t_const_value::CV_STRING: + return stringVal_ < that.stringVal_; + case ::t_const_value::CV_IDENTIFIER: + return identifierVal_ < that.identifierVal_; + case ::t_const_value::CV_MAP: + return std::lexicographical_compare( + mapVal_.begin(), mapVal_.end(), that.mapVal_.begin(), that.mapVal_.end(), map_entry_compare()); + case ::t_const_value::CV_LIST: + return std::lexicographical_compare( + listVal_.begin(), listVal_.end(), that.listVal_.begin(), that.listVal_.end(), value_compare()); + case ::t_const_value::CV_UNKNOWN: + default: + throw "unknown value type"; + } + } + +private: + std::map<t_const_value*, t_const_value*, value_compare> mapVal_; + std::vector<t_const_value*> listVal_; + std::string stringVal_; + int64_t intVal_; + double doubleVal_; + std::string identifierVal_; + t_enum* enum_; + + t_const_value_type valType_; +}; + +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_container.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_container.h new file mode 100644 index 000000000..a124d31fd --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_container.h @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef T_CONTAINER_H +#define T_CONTAINER_H + +#include "thrift/parse/t_type.h" + +class t_container : public t_type { +public: + t_container() : cpp_name_(), has_cpp_name_(false) {} + + ~t_container() override = default; + + void set_cpp_name(std::string cpp_name) { + cpp_name_ = cpp_name; + has_cpp_name_ = true; + } + + bool has_cpp_name() const { return has_cpp_name_; } + + std::string get_cpp_name() const { return cpp_name_; } + + bool is_container() const override { return true; } + +private: + std::string cpp_name_; + bool has_cpp_name_; +}; + +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_doc.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_doc.h new file mode 100644 index 000000000..0df893eb2 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_doc.h @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef T_DOC_H +#define T_DOC_H + +#include "thrift/globals.h" +#include "thrift/logging.h" + +/** + * Documentation stubs + * + */ +class t_doc { + +public: + t_doc() : has_doc_(false) {} + virtual ~t_doc() = default; + + void set_doc(const std::string& doc) { + doc_ = doc; + has_doc_ = true; + if ((g_program_doctext_lineno == g_doctext_lineno) + && (g_program_doctext_status == STILL_CANDIDATE)) { + g_program_doctext_status = ALREADY_PROCESSED; + pdebug("%s", "program doctext set to ALREADY_PROCESSED"); + } + } + + const std::string& get_doc() const { return doc_; } + + bool has_doc() { return has_doc_; } + +private: + std::string doc_; + bool has_doc_; +}; + +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_enum.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_enum.h new file mode 100644 index 000000000..3f013ee1f --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_enum.h @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef T_ENUM_H +#define T_ENUM_H + +#include <vector> + +#include "thrift/parse/t_enum_value.h" +#include "thrift/parse/t_type.h" + +/** + * An enumerated type. A list of constant objects with a name for the type. + * + */ +class t_enum : public t_type { +public: + t_enum(t_program* program) : t_type(program) {} + + void set_name(const std::string& name) override { name_ = name; } + + void append(t_enum_value* constant) { constants_.push_back(constant); } + + const std::vector<t_enum_value*>& get_constants() const { return constants_; } + + t_enum_value* get_constant_by_name(const std::string& name) const { + const std::vector<t_enum_value*>& enum_values = get_constants(); + std::vector<t_enum_value*>::const_iterator c_iter; + for (c_iter = enum_values.begin(); c_iter != enum_values.end(); ++c_iter) { + if ((*c_iter)->get_name() == name) { + return *c_iter; + } + } + return nullptr; + } + + t_enum_value* get_constant_by_value(int64_t value) const { + const std::vector<t_enum_value*>& enum_values = get_constants(); + std::vector<t_enum_value*>::const_iterator c_iter; + for (c_iter = enum_values.begin(); c_iter != enum_values.end(); ++c_iter) { + if ((*c_iter)->get_value() == value) { + return *c_iter; + } + } + return nullptr; + } + + t_enum_value* get_min_value() const { + const std::vector<t_enum_value*>& enum_values = get_constants(); + std::vector<t_enum_value*>::const_iterator c_iter; + t_enum_value* min_value; + if (enum_values.size() == 0) { + min_value = nullptr; + } else { + int min_value_value; + min_value = enum_values.front(); + min_value_value = min_value->get_value(); + for (c_iter = enum_values.begin(); c_iter != enum_values.end(); ++c_iter) { + if ((*c_iter)->get_value() < min_value_value) { + min_value = (*c_iter); + min_value_value = min_value->get_value(); + } + } + } + return min_value; + } + + t_enum_value* get_max_value() const { + const std::vector<t_enum_value*>& enum_values = get_constants(); + std::vector<t_enum_value*>::const_iterator c_iter; + t_enum_value* max_value; + if (enum_values.size() == 0) { + max_value = nullptr; + } else { + int max_value_value; + max_value = enum_values.back(); + max_value_value = max_value->get_value(); + for (c_iter = enum_values.begin(); c_iter != enum_values.end(); ++c_iter) { + if ((*c_iter)->get_value() > max_value_value) { + max_value = (*c_iter); + max_value_value = max_value->get_value(); + } + } + } + return max_value; + } + + bool is_enum() const override { return true; } + +private: + std::vector<t_enum_value*> constants_; +}; + +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_enum_value.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_enum_value.h new file mode 100644 index 000000000..70eee8618 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_enum_value.h @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef T_ENUM_VALUE_H +#define T_ENUM_VALUE_H + +#include <map> +#include <string> +#include "thrift/parse/t_doc.h" + +/** + * A constant. These are used inside of enum definitions. Constants are just + * symbol identifiers that may or may not have an explicit value associated + * with them. + * + */ +class t_enum_value : public t_doc { +public: + t_enum_value(std::string name, int value) : name_(name), value_(value) {} + + ~t_enum_value() override = default; + + const std::string& get_name() const { return name_; } + + int get_value() const { return value_; } + + std::map<std::string, std::string> annotations_; + +private: + std::string name_; + int value_; +}; + +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_field.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_field.h new file mode 100644 index 000000000..4be87706d --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_field.h @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef T_FIELD_H +#define T_FIELD_H + +#include <map> +#include <string> +#include <sstream> + +#include "thrift/parse/t_doc.h" +#include "thrift/parse/t_type.h" + +// Forward declare for xsd_attrs +class t_struct; + +/** + * Class to represent a field in a thrift structure. A field has a data type, + * a symbolic name, and a numeric identifier. + * + */ +class t_field : public t_doc { +public: + t_field(t_type* type, std::string name) + : type_(type), + name_(name), + key_(0), + value_(nullptr), + xsd_optional_(false), + xsd_nillable_(false), + xsd_attrs_(nullptr), + reference_(false) {} + + t_field(t_type* type, std::string name, int32_t key) + : type_(type), + name_(name), + key_(key), + req_(T_OPT_IN_REQ_OUT), + value_(nullptr), + xsd_optional_(false), + xsd_nillable_(false), + xsd_attrs_(nullptr), + reference_(false) {} + + ~t_field() override = default; + + t_type* get_type() { return type_; } + + const t_type* get_type() const { return type_; } + + const std::string& get_name() const { return name_; } + + int32_t get_key() const { return key_; } + + enum e_req { T_REQUIRED, T_OPTIONAL, T_OPT_IN_REQ_OUT }; + + void set_req(e_req req) { req_ = req; } + + e_req get_req() const { return req_; } + + void set_value(t_const_value* value) { value_ = value; } + + t_const_value* get_value() { return value_; } + + const t_const_value* get_value() const { return value_; } + + void set_xsd_optional(bool xsd_optional) { xsd_optional_ = xsd_optional; } + + bool get_xsd_optional() const { return xsd_optional_; } + + void set_xsd_nillable(bool xsd_nillable) { xsd_nillable_ = xsd_nillable; } + + bool get_xsd_nillable() const { return xsd_nillable_; } + + void set_xsd_attrs(t_struct* xsd_attrs) { xsd_attrs_ = xsd_attrs; } + + t_struct* get_xsd_attrs() { return xsd_attrs_; } + + const t_struct* get_xsd_attrs() const { return xsd_attrs_; } + + /** + * Comparator to sort fields in ascending order by key. + * Make this a functor instead of a function to help GCC inline it. + * The arguments are (const) references to const pointers to const t_fields. + */ + struct key_compare { + bool operator()(t_field const* const& a, t_field const* const& b) { + return a->get_key() < b->get_key(); + } + }; + + std::map<std::string, std::string> annotations_; + + bool get_reference() const { return reference_; } + + void set_reference(bool reference) { reference_ = reference; } + +private: + t_type* type_; + std::string name_; + int32_t key_; + e_req req_; + t_const_value* value_; + + bool xsd_optional_; + bool xsd_nillable_; + t_struct* xsd_attrs_; + bool reference_; +}; + +/** + * A simple struct for the parser to use to store a field ID, and whether or + * not it was specified by the user or automatically chosen. + */ +struct t_field_id { + int32_t value; + bool auto_assigned; +}; + +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_function.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_function.h new file mode 100644 index 000000000..d30c8a46e --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_function.h @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef T_FUNCTION_H +#define T_FUNCTION_H + +#include <string> +#include "thrift/parse/t_type.h" +#include "thrift/parse/t_struct.h" +#include "thrift/parse/t_doc.h" + +/** + * Representation of a function. Key parts are return type, function name, + * optional modifiers, and an argument list, which is implemented as a thrift + * struct. + * + */ +class t_function : public t_doc { +public: + t_function(t_type* returntype, std::string name, t_struct* arglist, bool oneway = false) + : returntype_(returntype), + name_(name), + arglist_(arglist), + xceptions_(new t_struct(nullptr)), + own_xceptions_(true), + oneway_(oneway) { + if (oneway_ && (!returntype_->is_void())) { + pwarning(1, "Oneway methods should return void.\n"); + } + } + + t_function(t_type* returntype, + std::string name, + t_struct* arglist, + t_struct* xceptions, + bool oneway = false) + : returntype_(returntype), + name_(name), + arglist_(arglist), + xceptions_(xceptions), + own_xceptions_(false), + oneway_(oneway) { + if (oneway_ && !xceptions_->get_members().empty()) { + throw std::string("Oneway methods can't throw exceptions."); + } + if (oneway_ && (!returntype_->is_void())) { + pwarning(1, "Oneway methods should return void.\n"); + } + } + + ~t_function() override { + if (own_xceptions_) + delete xceptions_; + } + + t_type* get_returntype() const { return returntype_; } + + const std::string& get_name() const { return name_; } + + t_struct* get_arglist() const { return arglist_; } + + t_struct* get_xceptions() const { return xceptions_; } + + bool is_oneway() const { return oneway_; } + + std::map<std::string, std::string> annotations_; + +private: + t_type* returntype_; + std::string name_; + t_struct* arglist_; + t_struct* xceptions_; + bool own_xceptions_; + bool oneway_; +}; + +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_list.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_list.h new file mode 100644 index 000000000..f0b896d0c --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_list.h @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef T_LIST_H +#define T_LIST_H + +#include "thrift/parse/t_container.h" + +/** + * A list is a lightweight container type that just wraps another data type. + * + */ +class t_list : public t_container { +public: + t_list(t_type* elem_type) : elem_type_(elem_type) {} + + t_type* get_elem_type() const { return elem_type_; } + + bool is_list() const override { return true; } + +private: + t_type* elem_type_; +}; + +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_map.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_map.h new file mode 100644 index 000000000..9614e6849 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_map.h @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef T_MAP_H +#define T_MAP_H + +#include "thrift/parse/t_container.h" + +/** + * A map is a lightweight container type that just wraps another two data + * types. + * + */ +class t_map : public t_container { +public: + t_map(t_type* key_type, t_type* val_type) : key_type_(key_type), val_type_(val_type) {} + + t_type* get_key_type() const { return key_type_; } + + t_type* get_val_type() const { return val_type_; } + + bool is_map() const override { return true; } + +private: + t_type* key_type_; + t_type* val_type_; +}; + +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_program.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_program.h new file mode 100644 index 000000000..e0b4b6ecd --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_program.h @@ -0,0 +1,416 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef T_PROGRAM_H +#define T_PROGRAM_H + +#include <map> +#include <string> +#include <vector> + +// For program_name() +#include "thrift/main.h" + +#include "thrift/parse/t_doc.h" +#include "thrift/parse/t_scope.h" +#include "thrift/parse/t_base_type.h" +#include "thrift/parse/t_typedef.h" +#include "thrift/parse/t_enum.h" +#include "thrift/parse/t_const.h" +#include "thrift/parse/t_struct.h" +#include "thrift/parse/t_service.h" +#include "thrift/parse/t_list.h" +#include "thrift/parse/t_map.h" +#include "thrift/parse/t_set.h" +#include "thrift/generate/t_generator_registry.h" +//#include "thrift/parse/t_doc.h" + +/** + * Top level class representing an entire thrift program. A program consists + * fundamentally of the following: + * + * Typedefs + * Enumerations + * Constants + * Structs + * Exceptions + * Services + * + * The program module also contains the definitions of the base types. + * + */ +class t_program : public t_doc { +public: + t_program(std::string path, std::string name) + : path_(path), name_(name), out_path_("./"), out_path_is_absolute_(false), scope_(new t_scope), recursive_(false) {} + + t_program(std::string path) : path_(path), out_path_("./"), out_path_is_absolute_(false), recursive_(false) { + name_ = program_name(path); + scope_ = new t_scope(); + } + + ~t_program() override { + if (scope_) { + delete scope_; + scope_ = nullptr; + } + } + + // Path accessor + const std::string& get_path() const { return path_; } + + // Output path accessor + const std::string& get_out_path() const { return out_path_; } + + // Create gen-* dir accessor + bool is_out_path_absolute() const { return out_path_is_absolute_; } + + // Name accessor + const std::string& get_name() const { return name_; } + + // Namespace + const std::string& get_namespace() const { return namespace_; } + + // Include prefix accessor + const std::string& get_include_prefix() const { return include_prefix_; } + + // Accessors for program elements + const std::vector<t_typedef*>& get_typedefs() const { return typedefs_; } + const std::vector<t_enum*>& get_enums() const { return enums_; } + const std::vector<t_const*>& get_consts() const { return consts_; } + const std::vector<t_struct*>& get_structs() const { return structs_; } + const std::vector<t_struct*>& get_xceptions() const { return xceptions_; } + const std::vector<t_struct*>& get_objects() const { return objects_; } + const std::vector<t_service*>& get_services() const { return services_; } + const std::map<std::string, std::string>& get_namespaces() const { return namespaces_; } + + // Program elements + void add_typedef(t_typedef* td) { typedefs_.push_back(td); } + void add_enum(t_enum* te) { enums_.push_back(te); } + void add_const(t_const* tc) { consts_.push_back(tc); } + void add_struct(t_struct* ts) { + objects_.push_back(ts); + structs_.push_back(ts); + } + void add_xception(t_struct* tx) { + objects_.push_back(tx); + xceptions_.push_back(tx); + } + void add_service(t_service* ts) { services_.push_back(ts); } + + // Programs to include + std::vector<t_program*>& get_includes() { return includes_; } + + const std::vector<t_program*>& get_includes() const { return includes_; } + + void set_out_path(std::string out_path, bool out_path_is_absolute) { + out_path_ = out_path; + out_path_is_absolute_ = out_path_is_absolute; + // Ensure that it ends with a trailing '/' (or '\' for windows machines) + char c = out_path_.at(out_path_.size() - 1); + if (!(c == '/' || c == '\\')) { + out_path_.push_back('/'); + } + } + + // Typename collision detection + /** + * Search for typename collisions + * @param t the type to test for collisions + * @return true if a certain collision was found, otherwise false + */ + bool is_unique_typename(const t_type* t) const { + int occurrences = program_typename_count(this, t); + for (auto it = includes_.cbegin(); it != includes_.cend(); ++it) { + occurrences += program_typename_count(*it, t); + } + return 0 == occurrences; + } + + /** + * Search all type collections for duplicate typenames + * @param prog the program to search + * @param t the type to test for collisions + * @return the number of certain typename collisions + */ + int program_typename_count(const t_program* prog, const t_type* t) const { + int occurrences = 0; + occurrences += collection_typename_count(prog, prog->typedefs_, t); + occurrences += collection_typename_count(prog, prog->enums_, t); + occurrences += collection_typename_count(prog, prog->objects_, t); + occurrences += collection_typename_count(prog, prog->services_, t); + return occurrences; + } + + /** + * Search a type collection for duplicate typenames + * @param prog the program to search + * @param type_collection the type collection to search + * @param t the type to test for collisions + * @return the number of certain typename collisions + */ + template <class T> + int collection_typename_count(const t_program* prog, const T type_collection, const t_type* t) const { + int occurrences = 0; + for (auto it = type_collection.cbegin(); it != type_collection.cend(); ++it) + if (t != *it && 0 == t->get_name().compare((*it)->get_name()) && is_common_namespace(prog, t)) + ++occurrences; + return occurrences; + } + + /** + * Determine whether identical typenames will collide based on namespaces. + * + * Because we do not know which languages the user will generate code for, + * collisions within programs (IDL files) having namespace declarations can be + * difficult to determine. Only guaranteed collisions return true (cause an error). + * Possible collisions involving explicit namespace declarations produce a warning. + * Other possible collisions go unreported. + * @param prog the program containing the preexisting typename + * @param t the type containing the typename match + * @return true if a collision within namespaces is found, otherwise false + */ + bool is_common_namespace(const t_program* prog, const t_type* t) const { + // Case 1: Typenames are in the same program [collision] + if (prog == t->get_program()) { + pwarning(1, + "Duplicate typename %s found in %s", + t->get_name().c_str(), + t->get_program()->get_name().c_str()); + return true; + } + + // Case 2: Both programs have identical namespace scope/name declarations [collision] + bool match = true; + for (auto it = prog->namespaces_.cbegin(); + it != prog->namespaces_.cend(); + ++it) { + if (0 == it->second.compare(t->get_program()->get_namespace(it->first))) { + pwarning(1, + "Duplicate typename %s found in %s,%s,%s and %s,%s,%s [file,scope,ns]", + t->get_name().c_str(), + t->get_program()->get_name().c_str(), + it->first.c_str(), + it->second.c_str(), + prog->get_name().c_str(), + it->first.c_str(), + it->second.c_str()); + } else { + match = false; + } + } + for (auto it = t->get_program()->namespaces_.cbegin(); + it != t->get_program()->namespaces_.cend(); + ++it) { + if (0 == it->second.compare(prog->get_namespace(it->first))) { + pwarning(1, + "Duplicate typename %s found in %s,%s,%s and %s,%s,%s [file,scope,ns]", + t->get_name().c_str(), + t->get_program()->get_name().c_str(), + it->first.c_str(), + it->second.c_str(), + prog->get_name().c_str(), + it->first.c_str(), + it->second.c_str()); + } else { + match = false; + } + } + if (0 == prog->namespaces_.size() && 0 == t->get_program()->namespaces_.size()) { + pwarning(1, + "Duplicate typename %s found in %s and %s", + t->get_name().c_str(), + t->get_program()->get_name().c_str(), + prog->get_name().c_str()); + } + return match; + } + + // Scoping and namespacing + void set_namespace(std::string name) { namespace_ = name; } + + // Scope accessor + t_scope* scope() { return scope_; } + + const t_scope* scope() const { return scope_; } + + // Includes + + void add_include(t_program* program) { + includes_.push_back(program); + } + + void add_include(std::string path, std::string include_site) { + t_program* program = new t_program(path); + + // include prefix for this program is the site at which it was included + // (minus the filename) + std::string include_prefix; + std::string::size_type last_slash = std::string::npos; + if ((last_slash = include_site.rfind("/")) != std::string::npos) { + include_prefix = include_site.substr(0, last_slash); + } + + program->set_include_prefix(include_prefix); + includes_.push_back(program); + } + + void set_include_prefix(std::string include_prefix) { + include_prefix_ = include_prefix; + + // this is intended to be a directory; add a trailing slash if necessary + std::string::size_type len = include_prefix_.size(); + if (len > 0 && include_prefix_[len - 1] != '/') { + include_prefix_ += '/'; + } + } + + // Language neutral namespace / packaging + void set_namespace(std::string language, std::string name_space) { + if (language != "*") { + size_t sub_index = language.find('.'); + std::string base_language = language.substr(0, sub_index); + + if (base_language == "smalltalk") { + pwarning(1, "Namespace 'smalltalk' is deprecated. Use 'st' instead"); + base_language = "st"; + } + else if ((base_language == "csharp") || (base_language == "netcore")) { + pwarning(1, "The '%s' target is deprecated. Consider moving to 'netstd' instead.", base_language.c_str()); + // warn only, don't change base_language + } + + t_generator_registry::gen_map_t my_copy = t_generator_registry::get_generator_map(); + + t_generator_registry::gen_map_t::iterator it; + it = my_copy.find(base_language); + + if (it == my_copy.end()) { + std::string warning = "No generator named '" + base_language + "' could be found!"; + pwarning(1, warning.c_str()); + } else { + if (sub_index != std::string::npos) { + std::string sub_namespace = language.substr(sub_index + 1); + if (!it->second->is_valid_namespace(sub_namespace)) { + std::string warning = base_language + " generator does not accept '" + sub_namespace + + "' as sub-namespace!"; + pwarning(1, warning.c_str()); + } + } + } + } + + namespaces_[language] = name_space; + } + + std::string get_namespace(std::string language) const { + std::map<std::string, std::string>::const_iterator iter; + if ((iter = namespaces_.find(language)) != namespaces_.end() + || (iter = namespaces_.find("*")) != namespaces_.end()) { + return iter->second; + } + return std::string(); + } + + const std::map<std::string, std::string>& get_all_namespaces() const { + return namespaces_; + } + + void set_namespace_annotations(std::string language, std::map<std::string, std::string> annotations) { + namespace_annotations_[language] = annotations; + } + + const std::map<std::string, std::string>& get_namespace_annotations(const std::string& language) const { + auto it = namespace_annotations_.find(language); + if (namespace_annotations_.end() != it) { + return it->second; + } + static const std::map<std::string, std::string> emptyMap; + return emptyMap; + } + + std::map<std::string, std::string>& get_namespace_annotations(const std::string& language) { + return namespace_annotations_[language]; + } + + // Language specific namespace / packaging + + void add_cpp_include(std::string path) { cpp_includes_.push_back(path); } + + const std::vector<std::string>& get_cpp_includes() const { return cpp_includes_; } + + void add_c_include(std::string path) { c_includes_.push_back(path); } + + const std::vector<std::string>& get_c_includes() const { return c_includes_; } + + void set_recursive(const bool recursive) { recursive_ = recursive; } + + bool get_recursive() const { return recursive_; } + +private: + // File path + std::string path_; + + // Name + std::string name_; + + // Output directory + std::string out_path_; + + // Output directory is absolute location for generated source (no gen-*) + bool out_path_is_absolute_; + + // Namespace + std::string namespace_; + + // Included programs + std::vector<t_program*> includes_; + + // Include prefix for this program, if any + std::string include_prefix_; + + // Identifier lookup scope + t_scope* scope_; + + // Components to generate code for + std::vector<t_typedef*> typedefs_; + std::vector<t_enum*> enums_; + std::vector<t_const*> consts_; + std::vector<t_struct*> objects_; + std::vector<t_struct*> structs_; + std::vector<t_struct*> xceptions_; + std::vector<t_service*> services_; + + // Dynamic namespaces + std::map<std::string, std::string> namespaces_; + + // Annotations for dynamic namespaces + std::map<std::string, std::map<std::string, std::string> > namespace_annotations_; + + // C++ extra includes + std::vector<std::string> cpp_includes_; + + // C extra includes + std::vector<std::string> c_includes_; + + // Recursive code generation + bool recursive_; +}; + +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_scope.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_scope.h new file mode 100644 index 000000000..a12c4df5e --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_scope.h @@ -0,0 +1,206 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef T_SCOPE_H +#define T_SCOPE_H + +#include <map> +#include <string> +#include <sstream> + +#include "thrift/parse/t_type.h" +#include "thrift/parse/t_service.h" +#include "thrift/parse/t_const.h" +#include "thrift/parse/t_const_value.h" +#include "thrift/parse/t_base_type.h" +#include "thrift/parse/t_map.h" +#include "thrift/parse/t_list.h" +#include "thrift/parse/t_set.h" + +/** + * This represents a variable scope used for looking up predefined types and + * services. Typically, a scope is associated with a t_program. Scopes are not + * used to determine code generation, but rather to resolve identifiers at + * parse time. + * + */ +class t_scope { +public: + t_scope() = default; + + void add_type(std::string name, t_type* type) { types_[name] = type; } + + t_type* get_type(std::string name) { return types_[name]; } + + const t_type* get_type(std::string name) const { + const auto it = types_.find(name); + if (types_.end() != it) + { + return it->second; + } + return nullptr; + } + + void add_service(std::string name, t_service* service) { services_[name] = service; } + + t_service* get_service(std::string name) { return services_[name]; } + + const t_service* get_service(std::string name) const { + const auto it = services_.find(name); + if (services_.end() != it) + { + return it->second; + } + return nullptr; + } + + void add_constant(std::string name, t_const* constant) { + if (constants_.find(name) != constants_.end()) { + throw "Enum " + name + " is already defined!"; + } else { + constants_[name] = constant; + } + } + + t_const* get_constant(std::string name) { return constants_[name]; } + + const t_const* get_constant(std::string name) const { + const auto it = constants_.find(name); + if (constants_.end() != it) + { + return it->second; + } + return nullptr; + } + + void print() { + std::map<std::string, t_type*>::iterator iter; + for (iter = types_.begin(); iter != types_.end(); ++iter) { + printf("%s => %s\n", iter->first.c_str(), iter->second->get_name().c_str()); + } + } + + void resolve_const_value(t_const_value* const_val, t_type* ttype) { + if (ttype->is_map()) { + const std::map<t_const_value*, t_const_value*, t_const_value::value_compare>& map = const_val->get_map(); + std::map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = map.begin(); v_iter != map.end(); ++v_iter) { + resolve_const_value(v_iter->first, ((t_map*)ttype)->get_key_type()); + resolve_const_value(v_iter->second, ((t_map*)ttype)->get_val_type()); + } + } else if (ttype->is_list()) { + const std::vector<t_const_value*>& val = const_val->get_list(); + std::vector<t_const_value*>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + resolve_const_value((*v_iter), ((t_list*)ttype)->get_elem_type()); + } + } else if (ttype->is_set()) { + const std::vector<t_const_value*>& val = const_val->get_list(); + std::vector<t_const_value*>::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + resolve_const_value((*v_iter), ((t_set*)ttype)->get_elem_type()); + } + } else if (ttype->is_struct()) { + auto* tstruct = (t_struct*)ttype; + const std::map<t_const_value*, t_const_value*, t_const_value::value_compare>& map = const_val->get_map(); + std::map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + for (v_iter = map.begin(); v_iter != map.end(); ++v_iter) { + t_field* field = tstruct->get_field_by_name(v_iter->first->get_string()); + if (field == nullptr) { + throw "No field named \"" + v_iter->first->get_string() + + "\" was found in struct of type \"" + tstruct->get_name() + "\""; + } + resolve_const_value(v_iter->second, field->get_type()); + } + } else if (const_val->get_type() == t_const_value::CV_IDENTIFIER) { + if (ttype->is_enum()) { + const_val->set_enum((t_enum*)ttype); + } else { + t_const* constant = get_constant(const_val->get_identifier()); + if (constant == nullptr) { + throw "No enum value or constant found named \"" + const_val->get_identifier() + "\"!"; + } + + // Resolve typedefs to the underlying type + t_type* const_type = constant->get_type()->get_true_type(); + + if (const_type->is_base_type()) { + switch (((t_base_type*)const_type)->get_base()) { + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + case t_base_type::TYPE_BOOL: + case t_base_type::TYPE_I8: + const_val->set_integer(constant->get_value()->get_integer()); + break; + case t_base_type::TYPE_STRING: + const_val->set_string(constant->get_value()->get_string()); + break; + case t_base_type::TYPE_DOUBLE: + const_val->set_double(constant->get_value()->get_double()); + break; + case t_base_type::TYPE_VOID: + throw "Constants cannot be of type VOID"; + } + } else if (const_type->is_map()) { + const std::map<t_const_value*, t_const_value*, t_const_value::value_compare>& map = constant->get_value()->get_map(); + std::map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; + + const_val->set_map(); + for (v_iter = map.begin(); v_iter != map.end(); ++v_iter) { + const_val->add_map(v_iter->first, v_iter->second); + } + } else if (const_type->is_list()) { + const std::vector<t_const_value*>& val = constant->get_value()->get_list(); + std::vector<t_const_value*>::const_iterator v_iter; + + const_val->set_list(); + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + const_val->add_list(*v_iter); + } + } + } + } else if (ttype->is_enum()) { + // enum constant with non-identifier value. set the enum and find the + // value's name. + auto* tenum = (t_enum*)ttype; + t_enum_value* enum_value = tenum->get_constant_by_value(const_val->get_integer()); + if (enum_value == nullptr) { + std::ostringstream valstm; + valstm << const_val->get_integer(); + throw "Couldn't find a named value in enum " + tenum->get_name() + " for value " + + valstm.str(); + } + const_val->set_identifier(tenum->get_name() + "." + enum_value->get_name()); + const_val->set_enum(tenum); + } + } + +private: + // Map of names to types + std::map<std::string, t_type*> types_; + + // Map of names to constants + std::map<std::string, t_const*> constants_; + + // Map of names to services + std::map<std::string, t_service*> services_; +}; + +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_service.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_service.h new file mode 100644 index 000000000..a43a515ca --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_service.h @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef T_SERVICE_H +#define T_SERVICE_H + +#include "thrift/parse/t_function.h" +#include <vector> + +class t_program; + +/** + * A service consists of a set of functions. + * + */ +class t_service : public t_type { +public: + t_service(t_program* program) : t_type(program), extends_(nullptr) {} + + bool is_service() const override { return true; } + + void set_extends(t_service* extends) { extends_ = extends; } + + void add_function(t_function* func) { + std::vector<t_function*>::const_iterator iter; + for (iter = functions_.begin(); iter != functions_.end(); ++iter) { + if (func->get_name() == (*iter)->get_name()) { + throw "Function " + func->get_name() + " is already defined"; + } + } + functions_.push_back(func); + } + + const std::vector<t_function*>& get_functions() const { return functions_; } + + t_service* get_extends() { return extends_; } + + const t_service* get_extends() const { return extends_; } + +private: + std::vector<t_function*> functions_; + t_service* extends_; +}; + +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_set.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_set.h new file mode 100644 index 000000000..c0d4a35c1 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_set.h @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef T_SET_H +#define T_SET_H + +#include "thrift/parse/t_container.h" + +/** + * A set is a lightweight container type that just wraps another data type. + * + */ +class t_set : public t_container { +public: + t_set(t_type* elem_type) : elem_type_(elem_type) {} + + const t_type* get_elem_type() const { return elem_type_; } + + t_type* get_elem_type() { return elem_type_; } + + bool is_set() const override { return true; } + +private: + t_type* elem_type_; +}; + +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_struct.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_struct.h new file mode 100644 index 000000000..7e1e6caf0 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_struct.h @@ -0,0 +1,165 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef T_STRUCT_H +#define T_STRUCT_H + +#include <algorithm> +#include <vector> +#include <utility> +#include <string> + +#include "thrift/parse/t_type.h" +#include "thrift/parse/t_field.h" + +// Forward declare that puppy +class t_program; + +/** + * A struct is a container for a set of member fields that has a name. Structs + * are also used to implement exception types. + * + */ +class t_struct : public t_type { +public: + typedef std::vector<t_field*> members_type; + + t_struct(t_program* program) + : t_type(program), + is_xception_(false), + is_union_(false), + members_validated(false), + members_with_value(0), + xsd_all_(false) {} + + t_struct(t_program* program, const std::string& name) + : t_type(program, name), + is_xception_(false), + is_union_(false), + members_validated(false), + members_with_value(0), + xsd_all_(false) {} + + void set_name(const std::string& name) override { + name_ = name; + validate_union_members(); + } + + void set_xception(bool is_xception) { is_xception_ = is_xception; } + + void validate_union_member(t_field* field) { + if (is_union_ && (!name_.empty())) { + + // 1) unions can't have required fields + // 2) union members are implicitly optional, otherwise bugs like THRIFT-3650 wait to happen + if (field->get_req() != t_field::T_OPTIONAL) { + // no warning on default requiredness, but do warn on anything else that is explicitly asked for + if(field->get_req() != t_field::T_OPT_IN_REQ_OUT) { + pwarning(1, + "Union %s field %s: union members must be optional, ignoring specified requiredness.\n", + name_.c_str(), + field->get_name().c_str()); + } + field->set_req(t_field::T_OPTIONAL); + } + + // unions may have up to one member defaulted, but not more + if (field->get_value() != nullptr) { + if (1 < ++members_with_value) { + throw "Error: Field " + field->get_name() + " provides another default value for union " + + name_; + } + } + } + } + + void validate_union_members() { + if (is_union_ && (!name_.empty()) && (!members_validated)) { + members_type::const_iterator m_iter; + for (m_iter = members_in_id_order_.begin(); m_iter != members_in_id_order_.end(); ++m_iter) { + validate_union_member(*m_iter); + } + members_validated = true; + } + } + + void set_union(bool is_union) { + is_union_ = is_union; + validate_union_members(); + } + + void set_xsd_all(bool xsd_all) { xsd_all_ = xsd_all; } + + bool get_xsd_all() const { return xsd_all_; } + + bool append(t_field* elem) { + typedef members_type::iterator iter_type; + std::pair<iter_type, iter_type> bounds = std::equal_range(members_in_id_order_.begin(), + members_in_id_order_.end(), + elem, + t_field::key_compare()); + if (bounds.first != bounds.second) { + return false; + } + // returns false when there is a conflict of field names + if (get_field_by_name(elem->get_name()) != nullptr) { + return false; + } + members_.push_back(elem); + members_in_id_order_.insert(bounds.second, elem); + validate_union_member(elem); + return true; + } + + const members_type& get_members() const { return members_; } + + const members_type& get_sorted_members() const { return members_in_id_order_; } + + bool is_struct() const override { return !is_xception_; } + + bool is_xception() const override { return is_xception_; } + + bool is_union() const { return is_union_; } + + t_field* get_field_by_name(std::string field_name) { + return const_cast<t_field*>(const_cast<const t_struct&>(*this).get_field_by_name(field_name)); + } + + const t_field* get_field_by_name(std::string field_name) const { + members_type::const_iterator m_iter; + for (m_iter = members_in_id_order_.begin(); m_iter != members_in_id_order_.end(); ++m_iter) { + if ((*m_iter)->get_name() == field_name) { + return *m_iter; + } + } + return nullptr; + } + +private: + members_type members_; + members_type members_in_id_order_; + bool is_xception_; + bool is_union_; + bool members_validated; + int members_with_value; + + bool xsd_all_; +}; + +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_type.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_type.h new file mode 100644 index 000000000..63f99ed87 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_type.h @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef T_TYPE_H +#define T_TYPE_H + +#include <string> +#include <map> +#include <cstring> +#include <stdint.h> +#include "thrift/parse/t_doc.h" + +class t_program; + +/** + * Generic representation of a thrift type. These objects are used by the + * parser module to build up a tree of object that are all explicitly typed. + * The generic t_type class exports a variety of useful methods that are + * used by the code generator to branch based upon different handling for the + * various types. + * + */ +class t_type : public t_doc { +public: + ~t_type() override = default; + + virtual void set_name(const std::string& name) { name_ = name; } + + virtual const std::string& get_name() const { return name_; } + + virtual bool is_void() const { return false; } + virtual bool is_base_type() const { return false; } + virtual bool is_string() const { return false; } + virtual bool is_binary() const { return false; } + virtual bool is_bool() const { return false; } + virtual bool is_typedef() const { return false; } + virtual bool is_enum() const { return false; } + virtual bool is_struct() const { return false; } + virtual bool is_xception() const { return false; } + virtual bool is_container() const { return false; } + virtual bool is_list() const { return false; } + virtual bool is_set() const { return false; } + virtual bool is_map() const { return false; } + virtual bool is_service() const { return false; } + + t_program* get_program() { return program_; } + + const t_program* get_program() const { return program_; } + + t_type* get_true_type(); + const t_type* get_true_type() const; + + // This function will break (maybe badly) unless 0 <= num <= 16. + static char nybble_to_xdigit(int num) { + if (num < 10) { + return '0' + num; + } else { + return 'A' + num - 10; + } + } + + static std::string byte_to_hex(uint8_t byte) { + std::string rv; + rv += nybble_to_xdigit(byte >> 4); + rv += nybble_to_xdigit(byte & 0x0f); + return rv; + } + + std::map<std::string, std::string> annotations_; + +protected: + t_type() : program_(nullptr) { ; } + + t_type(t_program* program) : program_(program) { ; } + + t_type(t_program* program, std::string name) : program_(program), name_(name) { ; } + + t_type(std::string name) : program_(nullptr), name_(name) { ; } + + t_program* program_; + std::string name_; +}; + +/** + * Placeholder struct for returning the key and value of an annotation + * during parsing. + */ +struct t_annotation { + std::string key; + std::string val; +}; + +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_typedef.cc b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_typedef.cc new file mode 100644 index 000000000..c80868560 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_typedef.cc @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#include <cstdio> + +#include "thrift/parse/t_typedef.h" +#include "thrift/parse/t_program.h" + +t_type* t_typedef::get_type() { + return const_cast<t_type*>(const_cast<const t_typedef*>(this)->get_type()); +} + +const t_type* t_typedef::get_type() const { + if (type_ == NULL) { + const t_type* type = get_program()->scope()->get_type(symbolic_); + if (type == NULL) { + printf("Type \"%s\" not defined\n", symbolic_.c_str()); + exit(1); + } + return type; + } + return type_; +} diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_typedef.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_typedef.h new file mode 100644 index 000000000..d21d863a9 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/parse/t_typedef.h @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef T_TYPEDEF_H +#define T_TYPEDEF_H + +#include <string> +#include "thrift/parse/t_type.h" + +/** + * A typedef is a mapping from a symbolic name to another type. In dymanically + * typed languages (i.e. php/python) the code generator can actually usually + * ignore typedefs and just use the underlying type directly, though in C++ + * the symbolic naming can be quite useful for code clarity. + * + */ +class t_typedef : public t_type { +public: + t_typedef(t_program* program, t_type* type, const std::string& symbolic) + : t_type(program, symbolic), type_(type), symbolic_(symbolic), forward_(false) {} + + /** + * This constructor is used to refer to a type that is lazily + * resolved at a later time, like for forward declarations or + * recursive types. + */ + t_typedef(t_program* program, const std::string& symbolic, bool forward) + : t_type(program, symbolic), + type_(nullptr), + symbolic_(symbolic), + forward_(forward) + {} + + ~t_typedef() override = default; + + t_type* get_type(); + + const t_type* get_type() const; + + const std::string& get_symbolic() const { return symbolic_; } + + bool is_forward_typedef() const { return forward_; } + + bool is_typedef() const override { return true; } + +private: + t_type* type_; + std::string symbolic_; + bool forward_; +}; + +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/platform.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/platform.h new file mode 100644 index 000000000..f49adb9c7 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/platform.h @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * define for mkdir,since the method signature + * is different for the non-POSIX MinGW + */ + +#ifdef _MSC_VER +#include "thrift/windows/config.h" +#endif + +#ifdef _WIN32 +#include <direct.h> +#include <io.h> +#else +#include <sys/types.h> +#include <sys/stat.h> +#endif + +#include <cerrno> + +// ignore EEXIST, throw on any other error +#ifdef _WIN32 +#define MKDIR(x) { int r = _mkdir(x); if (r == -1 && errno != EEXIST) { throw (std::string(x) + ": ") + strerror(errno); } } +#else +#define MKDIR(x) { int r = mkdir(x, S_IRWXU | S_IRWXG | S_IRWXO); if (r == -1 && errno != EEXIST) { throw (std::string(x) + ": ") + strerror(errno); } } +#endif + +#ifdef PATH_MAX +#define THRIFT_PATH_MAX PATH_MAX +#else +#define THRIFT_PATH_MAX MAX_PATH +#endif diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/thriftl.ll b/src/jaegertracing/thrift/compiler/cpp/src/thrift/thriftl.ll new file mode 100644 index 000000000..282e0309c --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/thriftl.ll @@ -0,0 +1,362 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Thrift scanner. + * + * Tokenizes a thrift definition file. + */ + +%{ + +/* This is redundant with some of the flags in Makefile.am, but it works + * when people override CXXFLAGS without being careful. The pragmas are + * the 'right' way to do it, but don't work on old-enough GCC (in particular + * the GCC that ship on Mac OS X 10.6.5, *counter* to what the GNU docs say) + * + * We should revert the Makefile.am changes once Apple ships a reasonable + * GCC. + */ +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wunused-function" +#pragma GCC diagnostic ignored "-Wunused-label" +#endif + +#ifdef _MSC_VER +#pragma warning( push ) + +// warning C4102: 'find_rule' : unreferenced label +#pragma warning( disable : 4102 ) + +// warning C4267: 'argument' : conversion from 'size_t' to 'int', possible loss of data +#pragma warning( disable : 4267 ) + +// avoid isatty redefinition +#define YY_NEVER_INTERACTIVE 1 + +#define YY_NO_UNISTD_H 1 +#endif + +#include <cassert> +#include <string> +#include <errno.h> +#include <stdlib.h> + +#ifdef _MSC_VER +#include "thrift/windows/config.h" +#endif +#include "thrift/main.h" +#include "thrift/common.h" +#include "thrift/globals.h" +#include "thrift/parse/t_program.h" + +/** + * Must be included AFTER parse/t_program.h, but I can't remember why anymore + * because I wrote this a while ago. + */ +#if defined(BISON_USE_PARSER_H_EXTENSION) +#include "thrift/thrifty.h" +#else +#include "thrift/thrifty.hh" +#endif + +void integer_overflow(char* text) { + yyerror("This integer is too big: \"%s\"\n", text); + exit(1); +} + +void unexpected_token(char* text) { + yyerror("Unexpected token in input: \"%s\"\n", text); + exit(1); +} + +%} + +/** + * Provides the yylineno global, useful for debugging output + */ +%option lex-compat + +/** + * Our inputs are all single files, so no need for yywrap + */ +%option noyywrap + +/** + * We don't use it, and it fires up warnings at -Wall + */ +%option nounput + +/** + * Helper definitions, comments, constants, and whatnot + */ + +intconstant ([+-]?[0-9]+) +hexconstant ([+-]?"0x"[0-9A-Fa-f]+) +dubconstant ([+-]?[0-9]*(\.[0-9]+)?([eE][+-]?[0-9]+)?) +identifier ([a-zA-Z_](\.[a-zA-Z_0-9]|[a-zA-Z_0-9])*) +whitespace ([ \t\r\n]*) +sillycomm ("/*""*"*"*/") +multicm_begin ("/*") +doctext_begin ("/**") +comment ("//"[^\n]*) +unixcomment ("#"[^\n]*) +symbol ([:;\,\{\}\(\)\=<>\[\]]) +literal_begin (['\"]) + +%% + +{whitespace} { /* do nothing */ } +{sillycomm} { /* do nothing */ } + +{doctext_begin} { + std::string parsed("/**"); + int state = 0; // 0 = normal, 1 = "*" seen, "*/" seen + while(state < 2) + { + int ch = yyinput(); + parsed.push_back(ch); + switch (ch) { + case EOF: + yyerror("Unexpected end of file in doc-comment at %d\n", yylineno); + exit(1); + case '*': + state = 1; + break; + case '/': + state = (state == 1) ? 2 : 0; + break; + default: + state = 0; + break; + } + } + pdebug("doctext = \"%s\"\n",parsed.c_str()); + + /* This does not show up in the parse tree. */ + /* Rather, the parser will grab it out of the global. */ + if (g_parse_mode == PROGRAM) { + clear_doctext(); + g_doctext = strdup(parsed.c_str() + 3); + assert(strlen(g_doctext) >= 2); + g_doctext[strlen(g_doctext) - 2] = ' '; + g_doctext[strlen(g_doctext) - 1] = '\0'; + g_doctext = clean_up_doctext(g_doctext); + g_doctext_lineno = yylineno; + if( (g_program_doctext_candidate == NULL) && (g_program_doctext_status == INVALID)){ + g_program_doctext_candidate = strdup(g_doctext); + g_program_doctext_lineno = g_doctext_lineno; + g_program_doctext_status = STILL_CANDIDATE; + pdebug("%s","program doctext set to STILL_CANDIDATE"); + } + } +} + +{multicm_begin} { /* parsed, but thrown away */ + std::string parsed("/*"); + int state = 0; // 0 = normal, 1 = "*" seen, "*/" seen + while(state < 2) + { + int ch = yyinput(); + parsed.push_back(ch); + switch (ch) { + case EOF: + yyerror("Unexpected end of file in multiline comment at %d\n", yylineno); + exit(1); + case '*': + state = 1; + break; + case '/': + state = (state == 1) ? 2 : 0; + break; + default: + state = 0; + break; + } + } + pdebug("multi_comm = \"%s\"\n",parsed.c_str()); +} + +{comment} { /* do nothing */ } +{unixcomment} { /* do nothing */ } + +{symbol} { return yytext[0]; } +"*" { return yytext[0]; } + +"false" { yylval.iconst=0; return tok_int_constant; } +"true" { yylval.iconst=1; return tok_int_constant; } + +"namespace" { return tok_namespace; } +"cpp_namespace" { error_unsupported_namespace_decl("cpp"); /* do nothing */ } +"cpp_include" { return tok_cpp_include; } +"cpp_type" { return tok_cpp_type; } +"java_package" { error_unsupported_namespace_decl("java_package", "java"); /* do nothing */ } +"csharp_namespace" { error_unsupported_namespace_decl("csharp"); /* do nothing */ } +"delphi_namespace" { error_unsupported_namespace_decl("delphi"); /* do nothing */ } +"php_namespace" { error_unsupported_namespace_decl("php"); /* do nothing */ } +"py_module" { error_unsupported_namespace_decl("py_module", "py"); /* do nothing */ } +"perl_package" { error_unsupported_namespace_decl("perl_package", "perl"); /* do nothing */ } +"ruby_namespace" { error_unsupported_namespace_decl("ruby"); /* do nothing */ } +"smalltalk_category" { error_unsupported_namespace_decl("smalltalk_category", "st"); /* do nothing */ } +"smalltalk_prefix" { error_unsupported_namespace_decl("smalltalk_prefix", "st"); /* do nothing */ } +"xsd_all" { return tok_xsd_all; } +"xsd_optional" { return tok_xsd_optional; } +"xsd_nillable" { return tok_xsd_nillable; } +"xsd_namespace" { error_unsupported_namespace_decl("xsd"); /* do nothing */ } +"xsd_attrs" { return tok_xsd_attrs; } +"include" { return tok_include; } +"void" { return tok_void; } +"bool" { return tok_bool; } +"byte" { + emit_byte_type_warning(); + return tok_i8; +} +"i8" { return tok_i8; } +"i16" { return tok_i16; } +"i32" { return tok_i32; } +"i64" { return tok_i64; } +"double" { return tok_double; } +"string" { return tok_string; } +"binary" { return tok_binary; } +"slist" { + pwarning(0, "\"slist\" is deprecated and will be removed in a future compiler version. This type should be replaced with \"string\".\n"); + return tok_slist; +} +"senum" { + pwarning(0, "\"senum\" is deprecated and will be removed in a future compiler version. This type should be replaced with \"string\".\n"); + return tok_senum; +} +"map" { return tok_map; } +"list" { return tok_list; } +"set" { return tok_set; } +"oneway" { return tok_oneway; } +"typedef" { return tok_typedef; } +"struct" { return tok_struct; } +"union" { return tok_union; } +"exception" { return tok_xception; } +"extends" { return tok_extends; } +"throws" { return tok_throws; } +"service" { return tok_service; } +"enum" { return tok_enum; } +"const" { return tok_const; } +"required" { return tok_required; } +"optional" { return tok_optional; } +"async" { + pwarning(0, "\"async\" is deprecated. It is called \"oneway\" now.\n"); + return tok_oneway; +} +"&" { return tok_reference; } + +{intconstant} { + errno = 0; + yylval.iconst = strtoll(yytext, NULL, 10); + if (errno == ERANGE) { + integer_overflow(yytext); + } + return tok_int_constant; +} + +{hexconstant} { + errno = 0; + char sign = yytext[0]; + int shift = sign == '0' ? 2 : 3; + yylval.iconst = strtoll(yytext+shift, NULL, 16); + if (sign == '-') { + yylval.iconst = -yylval.iconst; + } + if (errno == ERANGE) { + integer_overflow(yytext); + } + return tok_int_constant; +} + +{identifier} { + yylval.id = strdup(yytext); + return tok_identifier; +} + +{dubconstant} { + /* Deliberately placed after identifier, since "e10" is NOT a double literal (THRIFT-3477) */ + yylval.dconst = atof(yytext); + return tok_dub_constant; +} + +{literal_begin} { + char mark = yytext[0]; + std::string result; + for(;;) + { + int ch = yyinput(); + switch (ch) { + case EOF: + yyerror("End of file while read string at %d\n", yylineno); + exit(1); + case '\n': + yyerror("End of line while read string at %d\n", yylineno - 1); + exit(1); + case '\\': + ch = yyinput(); + switch (ch) { + case 'r': + result.push_back('\r'); + continue; + case 'n': + result.push_back('\n'); + continue; + case 't': + result.push_back('\t'); + continue; + case '"': + result.push_back('"'); + continue; + case '\'': + result.push_back('\''); + continue; + case '\\': + result.push_back('\\'); + continue; + default: + yyerror("Bad escape character\n"); + return -1; + } + break; + default: + if (ch == mark) { + yylval.id = strdup(result.c_str()); + return tok_literal; + } else { + result.push_back(ch); + } + } + } +} + + +. { + unexpected_token(yytext); +} + +%% + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +/* vim: filetype=lex +*/ diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/thrifty.yy b/src/jaegertracing/thrift/compiler/cpp/src/thrift/thrifty.yy new file mode 100644 index 000000000..df34adf04 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/thrifty.yy @@ -0,0 +1,1201 @@ +%code requires { +#include "thrift/parse/t_program.h" +} +%{ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Thrift parser. + * + * This parser is used on a thrift definition file. + * + */ + +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS +#endif +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif +#include <stdio.h> +#include <string.h> +#ifndef _MSC_VER +#include <inttypes.h> +#else +#include <stdint.h> +#endif +#include <limits.h> +#ifdef _MSC_VER +#include "thrift/windows/config.h" +#endif +#include "thrift/main.h" +#include "thrift/common.h" +#include "thrift/globals.h" +#include "thrift/parse/t_program.h" +#include "thrift/parse/t_scope.h" + +#ifdef _MSC_VER +//warning C4065: switch statement contains 'default' but no 'case' labels +#pragma warning(disable:4065) +#endif + +/** + * This global variable is used for automatic numbering of field indices etc. + * when parsing the members of a struct. Field values are automatically + * assigned starting from -1 and working their way down. + */ +int y_field_val = -1; +/** + * This global variable is used for automatic numbering of enum values. + * y_enum_val is the last value assigned; the next auto-assigned value will be + * y_enum_val+1, and then it continues working upwards. Explicitly specified + * enum values reset y_enum_val to that value. + */ +int32_t y_enum_val = -1; +int g_arglist = 0; +const int struct_is_struct = 0; +const int struct_is_union = 1; + +%} + +/** + * This structure is used by the parser to hold the data types associated with + * various parse nodes. + */ +%union { + char* id; + int64_t iconst; + double dconst; + bool tbool; + t_doc* tdoc; + t_type* ttype; + t_base_type* tbase; + t_typedef* ttypedef; + t_enum* tenum; + t_enum_value* tenumv; + t_const* tconst; + t_const_value* tconstv; + t_struct* tstruct; + t_service* tservice; + t_function* tfunction; + t_field* tfield; + char* dtext; + t_field::e_req ereq; + t_annotation* tannot; + t_field_id tfieldid; +} + +/** + * Strings identifier + */ +%token<id> tok_identifier +%token<id> tok_literal +%token<dtext> tok_doctext + +/** + * Constant values + */ +%token<iconst> tok_int_constant +%token<dconst> tok_dub_constant + +/** + * Header keywords + */ +%token tok_include +%token tok_namespace +%token tok_cpp_include +%token tok_cpp_type +%token tok_xsd_all +%token tok_xsd_optional +%token tok_xsd_nillable +%token tok_xsd_attrs + +/** + * Base datatype keywords + */ +%token tok_void +%token tok_bool +%token tok_string +%token tok_binary +%token tok_slist +%token tok_senum +%token tok_i8 +%token tok_i16 +%token tok_i32 +%token tok_i64 +%token tok_double + +/** + * Complex type keywords + */ +%token tok_map +%token tok_list +%token tok_set + +/** + * Function modifiers + */ +%token tok_oneway + +/** + * Thrift language keywords + */ +%token tok_typedef +%token tok_struct +%token tok_xception +%token tok_throws +%token tok_extends +%token tok_service +%token tok_enum +%token tok_const +%token tok_required +%token tok_optional +%token tok_union +%token tok_reference + +/** + * Grammar nodes + */ + +%type<ttype> BaseType +%type<ttype> SimpleBaseType +%type<ttype> ContainerType +%type<ttype> SimpleContainerType +%type<ttype> MapType +%type<ttype> SetType +%type<ttype> ListType + +%type<tdoc> Definition +%type<ttype> TypeDefinition + +%type<ttypedef> Typedef + +%type<ttype> TypeAnnotations +%type<ttype> TypeAnnotationList +%type<tannot> TypeAnnotation +%type<id> TypeAnnotationValue + +%type<tfield> Field +%type<tfieldid> FieldIdentifier +%type<ereq> FieldRequiredness +%type<ttype> FieldType +%type<tconstv> FieldValue +%type<tstruct> FieldList +%type<tbool> FieldReference + +%type<tenum> Enum +%type<tenum> EnumDefList +%type<tenumv> EnumDef +%type<tenumv> EnumValue + +%type<ttypedef> Senum +%type<tbase> SenumDefList +%type<id> SenumDef + +%type<tconst> Const +%type<tconstv> ConstValue +%type<tconstv> ConstList +%type<tconstv> ConstListContents +%type<tconstv> ConstMap +%type<tconstv> ConstMapContents + +%type<iconst> StructHead +%type<tstruct> Struct +%type<tstruct> Xception +%type<tservice> Service + +%type<tfunction> Function +%type<ttype> FunctionType +%type<tservice> FunctionList + +%type<tstruct> Throws +%type<tservice> Extends +%type<tbool> Oneway +%type<tbool> XsdAll +%type<tbool> XsdOptional +%type<tbool> XsdNillable +%type<tstruct> XsdAttributes +%type<id> CppType + +%type<dtext> CaptureDocText + +%% + +/** + * Thrift Grammar Implementation. + * + * For the most part this source file works its way top down from what you + * might expect to find in a typical .thrift file, i.e. type definitions and + * namespaces up top followed by service definitions using those types. + */ + +Program: + HeaderList DefinitionList + { + pdebug("Program -> Headers DefinitionList"); + if((g_program_doctext_candidate != NULL) && (g_program_doctext_status != ALREADY_PROCESSED)) + { + g_program->set_doc(g_program_doctext_candidate); + g_program_doctext_status = ALREADY_PROCESSED; + } + clear_doctext(); + } + +CaptureDocText: + { + if (g_parse_mode == PROGRAM) { + $$ = g_doctext; + g_doctext = NULL; + } else { + $$ = NULL; + } + } + +/* TODO(dreiss): Try to DestroyDocText in all sorts or random places. */ +DestroyDocText: + { + if (g_parse_mode == PROGRAM) { + clear_doctext(); + } + } + +/* We have to DestroyDocText here, otherwise it catches the doctext + on the first real element. */ +HeaderList: + HeaderList DestroyDocText Header + { + pdebug("HeaderList -> HeaderList Header"); + } +| + { + pdebug("HeaderList -> "); + } + +Header: + Include + { + pdebug("Header -> Include"); + } +| tok_namespace tok_identifier tok_identifier TypeAnnotations + { + pdebug("Header -> tok_namespace tok_identifier tok_identifier"); + declare_valid_program_doctext(); + if (g_parse_mode == PROGRAM) { + g_program->set_namespace($2, $3); + } + if ($4 != NULL) { + g_program->set_namespace_annotations($2, $4->annotations_); + delete $4; + } + } +| tok_namespace '*' tok_identifier + { + pdebug("Header -> tok_namespace * tok_identifier"); + declare_valid_program_doctext(); + if (g_parse_mode == PROGRAM) { + g_program->set_namespace("*", $3); + } + } +| tok_cpp_include tok_literal + { + pdebug("Header -> tok_cpp_include tok_literal"); + declare_valid_program_doctext(); + if (g_parse_mode == PROGRAM) { + g_program->add_cpp_include($2); + } + } + +Include: + tok_include tok_literal + { + pdebug("Include -> tok_include tok_literal"); + declare_valid_program_doctext(); + if (g_parse_mode == INCLUDES) { + std::string path = include_file(std::string($2)); + if (!path.empty()) { + g_program->add_include(path, std::string($2)); + } + } + } + +DefinitionList: + DefinitionList CaptureDocText Definition + { + pdebug("DefinitionList -> DefinitionList Definition"); + if ($2 != NULL && $3 != NULL) { + $3->set_doc($2); + } + } +| + { + pdebug("DefinitionList -> "); + } + +Definition: + Const + { + pdebug("Definition -> Const"); + if (g_parse_mode == PROGRAM) { + g_program->add_const($1); + } + $$ = $1; + } +| TypeDefinition + { + pdebug("Definition -> TypeDefinition"); + if (g_parse_mode == PROGRAM) { + g_scope->add_type($1->get_name(), $1); + if (g_parent_scope != NULL) { + g_parent_scope->add_type(g_parent_prefix + $1->get_name(), $1); + } + if (! g_program->is_unique_typename($1)) { + yyerror("Type \"%s\" is already defined.", $1->get_name().c_str()); + exit(1); + } + } + $$ = $1; + } +| Service + { + pdebug("Definition -> Service"); + if (g_parse_mode == PROGRAM) { + g_scope->add_service($1->get_name(), $1); + if (g_parent_scope != NULL) { + g_parent_scope->add_service(g_parent_prefix + $1->get_name(), $1); + } + g_program->add_service($1); + if (! g_program->is_unique_typename($1)) { + yyerror("Type \"%s\" is already defined.", $1->get_name().c_str()); + exit(1); + } + } + $$ = $1; + } + +TypeDefinition: + Typedef + { + pdebug("TypeDefinition -> Typedef"); + if (g_parse_mode == PROGRAM) { + g_program->add_typedef($1); + } + } +| Enum + { + pdebug("TypeDefinition -> Enum"); + if (g_parse_mode == PROGRAM) { + g_program->add_enum($1); + } + } +| Senum + { + pdebug("TypeDefinition -> Senum"); + if (g_parse_mode == PROGRAM) { + g_program->add_typedef($1); + } + } +| Struct + { + pdebug("TypeDefinition -> Struct"); + if (g_parse_mode == PROGRAM) { + g_program->add_struct($1); + } + } +| Xception + { + pdebug("TypeDefinition -> Xception"); + if (g_parse_mode == PROGRAM) { + g_program->add_xception($1); + } + } + +CommaOrSemicolonOptional: + ',' + {} +| ';' + {} +| + {} + +Typedef: + tok_typedef FieldType tok_identifier TypeAnnotations CommaOrSemicolonOptional + { + pdebug("TypeDef -> tok_typedef FieldType tok_identifier"); + validate_simple_identifier( $3); + t_typedef *td = new t_typedef(g_program, $2, $3); + $$ = td; + if ($4 != NULL) { + $$->annotations_ = $4->annotations_; + delete $4; + } + } + +Enum: + tok_enum tok_identifier '{' EnumDefList '}' TypeAnnotations + { + pdebug("Enum -> tok_enum tok_identifier { EnumDefList }"); + $$ = $4; + validate_simple_identifier( $2); + $$->set_name($2); + if ($6 != NULL) { + $$->annotations_ = $6->annotations_; + delete $6; + } + + // make constants for all the enum values + if (g_parse_mode == PROGRAM) { + const std::vector<t_enum_value*>& enum_values = $$->get_constants(); + std::vector<t_enum_value*>::const_iterator c_iter; + for (c_iter = enum_values.begin(); c_iter != enum_values.end(); ++c_iter) { + std::string const_name = $$->get_name() + "." + (*c_iter)->get_name(); + t_const_value* const_val = new t_const_value((*c_iter)->get_value()); + const_val->set_enum($$); + g_scope->add_constant(const_name, new t_const(g_type_i32, (*c_iter)->get_name(), const_val)); + if (g_parent_scope != NULL) { + g_parent_scope->add_constant(g_parent_prefix + const_name, new t_const(g_type_i32, (*c_iter)->get_name(), const_val)); + } + } + } + } + +EnumDefList: + EnumDefList EnumDef + { + pdebug("EnumDefList -> EnumDefList EnumDef"); + $$ = $1; + $$->append($2); + } +| + { + pdebug("EnumDefList -> "); + $$ = new t_enum(g_program); + y_enum_val = -1; + } + +EnumDef: + CaptureDocText EnumValue TypeAnnotations CommaOrSemicolonOptional + { + pdebug("EnumDef -> EnumValue"); + $$ = $2; + if ($1 != NULL) { + $$->set_doc($1); + } + if ($3 != NULL) { + $$->annotations_ = $3->annotations_; + delete $3; + } + } + +EnumValue: + tok_identifier '=' tok_int_constant + { + pdebug("EnumValue -> tok_identifier = tok_int_constant"); + if ($3 < INT32_MIN || $3 > INT32_MAX) { + // Note: this used to be just a warning. However, since thrift always + // treats enums as i32 values, I'm changing it to a fatal error. + // I doubt this will affect many people, but users who run into this + // will have to update their thrift files to manually specify the + // truncated i32 value that thrift has always been using anyway. + failure("64-bit value supplied for enum %s will be truncated.", $1); + } + y_enum_val = static_cast<int32_t>($3); + $$ = new t_enum_value($1, y_enum_val); + } + | + tok_identifier + { + pdebug("EnumValue -> tok_identifier"); + validate_simple_identifier( $1); + if (y_enum_val == INT32_MAX) { + failure("enum value overflow at enum %s", $1); + } + ++y_enum_val; + $$ = new t_enum_value($1, y_enum_val); + } + +Senum: + tok_senum tok_identifier '{' SenumDefList '}' TypeAnnotations + { + pdebug("Senum -> tok_senum tok_identifier { SenumDefList }"); + validate_simple_identifier( $2); + $$ = new t_typedef(g_program, $4, $2); + if ($6 != NULL) { + $$->annotations_ = $6->annotations_; + delete $6; + } + } + +SenumDefList: + SenumDefList SenumDef + { + pdebug("SenumDefList -> SenumDefList SenumDef"); + $$ = $1; + $$->add_string_enum_val($2); + } +| + { + pdebug("SenumDefList -> "); + $$ = new t_base_type("string", t_base_type::TYPE_STRING); + $$->set_string_enum(true); + } + +SenumDef: + tok_literal CommaOrSemicolonOptional + { + pdebug("SenumDef -> tok_literal"); + $$ = $1; + } + +Const: + tok_const FieldType tok_identifier '=' ConstValue CommaOrSemicolonOptional + { + pdebug("Const -> tok_const FieldType tok_identifier = ConstValue"); + if (g_parse_mode == PROGRAM) { + validate_simple_identifier( $3); + g_scope->resolve_const_value($5, $2); + $$ = new t_const($2, $3, $5); + validate_const_type($$); + + g_scope->add_constant($3, $$); + if (g_parent_scope != NULL) { + g_parent_scope->add_constant(g_parent_prefix + $3, $$); + } + } else { + $$ = NULL; + } + } + +ConstValue: + tok_int_constant + { + pdebug("ConstValue => tok_int_constant"); + $$ = new t_const_value(); + $$->set_integer($1); + if (!g_allow_64bit_consts && ($1 < INT32_MIN || $1 > INT32_MAX)) { + pwarning(1, "64-bit constant \"%" PRIi64"\" may not work in all languages.\n", $1); + } + } +| tok_dub_constant + { + pdebug("ConstValue => tok_dub_constant"); + $$ = new t_const_value(); + $$->set_double($1); + } +| tok_literal + { + pdebug("ConstValue => tok_literal"); + $$ = new t_const_value($1); + } +| tok_identifier + { + pdebug("ConstValue => tok_identifier"); + $$ = new t_const_value(); + $$->set_identifier($1); + } +| ConstList + { + pdebug("ConstValue => ConstList"); + $$ = $1; + } +| ConstMap + { + pdebug("ConstValue => ConstMap"); + $$ = $1; + } + +ConstList: + '[' ConstListContents ']' + { + pdebug("ConstList => [ ConstListContents ]"); + $$ = $2; + } + +ConstListContents: + ConstListContents ConstValue CommaOrSemicolonOptional + { + pdebug("ConstListContents => ConstListContents ConstValue CommaOrSemicolonOptional"); + $$ = $1; + $$->add_list($2); + } +| + { + pdebug("ConstListContents =>"); + $$ = new t_const_value(); + $$->set_list(); + } + +ConstMap: + '{' ConstMapContents '}' + { + pdebug("ConstMap => { ConstMapContents }"); + $$ = $2; + } + +ConstMapContents: + ConstMapContents ConstValue ':' ConstValue CommaOrSemicolonOptional + { + pdebug("ConstMapContents => ConstMapContents ConstValue CommaOrSemicolonOptional"); + $$ = $1; + $$->add_map($2, $4); + } +| + { + pdebug("ConstMapContents =>"); + $$ = new t_const_value(); + $$->set_map(); + } + +StructHead: + tok_struct + { + $$ = struct_is_struct; + } +| tok_union + { + $$ = struct_is_union; + } + +Struct: + StructHead tok_identifier XsdAll '{' FieldList '}' TypeAnnotations + { + pdebug("Struct -> tok_struct tok_identifier { FieldList }"); + validate_simple_identifier( $2); + $5->set_xsd_all($3); + $5->set_union($1 == struct_is_union); + $$ = $5; + $$->set_name($2); + if ($7 != NULL) { + $$->annotations_ = $7->annotations_; + delete $7; + } + } + +XsdAll: + tok_xsd_all + { + $$ = true; + } +| + { + $$ = false; + } + +XsdOptional: + tok_xsd_optional + { + $$ = true; + } +| + { + $$ = false; + } + +XsdNillable: + tok_xsd_nillable + { + $$ = true; + } +| + { + $$ = false; + } + +XsdAttributes: + tok_xsd_attrs '{' FieldList '}' + { + $$ = $3; + } +| + { + $$ = NULL; + } + +Xception: + tok_xception tok_identifier '{' FieldList '}' TypeAnnotations + { + pdebug("Xception -> tok_xception tok_identifier { FieldList }"); + validate_simple_identifier( $2); + $4->set_name($2); + $4->set_xception(true); + $$ = $4; + if ($6 != NULL) { + $$->annotations_ = $6->annotations_; + delete $6; + } + } + +Service: + tok_service tok_identifier Extends '{' FlagArgs FunctionList UnflagArgs '}' TypeAnnotations + { + pdebug("Service -> tok_service tok_identifier { FunctionList }"); + validate_simple_identifier( $2); + $$ = $6; + $$->set_name($2); + $$->set_extends($3); + if ($9 != NULL) { + $$->annotations_ = $9->annotations_; + delete $9; + } + } + +FlagArgs: + { + g_arglist = 1; + } + +UnflagArgs: + { + g_arglist = 0; + } + +Extends: + tok_extends tok_identifier + { + pdebug("Extends -> tok_extends tok_identifier"); + $$ = NULL; + if (g_parse_mode == PROGRAM) { + $$ = g_scope->get_service($2); + if ($$ == NULL) { + yyerror("Service \"%s\" has not been defined.", $2); + exit(1); + } + } + } +| + { + $$ = NULL; + } + +FunctionList: + FunctionList Function + { + pdebug("FunctionList -> FunctionList Function"); + $$ = $1; + $1->add_function($2); + } +| + { + pdebug("FunctionList -> "); + $$ = new t_service(g_program); + } + +Function: + CaptureDocText Oneway FunctionType tok_identifier '(' FieldList ')' Throws TypeAnnotations CommaOrSemicolonOptional + { + validate_simple_identifier( $4); + $6->set_name(std::string($4) + "_args"); + $$ = new t_function($3, $4, $6, $8, $2); + if ($1 != NULL) { + $$->set_doc($1); + } + if ($9 != NULL) { + $$->annotations_ = $9->annotations_; + delete $9; + } + } + +Oneway: + tok_oneway + { + $$ = true; + } +| + { + $$ = false; + } + +Throws: + tok_throws '(' FieldList ')' + { + pdebug("Throws -> tok_throws ( FieldList )"); + $$ = $3; + if (g_parse_mode == PROGRAM && !validate_throws($$)) { + yyerror("Throws clause may not contain non-exception types"); + exit(1); + } + } +| + { + $$ = new t_struct(g_program); + } + +FieldList: + FieldList Field + { + pdebug("FieldList -> FieldList , Field"); + $$ = $1; + if (!($$->append($2))) { + yyerror("\"%d: %s\" - field identifier/name has already been used", $2->get_key(), $2->get_name().c_str()); + exit(1); + } + } +| + { + pdebug("FieldList -> "); + y_field_val = -1; + $$ = new t_struct(g_program); + } + +Field: + CaptureDocText FieldIdentifier FieldRequiredness FieldType FieldReference tok_identifier FieldValue XsdOptional XsdNillable XsdAttributes TypeAnnotations CommaOrSemicolonOptional + { + pdebug("tok_int_constant : Field -> FieldType tok_identifier"); + if ($2.auto_assigned) { + pwarning(1, "No field key specified for %s, resulting protocol may have conflicts or not be backwards compatible!\n", $6); + if (g_strict >= 192) { + yyerror("Implicit field keys are deprecated and not allowed with -strict"); + exit(1); + } + } + validate_simple_identifier($6); + $$ = new t_field($4, $6, $2.value); + $$->set_reference($5); + $$->set_req($3); + if ($7 != NULL) { + g_scope->resolve_const_value($7, $4); + validate_field_value($$, $7); + $$->set_value($7); + } + $$->set_xsd_optional($8); + $$->set_xsd_nillable($9); + if ($1 != NULL) { + $$->set_doc($1); + } + if ($10 != NULL) { + $$->set_xsd_attrs($10); + } + if ($11 != NULL) { + $$->annotations_ = $11->annotations_; + delete $11; + } + } + +FieldIdentifier: + tok_int_constant ':' + { + if ($1 <= 0) { + if (g_allow_neg_field_keys) { + /* + * g_allow_neg_field_keys exists to allow users to add explicitly + * specified key values to old .thrift files without breaking + * protocol compatibility. + */ + if ($1 != y_field_val) { + /* + * warn if the user-specified negative value isn't what + * thrift would have auto-assigned. + */ + pwarning(1, "Nonpositive field key (%" PRIi64") differs from what would be " + "auto-assigned by thrift (%d).\n", $1, y_field_val); + } + /* + * Leave $1 as-is, and update y_field_val to be one less than $1. + * The FieldList parsing will catch any duplicate key values. + */ + y_field_val = static_cast<int32_t>($1 - 1); + $$.value = static_cast<int32_t>($1); + $$.auto_assigned = false; + } else { + pwarning(1, "Nonpositive value (%d) not allowed as a field key.\n", + $1); + $$.value = y_field_val--; + $$.auto_assigned = true; + } + } else { + $$.value = static_cast<int32_t>($1); + $$.auto_assigned = false; + } + if( (SHRT_MIN > $$.value) || ($$.value > SHRT_MAX)) { + pwarning(1, "Field key (%d) exceeds allowed range (%d..%d).\n", + $$.value, SHRT_MIN, SHRT_MAX); + } + } +| + { + $$.value = y_field_val--; + $$.auto_assigned = true; + if( (SHRT_MIN > $$.value) || ($$.value > SHRT_MAX)) { + pwarning(1, "Field key (%d) exceeds allowed range (%d..%d).\n", + $$.value, SHRT_MIN, SHRT_MAX); + } + } + +FieldReference: + tok_reference + { + $$ = true; + } +| + { + $$ = false; + } + +FieldRequiredness: + tok_required + { + $$ = t_field::T_REQUIRED; + } +| tok_optional + { + if (g_arglist) { + if (g_parse_mode == PROGRAM) { + pwarning(1, "optional keyword is ignored in argument lists.\n"); + } + $$ = t_field::T_OPT_IN_REQ_OUT; + } else { + $$ = t_field::T_OPTIONAL; + } + } +| + { + $$ = t_field::T_OPT_IN_REQ_OUT; + } + +FieldValue: + '=' ConstValue + { + if (g_parse_mode == PROGRAM) { + $$ = $2; + } else { + $$ = NULL; + } + } +| + { + $$ = NULL; + } + +FunctionType: + FieldType + { + pdebug("FunctionType -> FieldType"); + $$ = $1; + } +| tok_void + { + pdebug("FunctionType -> tok_void"); + $$ = g_type_void; + } + +FieldType: + tok_identifier + { + pdebug("FieldType -> tok_identifier"); + if (g_parse_mode == INCLUDES) { + // Ignore identifiers in include mode + $$ = NULL; + } else { + // Lookup the identifier in the current scope + $$ = g_scope->get_type($1); + if ($$ == NULL) { + /* + * Either this type isn't yet declared, or it's never + declared. Either way allow it and we'll figure it out + during generation. + */ + $$ = new t_typedef(g_program, $1, true); + } + } + } +| BaseType + { + pdebug("FieldType -> BaseType"); + $$ = $1; + } +| ContainerType + { + pdebug("FieldType -> ContainerType"); + $$ = $1; + } + +BaseType: SimpleBaseType TypeAnnotations + { + pdebug("BaseType -> SimpleBaseType TypeAnnotations"); + if ($2 != NULL) { + $$ = new t_base_type(*static_cast<t_base_type*>($1)); + $$->annotations_ = $2->annotations_; + delete $2; + } else { + $$ = $1; + } + } + +SimpleBaseType: + tok_string + { + pdebug("BaseType -> tok_string"); + $$ = g_type_string; + } +| tok_binary + { + pdebug("BaseType -> tok_binary"); + $$ = g_type_binary; + } +| tok_slist + { + pdebug("BaseType -> tok_slist"); + $$ = g_type_slist; + } +| tok_bool + { + pdebug("BaseType -> tok_bool"); + $$ = g_type_bool; + } +| tok_i8 + { + pdebug("BaseType -> tok_i8"); + $$ = g_type_i8; + } +| tok_i16 + { + pdebug("BaseType -> tok_i16"); + $$ = g_type_i16; + } +| tok_i32 + { + pdebug("BaseType -> tok_i32"); + $$ = g_type_i32; + } +| tok_i64 + { + pdebug("BaseType -> tok_i64"); + $$ = g_type_i64; + } +| tok_double + { + pdebug("BaseType -> tok_double"); + $$ = g_type_double; + } + +ContainerType: SimpleContainerType TypeAnnotations + { + pdebug("ContainerType -> SimpleContainerType TypeAnnotations"); + $$ = $1; + if ($2 != NULL) { + $$->annotations_ = $2->annotations_; + delete $2; + } + } + +SimpleContainerType: + MapType + { + pdebug("SimpleContainerType -> MapType"); + $$ = $1; + } +| SetType + { + pdebug("SimpleContainerType -> SetType"); + $$ = $1; + } +| ListType + { + pdebug("SimpleContainerType -> ListType"); + $$ = $1; + } + +MapType: + tok_map CppType '<' FieldType ',' FieldType '>' + { + pdebug("MapType -> tok_map <FieldType, FieldType>"); + $$ = new t_map($4, $6); + if ($2 != NULL) { + ((t_container*)$$)->set_cpp_name(std::string($2)); + } + } + +SetType: + tok_set CppType '<' FieldType '>' + { + pdebug("SetType -> tok_set<FieldType>"); + $$ = new t_set($4); + if ($2 != NULL) { + ((t_container*)$$)->set_cpp_name(std::string($2)); + } + } + +ListType: + tok_list '<' FieldType '>' CppType + { + pdebug("ListType -> tok_list<FieldType>"); + check_for_list_of_bytes($3); + $$ = new t_list($3); + if ($5 != NULL) { + ((t_container*)$$)->set_cpp_name(std::string($5)); + } + } + +CppType: + tok_cpp_type tok_literal + { + $$ = $2; + } +| + { + $$ = NULL; + } + +TypeAnnotations: + '(' TypeAnnotationList ')' + { + pdebug("TypeAnnotations -> ( TypeAnnotationList )"); + $$ = $2; + } +| + { + $$ = NULL; + } + +TypeAnnotationList: + TypeAnnotationList TypeAnnotation + { + pdebug("TypeAnnotationList -> TypeAnnotationList , TypeAnnotation"); + $$ = $1; + $$->annotations_[$2->key] = $2->val; + delete $2; + } +| + { + /* Just use a dummy structure to hold the annotations. */ + $$ = new t_struct(g_program); + } + +TypeAnnotation: + tok_identifier TypeAnnotationValue CommaOrSemicolonOptional + { + pdebug("TypeAnnotation -> TypeAnnotationValue"); + $$ = new t_annotation; + $$->key = $1; + $$->val = $2; + } + +TypeAnnotationValue: + '=' tok_literal + { + pdebug("TypeAnnotationValue -> = tok_literal"); + $$ = $2; + } +| + { + pdebug("TypeAnnotationValue ->"); + $$ = strdup("1"); + } + +%% diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/version.h.in b/src/jaegertracing/thrift/compiler/cpp/src/thrift/version.h.in new file mode 100644 index 000000000..aef076f7f --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/version.h.in @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef _THRIFT_VERSION_H_ +#define _THRIFT_VERSION_H_ 1 + +#if defined(_MSC_VER) && (_MSC_VER > 1200) +#pragma once +#endif // _MSC_VER + +#define THRIFT_VERSION "@PACKAGE_VERSION@" + +#endif // _THRIFT_VERSION_H_ diff --git a/src/jaegertracing/thrift/compiler/cpp/src/thrift/windows/config.h b/src/jaegertracing/thrift/compiler/cpp/src/thrift/windows/config.h new file mode 100644 index 000000000..6ba4f9ae6 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/src/thrift/windows/config.h @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef _THRIFT_WINDOWS_CONFIG_H_ +#define _THRIFT_WINDOWS_CONFIG_H_ 1 + +#if defined(_MSC_VER) && (_MSC_VER > 1200) +#pragma once +#endif // _MSC_VER + +#ifndef _WIN32 +#error "This is a Windows header only" +#endif + +#include <io.h> +#include <stdlib.h> +#include <direct.h> + +#define strtoll(begin_ptr, end_ptr, length) _strtoi64(begin_ptr, end_ptr, length) + +#ifndef PRIu64 +#define PRIu64 "I64u" +#endif + +#ifndef PRIi64 +#define PRIi64 "I64i" +#endif +// squelch deprecation warnings +#pragma warning(disable : 4996) +// squelch bool conversion performance warning +#pragma warning(disable : 4800) + +#include <cstdint> + +#endif // _THRIFT_WINDOWS_CONFIG_H_ diff --git a/src/jaegertracing/thrift/compiler/cpp/test/CMakeLists.txt b/src/jaegertracing/thrift/compiler/cpp/test/CMakeLists.txt new file mode 100644 index 000000000..2bc7e9e69 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/test/CMakeLists.txt @@ -0,0 +1,43 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Unit tests for the compiler still require boost +include(BoostMacros) +REQUIRE_BOOST_HEADERS() +set(BOOST_COMPONENTS unit_test_framework) +REQUIRE_BOOST_LIBRARIES(BOOST_COMPONENTS) + +file(GLOB KEYWORD_SAMPLES "${CMAKE_CURRENT_SOURCE_DIR}/keyword-samples/*.thrift") +foreach(LANG ${thrift_compiler_LANGS}) + foreach(SAMPLE ${KEYWORD_SAMPLES}) + get_filename_component(FILENAME ${SAMPLE} NAME_WE) + add_test(NAME "${LANG}_${FILENAME}" + COMMAND thrift-compiler --gen ${LANG} ${SAMPLE}) + set_tests_properties("${LANG}_${FILENAME}" PROPERTIES + PASS_REGULAR_EXPRESSION "Cannot use reserved language keyword") + endforeach() +endforeach() + + +find_package(PythonInterp QUIET) +if(PYTHONINTERP_FOUND) + add_test(NAME StalenessCheckTest COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/compiler/staleness_check.py ${THRIFT_COMPILER}) +else() + message(WARNING "Skipping StalenessCheckTest as there is no python interpreter available.") +endif() diff --git a/src/jaegertracing/thrift/compiler/cpp/test/Makefile.am b/src/jaegertracing/thrift/compiler/cpp/test/Makefile.am new file mode 100644 index 000000000..801f9d85e --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/test/Makefile.am @@ -0,0 +1,28 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# +# Contains some contributions under the Thrift Software License. +# Please see doc/old-thrift-license.txt in the Thrift distribution for +# details. + +AUTOMAKE_OPTIONS = subdir-objects serial-tests nostdinc + +AM_CPPFLAGS = $(BOOST_CPPFLAGS) -I$(top_srcdir)/compiler/cpp/src +AM_LDFLAGS = $(BOOST_LDFLAGS) +AM_CXXFLAGS = -Wall -Wextra -pedantic diff --git a/src/jaegertracing/thrift/compiler/cpp/test/bincat.sh b/src/jaegertracing/thrift/compiler/cpp/test/bincat.sh new file mode 100755 index 000000000..c7f90785e --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/test/bincat.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +exec /bin/cat diff --git a/src/jaegertracing/thrift/compiler/cpp/test/compiler/Included.thrift b/src/jaegertracing/thrift/compiler/cpp/test/compiler/Included.thrift new file mode 100644 index 000000000..ce84ab6b9 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/test/compiler/Included.thrift @@ -0,0 +1,18 @@ +const string foo = "bar" + +struct a_struct { + 1: bool im_true, + 2: bool im_false, + 3: i8 a_bite, + 4: i16 integer16, + 5: i32 integer32, + 6: i64 integer64, + 7: double double_precision, + 8: string some_characters, + 9: string zomg_unicode, + 10: bool what_who, +} + +service AService { + i32 a_procedure(1: i32 arg) +} diff --git a/src/jaegertracing/thrift/compiler/cpp/test/compiler/Including.thrift b/src/jaegertracing/thrift/compiler/cpp/test/compiler/Including.thrift new file mode 100644 index 000000000..677af7bb2 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/test/compiler/Including.thrift @@ -0,0 +1,7 @@ +include "Included.thrift" + +const string s = "string" + +struct BStruct { + 1: Included.a_struct one_of_each +} diff --git a/src/jaegertracing/thrift/compiler/cpp/test/compiler/Single.thrift b/src/jaegertracing/thrift/compiler/cpp/test/compiler/Single.thrift new file mode 100644 index 000000000..2ec301f50 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/test/compiler/Single.thrift @@ -0,0 +1 @@ +const string foo = "bar" diff --git a/src/jaegertracing/thrift/compiler/cpp/test/compiler/staleness_check.py b/src/jaegertracing/thrift/compiler/cpp/test/compiler/staleness_check.py new file mode 100755 index 000000000..5b11dff9e --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/test/compiler/staleness_check.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +from __future__ import print_function +import os +import shutil +import subprocess +import sys +import tempfile +import time +import unittest + + +class TestStalenessCheck(unittest.TestCase): + + CURRENT_DIR_PATH = os.path.dirname(os.path.realpath(__file__)) + THRIFT_EXECUTABLE_PATH = None + SINGLE_THRIFT_FILE_PATH = os.path.join(CURRENT_DIR_PATH, "Single.thrift") + INCLUDING_THRIFT_FILE_PATH = os.path.join(CURRENT_DIR_PATH, "Including.thrift") + INCLUDED_THRIFT_FILE_PATH = os.path.join(CURRENT_DIR_PATH, "Included.thrift") + + def test_staleness_check_of_single_thrift_file_without_changed_output(self): + temp_dir = tempfile.mkdtemp(dir=TestStalenessCheck.CURRENT_DIR_PATH) + + command = [TestStalenessCheck.THRIFT_EXECUTABLE_PATH, "-gen", "cpp", "-o", temp_dir] + command += [TestStalenessCheck.SINGLE_THRIFT_FILE_PATH] + subprocess.call(command) + + used_file_path = os.path.join(temp_dir, "gen-cpp", "Single_constants.cpp") + + first_modification_time = os.path.getmtime(os.path.join(used_file_path)) + + time.sleep(0.1) + + subprocess.call(command) + + second_modification_time = os.path.getmtime(used_file_path) + + self.assertEqual(second_modification_time, first_modification_time) + + shutil.rmtree(temp_dir, ignore_errors=True) + + def test_staleness_check_of_single_thrift_file_with_changed_output(self): + temp_dir = tempfile.mkdtemp(dir=TestStalenessCheck.CURRENT_DIR_PATH) + + command = [TestStalenessCheck.THRIFT_EXECUTABLE_PATH, "-gen", "cpp", "-o", temp_dir] + command += [TestStalenessCheck.SINGLE_THRIFT_FILE_PATH] + subprocess.call(command) + + used_file_path = os.path.join(temp_dir, "gen-cpp", "Single_constants.cpp") + + first_modification_time = os.path.getmtime(os.path.join(used_file_path)) + used_file = open(used_file_path, "r") + first_contents = used_file.read() + used_file.close() + + used_file = open(used_file_path, "a") + used_file.write("\n/* This is a comment */\n") + used_file.close() + + time.sleep(0.1) + + subprocess.call(command) + + second_modification_time = os.path.getmtime(used_file_path) + used_file = open(used_file_path, "r") + second_contents = used_file.read() + used_file.close() + + self.assertGreater(second_modification_time, first_modification_time) + self.assertEqual(first_contents, second_contents) + + shutil.rmtree(temp_dir, ignore_errors=True) + + def test_staleness_check_of_included_file(self): + temp_dir = tempfile.mkdtemp(dir=TestStalenessCheck.CURRENT_DIR_PATH) + + temp_included_file_path = os.path.join(temp_dir, "Included.thrift") + temp_including_file_path = os.path.join(temp_dir, "Including.thrift") + + shutil.copy2(TestStalenessCheck.INCLUDED_THRIFT_FILE_PATH, temp_included_file_path) + shutil.copy2(TestStalenessCheck.INCLUDING_THRIFT_FILE_PATH, temp_including_file_path) + + command = [TestStalenessCheck.THRIFT_EXECUTABLE_PATH, "-gen", "cpp", "-recurse", "-o", temp_dir] + command += [temp_including_file_path] + + subprocess.call(command) + + included_constants_cpp_file_path = os.path.join(temp_dir, "gen-cpp", "Included_constants.cpp") + including_constants_cpp_file_path = os.path.join(temp_dir, "gen-cpp", "Including_constants.cpp") + + included_constants_cpp_first_modification_time = os.path.getmtime(included_constants_cpp_file_path) + including_constants_cpp_first_modification_time = os.path.getmtime(including_constants_cpp_file_path) + + temp_included_file = open(temp_included_file_path, "a") + temp_included_file.write("\nconst i32 an_integer = 42\n") + temp_included_file.close() + + time.sleep(0.1) + + subprocess.call(command) + + included_constants_cpp_second_modification_time = os.path.getmtime(included_constants_cpp_file_path) + including_constants_cpp_second_modification_time = os.path.getmtime(including_constants_cpp_file_path) + + self.assertGreater( + included_constants_cpp_second_modification_time, included_constants_cpp_first_modification_time) + self.assertEqual( + including_constants_cpp_first_modification_time, including_constants_cpp_second_modification_time) + + shutil.rmtree(temp_dir, ignore_errors=True) + + +def suite(): + suite = unittest.TestSuite() + loader = unittest.TestLoader() + suite.addTest(loader.loadTestsFromTestCase(TestStalenessCheck)) + return suite + + +if __name__ == "__main__": + # The path of Thrift compiler is passed as an argument to the test script. + # Remove it to not confuse the unit testing framework + TestStalenessCheck.THRIFT_EXECUTABLE_PATH = sys.argv[-1] + del sys.argv[-1] + unittest.main(defaultTest="suite", testRunner=unittest.TextTestRunner(verbosity=2)) diff --git a/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/const1_return.thrift b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/const1_return.thrift new file mode 100644 index 000000000..735e4acd8 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/const1_return.thrift @@ -0,0 +1 @@ +const bool return = 0 diff --git a/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/enum1_return.thrift b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/enum1_return.thrift new file mode 100644 index 000000000..6d834e1da --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/enum1_return.thrift @@ -0,0 +1,2 @@ +enum return { +} diff --git a/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/enum2_return.thrift b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/enum2_return.thrift new file mode 100644 index 000000000..a2caa8e14 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/enum2_return.thrift @@ -0,0 +1,3 @@ +enum enum_name { + return +} diff --git a/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/exception1_return.thrift b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/exception1_return.thrift new file mode 100644 index 000000000..eadb33834 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/exception1_return.thrift @@ -0,0 +1 @@ +exception return {} diff --git a/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/exception2_return.thrift b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/exception2_return.thrift new file mode 100644 index 000000000..493c35297 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/exception2_return.thrift @@ -0,0 +1,3 @@ +exception exception_name { + 1: required i8 return +} diff --git a/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/service1_return.thrift b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/service1_return.thrift new file mode 100644 index 000000000..5286a3691 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/service1_return.thrift @@ -0,0 +1 @@ +service return {} diff --git a/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/service2_return.thrift b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/service2_return.thrift new file mode 100644 index 000000000..6f7331da0 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/service2_return.thrift @@ -0,0 +1,3 @@ +service service_name { + bool function_name(1: i32 return) +} diff --git a/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/service3_return.thrift b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/service3_return.thrift new file mode 100644 index 000000000..c6dd946fd --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/service3_return.thrift @@ -0,0 +1,3 @@ +service service_name { + void return() +} diff --git a/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/service4_return.thrift b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/service4_return.thrift new file mode 100644 index 000000000..d0787dfde --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/service4_return.thrift @@ -0,0 +1,5 @@ +exception exception_name {} + +service service_name { + void function_name() throws ( 1: exception_name return) +} diff --git a/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/struct1_return.thrift b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/struct1_return.thrift new file mode 100644 index 000000000..c82b8b9ca --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/struct1_return.thrift @@ -0,0 +1 @@ +struct return {} diff --git a/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/struct2_return.thrift b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/struct2_return.thrift new file mode 100644 index 000000000..a0700d101 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/struct2_return.thrift @@ -0,0 +1,3 @@ +struct struct_name { + 1: required bool return = 1 +} diff --git a/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/typedef1_return.thrift b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/typedef1_return.thrift new file mode 100644 index 000000000..f159bb880 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/typedef1_return.thrift @@ -0,0 +1 @@ +typedef bool return diff --git a/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/union1_return.thrift b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/union1_return.thrift new file mode 100644 index 000000000..368df1383 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/union1_return.thrift @@ -0,0 +1 @@ +union return {} diff --git a/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/union2_return.thrift b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/union2_return.thrift new file mode 100644 index 000000000..9719d1e40 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/test/keyword-samples/union2_return.thrift @@ -0,0 +1,3 @@ +union union_name { + 1: optional bool return=1 +} diff --git a/src/jaegertracing/thrift/compiler/cpp/tests/CMakeLists.txt b/src/jaegertracing/thrift/compiler/cpp/tests/CMakeLists.txt new file mode 100644 index 000000000..fde9073ca --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/tests/CMakeLists.txt @@ -0,0 +1,153 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +cmake_minimum_required(VERSION 2.8.12) + +project(thrift_compiler_tests) + +set(THRIFT_COMPILER_SOURCE_DIR + ${CMAKE_CURRENT_SOURCE_DIR}/.. +) + +# don't generate ZERO_CHECK +set(CMAKE_SUPPRESS_REGENERATION true) + +configure_file(${THRIFT_COMPILER_SOURCE_DIR}/src/thrift/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/thrift/version.h) +if(MSVC) + # The winflexbison generator outputs some macros that conflict with the Visual Studio 2010 copy of stdint.h + # This might be fixed in later versions of Visual Studio, but an easy solution is to include stdint.h first + if(HAVE_STDINT_H) + add_definitions(-D__STDC_LIMIT_MACROS) + add_definitions(/FI"stdint.h") + endif(HAVE_STDINT_H) +endif() + +find_package(FLEX REQUIRED) +find_package(BISON REQUIRED) + +# create directory for thrifty and thriftl +file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/thrift/) + +# Create flex and bison files and build the lib parse static library +BISON_TARGET(thrifty ${THRIFT_COMPILER_SOURCE_DIR}/src/thrift/thrifty.yy ${CMAKE_CURRENT_BINARY_DIR}/thrift/thrifty.cc) +FLEX_TARGET(thriftl ${THRIFT_COMPILER_SOURCE_DIR}/src/thrift/thriftl.ll ${CMAKE_CURRENT_BINARY_DIR}/thrift/thriftl.cc) +ADD_FLEX_BISON_DEPENDENCY(thriftl thrifty) + +set(parse_SOURCES + ${CMAKE_CURRENT_BINARY_DIR}/thrift/thrifty.cc + ${CMAKE_CURRENT_BINARY_DIR}/thrift/thriftl.cc + ${CMAKE_CURRENT_BINARY_DIR}/thrift/thrifty.hh +) + +add_library(parse STATIC ${parse_SOURCES}) + +# Thrift compiler tests +set(thrift_compiler_tests +) + +# you can add some files manually there +set(thrift_compiler_tests_manual_SOURCES + # tests file to avoid main in every test file + ${CMAKE_CURRENT_SOURCE_DIR}/tests_main.cc +) + +# set variable for tests sources - will be filled later +set(thrift_compiler_tests_SOURCES +) + +set(thrift_compiler_SOURCES + ${THRIFT_COMPILER_SOURCE_DIR}/src/thrift/logging.cc # we use logging instead of main to avoid breaking compillation (2 main v) + ${THRIFT_COMPILER_SOURCE_DIR}/src/thrift/audit/t_audit.cpp + ${THRIFT_COMPILER_SOURCE_DIR}/src/thrift/common.cc + ${THRIFT_COMPILER_SOURCE_DIR}/src/thrift/generate/t_generator.cc + ${THRIFT_COMPILER_SOURCE_DIR}/src/thrift/parse/t_typedef.cc + ${THRIFT_COMPILER_SOURCE_DIR}/src/thrift/parse/parse.cc + ${CMAKE_CURRENT_BINARY_DIR}/thrift/version.h +) + +# This macro adds an option THRIFT_COMPILER_${NAME} +# that allows enabling or disabling certain languages +macro(THRIFT_ADD_COMPILER name description initial) + string(TOUPPER "THRIFT_COMPILER_${name}" enabler) + set(src "${THRIFT_COMPILER_SOURCE_DIR}/src/thrift/generate/t_${name}_generator.cc") + option(${enabler} ${description} ${initial}) + if(${enabler}) + list(APPEND thrift_compiler_SOURCES ${src}) + file(GLOB thrift_compiler_tests_SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/${name}/*.c*" + "${CMAKE_CURRENT_SOURCE_DIR}/${name}/*.thrift" + ) + endif() +endmacro() + +# The following compiler with unit tests can be enabled or disabled +THRIFT_ADD_COMPILER(as3 "Enable compiler for ActionScript 3" OFF) +THRIFT_ADD_COMPILER(c_glib "Enable compiler for C with Glib" OFF) +THRIFT_ADD_COMPILER(cl "Enable compiler for Common LISP" OFF) +THRIFT_ADD_COMPILER(cpp "Enable compiler for C++" OFF) +THRIFT_ADD_COMPILER(csharp "Enable compiler for C#" OFF) +THRIFT_ADD_COMPILER(d "Enable compiler for D" OFF) +THRIFT_ADD_COMPILER(dart "Enable compiler for Dart" OFF) +THRIFT_ADD_COMPILER(delphi "Enable compiler for Delphi" OFF) +THRIFT_ADD_COMPILER(erl "Enable compiler for Erlang" OFF) +THRIFT_ADD_COMPILER(go "Enable compiler for Go" OFF) +THRIFT_ADD_COMPILER(gv "Enable compiler for GraphViz" OFF) +THRIFT_ADD_COMPILER(haxe "Enable compiler for Haxe" OFF) +THRIFT_ADD_COMPILER(hs "Enable compiler for Haskell" OFF) +THRIFT_ADD_COMPILER(html "Enable compiler for HTML Documentation" OFF) +THRIFT_ADD_COMPILER(java "Enable compiler for Java" OFF) +THRIFT_ADD_COMPILER(javame "Enable compiler for Java ME" OFF) +THRIFT_ADD_COMPILER(js "Enable compiler for JavaScript" OFF) +THRIFT_ADD_COMPILER(json "Enable compiler for JSON" OFF) +THRIFT_ADD_COMPILER(lua "Enable compiler for Lua" OFF) +THRIFT_ADD_COMPILER(netcore "Enable compiler for .NET Core" ON) +THRIFT_ADD_COMPILER(ocaml "Enable compiler for OCaml" OFF) +THRIFT_ADD_COMPILER(perl "Enable compiler for Perl" OFF) +THRIFT_ADD_COMPILER(php "Enable compiler for PHP" OFF) +THRIFT_ADD_COMPILER(py "Enable compiler for Python 2.0" OFF) +THRIFT_ADD_COMPILER(rb "Enable compiler for Ruby" OFF) +THRIFT_ADD_COMPILER(rs "Enable compiler for Rust" OFF) +THRIFT_ADD_COMPILER(st "Enable compiler for Smalltalk" OFF) +THRIFT_ADD_COMPILER(swift "Enable compiler for Swift" OFF) +THRIFT_ADD_COMPILER(xml "Enable compiler for XML" OFF) +THRIFT_ADD_COMPILER(xsd "Enable compiler for XSD" OFF) + +# Thrift is looking for include files in the src directory +# we also add the current binary directory for generated files +include_directories(${CMAKE_CURRENT_BINARY_DIR} ${THRIFT_COMPILER_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}/catch) + +add_library(thrift_compiler ${thrift_compiler_SOURCES}) + +#link parse lib to thrift_compiler lib +target_link_libraries(thrift_compiler parse) + +# add tests executable +add_executable(thrift_compiler_tests ${thrift_compiler_tests_manual_SOURCES} ${thrift_compiler_tests_SOURCES}) + +# if generates for Visual Studio set thrift_compiler_tests as default project +if(MSVC) + set_property(TARGET thrift_compiler_tests PROPERTY VS_STARTUP_PROJECT thrift_compiler_tests) +endif() + +set_target_properties(thrift_compiler_tests PROPERTIES RUNTIME_OUTPUT_DIRECTORY bin/) +set_target_properties(thrift_compiler_tests PROPERTIES OUTPUT_NAME thrift_compiler_tests) + +target_link_libraries(thrift_compiler_tests thrift_compiler) + +enable_testing() +add_test(NAME ThriftTests COMMAND thrift_compiler_tests) diff --git a/src/jaegertracing/thrift/compiler/cpp/tests/README.md b/src/jaegertracing/thrift/compiler/cpp/tests/README.md new file mode 100644 index 000000000..27be491cb --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/tests/README.md @@ -0,0 +1,88 @@ +# Build and run compiler tests using CMake + +<!-- TOC --> + +- [Build and run compiler tests using CMake](#build-and-run-compiler-tests-using-cmake) + - [General information](#general-information) + - [How to add your tests](#how-to-add-your-tests) + - [Build and run tests on Unix-like systems](#build-and-run-tests-on-unix-like-systems) + - [Prerequisites:](#prerequisites) + - [Build and run test with CMake](#build-and-run-test-with-cmake) + - [Build and run tests on Windows](#build-and-run-tests-on-windows) + - [Prerequisites:](#prerequisites-1) + - [Generation of VS project with CMake, build and run on Windows](#generation-of-vs-project-with-cmake-build-and-run-on-windows) + +<!-- /TOC --> + +## General information + +Added generic way to cover code by tests for many languages (you just need to make a correct header file for generator for your language - example in **netcore** implementation) + +At current moment these tests use free Catch library (https://github.com/catchorg/Catch2/tree/Catch1.x) for easy test creation and usage. +Decision to use it was because of simplicity, easy usage, one header file to use, stable community and growing interest (https://cpp.libhunt.com/project/googletest-google/vs/catch?rel=cmp-cmp) + +Also, maybe, later it will be migrated to Catch2 (https://github.com/philsquared/Catch) - depends on need to support legacy compilers (c++98) + +## How to add your tests + +- Open **CMakeLists.txt** +- Set **On** to call of **THRIFT_ADD_COMPILER** for your language + +``` cmake +THRIFT_ADD_COMPILER(netcore "Enable compiler for .NET Core" ON) +``` + +- Create folder with name specified in list of languages in **CMakeLists.txt** +- Create tests in folder for your language (with extensions like *.c* - cc, cpp, etc) + - Don't forget to add include of catch.hpp in your test file + ``` C + #include "../catch/catch.hpp" + ``` + +- If you need - add files manually to **thrift_compiler_tests_manual_SOURCES** in **CMakeLists.txt** similar to + +``` cmake +# you can add some files manually there +set(thrift_compiler_tests_manual_SOURCES + # tests file to avoid main in every test file + ${CMAKE_CURRENT_SOURCE_DIR}/tests_main.cc +) +``` + +- Run **cmake** with arguments for your environment and compiler +- Enjoy + +## Build and run tests on Unix-like systems + +### Prerequisites: +- Install CMake - <https://cmake.org/download/> +- Install winflexbison - <https://sourceforge.net/projects/winflexbison/> + +### Build and run test with CMake + +- Run commands in command line in current directory: + +``` +mkdir cmake-vs && cd cmake-vs +cmake .. +cmake --build . +ctest -C Debug -V +``` + +## Build and run tests on Windows + +### Prerequisites: +- Install CMake - <https://cmake.org/download/> +- Install winflexbison - <https://sourceforge.net/projects/winflexbison/> +- Install VS2017 Community Edition - <https://www.visualstudio.com/vs/whatsnew/> (ensure that you installed workload "Desktop Development with C++" for VS2017) + +### Generation of VS project with CMake, build and run on Windows +- Run commands in command line in current directory (ensure that VS installed): + +``` +mkdir cmake-vs +cd cmake-vs +cmake .. +cmake --build . +ctest -C Debug -V +```
\ No newline at end of file diff --git a/src/jaegertracing/thrift/compiler/cpp/tests/catch/catch.hpp b/src/jaegertracing/thrift/compiler/cpp/tests/catch/catch.hpp new file mode 100644 index 000000000..33d037e55 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/tests/catch/catch.hpp @@ -0,0 +1,11508 @@ +/* + * Catch v1.9.4 + * Generated: 2017-05-16 13:51:55.506519 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + +#define TWOBLUECUBES_CATCH_HPP_INCLUDED + +#ifdef __clang__ +# pragma clang system_header +#elif defined __GNUC__ +# pragma GCC system_header +#endif + +// #included from: internal/catch_suppress_warnings.h + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic ignored "-Wglobal-constructors" +# pragma clang diagnostic ignored "-Wvariadic-macros" +# pragma clang diagnostic ignored "-Wc99-extensions" +# pragma clang diagnostic ignored "-Wunused-variable" +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wc++98-compat" +# pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +# pragma clang diagnostic ignored "-Wswitch-enum" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic ignored "-Wvariadic-macros" +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic ignored "-Wparentheses" + +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wpadded" +#endif +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +#endif + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// #included from: internal/catch_notimplemented_exception.h +#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_H_INCLUDED + +// #included from: catch_common.h +#define TWOBLUECUBES_CATCH_COMMON_H_INCLUDED + +// #included from: catch_compiler_capabilities.h +#define TWOBLUECUBES_CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED + +// Detect a number of compiler features - mostly C++11/14 conformance - by compiler +// The following features are defined: +// +// CATCH_CONFIG_CPP11_NULLPTR : is nullptr supported? +// CATCH_CONFIG_CPP11_NOEXCEPT : is noexcept supported? +// CATCH_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods +// CATCH_CONFIG_CPP11_IS_ENUM : std::is_enum is supported? +// CATCH_CONFIG_CPP11_TUPLE : std::tuple is supported +// CATCH_CONFIG_CPP11_LONG_LONG : is long long supported? +// CATCH_CONFIG_CPP11_OVERRIDE : is override supported? +// CATCH_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr) +// CATCH_CONFIG_CPP11_SHUFFLE : is std::shuffle supported? +// CATCH_CONFIG_CPP11_TYPE_TRAITS : are type_traits and enable_if supported? + +// CATCH_CONFIG_CPP11_OR_GREATER : Is C++11 supported? + +// CATCH_CONFIG_VARIADIC_MACROS : are variadic macros supported? +// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_<feature name> form +// (e.g. CATCH_CONFIG_CPP11_NO_NULLPTR) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +// All the C++11 features can be disabled with CATCH_CONFIG_NO_CPP11 + +#ifdef __cplusplus + +# if __cplusplus >= 201103L +# define CATCH_CPP11_OR_GREATER +# endif + +# if __cplusplus >= 201402L +# define CATCH_CPP14_OR_GREATER +# endif + +#endif + +#ifdef __clang__ + +# if __has_feature(cxx_nullptr) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# endif + +# if __has_feature(cxx_noexcept) +# define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +# endif + +# if defined(CATCH_CPP11_OR_GREATER) +# define CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic pop" ) +# endif + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined(__CYGWIN__) || defined(__QNX__) + +# if !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +# endif + +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Cygwin +#ifdef __CYGWIN__ + +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE + +#endif // __CYGWIN__ + +//////////////////////////////////////////////////////////////////////////////// +// Borland +#ifdef __BORLANDC__ + +#endif // __BORLANDC__ + +//////////////////////////////////////////////////////////////////////////////// +// EDG +#ifdef __EDG_VERSION__ + +#endif // __EDG_VERSION__ + +//////////////////////////////////////////////////////////////////////////////// +// Digital Mars +#ifdef __DMC__ + +#endif // __DMC__ + +//////////////////////////////////////////////////////////////////////////////// +// GCC +#ifdef __GNUC__ + +# if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# endif + +// - otherwise more recent versions define __cplusplus >= 201103L +// and will get picked up below + +#endif // __GNUC__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#ifdef _MSC_VER + +#define CATCH_INTERNAL_CONFIG_WINDOWS_SEH + +#if (_MSC_VER >= 1600) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR +#endif + +#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) +#define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +#define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +#define CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE +#define CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS +#endif + +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// + +// Use variadic macros if the compiler supports them +#if ( defined _MSC_VER && _MSC_VER > 1400 && !defined __EDGE__) || \ + ( defined __WAVE__ && __WAVE_HAS_VARIADICS ) || \ + ( defined __GNUC__ && __GNUC__ >= 3 ) || \ + ( !defined __cplusplus && __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L ) + +#define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS + +#endif + +// Use __COUNTER__ if the compiler supports it +#if ( defined _MSC_VER && _MSC_VER >= 1300 ) || \ + ( defined __GNUC__ && __GNUC__ >= 4 && __GNUC_MINOR__ >= 3 ) || \ + ( defined __clang__ && __clang_major__ >= 3 ) + +#define CATCH_INTERNAL_CONFIG_COUNTER + +#endif + +//////////////////////////////////////////////////////////////////////////////// +// C++ language feature support + +// catch all support for C++11 +#if defined(CATCH_CPP11_OR_GREATER) + +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +# define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +# define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM +# define CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_TUPLE +# define CATCH_INTERNAL_CONFIG_CPP11_TUPLE +# endif + +# ifndef CATCH_INTERNAL_CONFIG_VARIADIC_MACROS +# define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS +# endif + +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) +# define CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG +# endif + +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) +# define CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE +# endif +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) +# define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR +# endif +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE) +# define CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE +# endif +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS) +# define CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS +# endif + +#endif // __cplusplus >= 201103L + +// Now set the actual defines based on the above + anything the user has configured +#if defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NO_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_NULLPTR +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_NOEXCEPT +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_GENERATED_METHODS +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_NO_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_IS_ENUM +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_CPP11_NO_TUPLE) && !defined(CATCH_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_TUPLE +#endif +#if defined(CATCH_INTERNAL_CONFIG_VARIADIC_MACROS) && !defined(CATCH_CONFIG_NO_VARIADIC_MACROS) && !defined(CATCH_CONFIG_VARIADIC_MACROS) +# define CATCH_CONFIG_VARIADIC_MACROS +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_NO_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_LONG_LONG +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_NO_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_OVERRIDE +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_NO_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_UNIQUE_PTR +#endif +// Use of __COUNTER__ is suppressed if __JETBRAINS_IDE__ is #defined (meaning we're being parsed by a JetBrains IDE for +// analytics) because, at time of writing, __COUNTER__ is not properly handled by it. +// This does not affect compilation +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) && !defined(__JETBRAINS_IDE__) +# define CATCH_CONFIG_COUNTER +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE) && !defined(CATCH_CONFIG_CPP11_NO_SHUFFLE) && !defined(CATCH_CONFIG_CPP11_SHUFFLE) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_SHUFFLE +#endif +# if defined(CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS) && !defined(CATCH_CONFIG_CPP11_NO_TYPE_TRAITS) && !defined(CATCH_CONFIG_CPP11_TYPE_TRAITS) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_TYPE_TRAITS +# endif +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS +#endif + +// noexcept support: +#if defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_NOEXCEPT) +# define CATCH_NOEXCEPT noexcept +# define CATCH_NOEXCEPT_IS(x) noexcept(x) +#else +# define CATCH_NOEXCEPT throw() +# define CATCH_NOEXCEPT_IS(x) +#endif + +// nullptr support +#ifdef CATCH_CONFIG_CPP11_NULLPTR +# define CATCH_NULL nullptr +#else +# define CATCH_NULL NULL +#endif + +// override support +#ifdef CATCH_CONFIG_CPP11_OVERRIDE +# define CATCH_OVERRIDE override +#else +# define CATCH_OVERRIDE +#endif + +// unique_ptr support +#ifdef CATCH_CONFIG_CPP11_UNIQUE_PTR +# define CATCH_AUTO_PTR( T ) std::unique_ptr<T> +#else +# define CATCH_AUTO_PTR( T ) std::auto_ptr<T> +#endif + +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#define INTERNAL_CATCH_STRINGIFY2( expr ) #expr +#define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr ) + +#include <sstream> +#include <algorithm> + +namespace Catch { + + struct IConfig; + + struct CaseSensitive { enum Choice { + Yes, + No + }; }; + + class NonCopyable { +#ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; +#else + NonCopyable( NonCopyable const& info ); + NonCopyable& operator = ( NonCopyable const& ); +#endif + + protected: + NonCopyable() {} + virtual ~NonCopyable(); + }; + + class SafeBool { + public: + typedef void (SafeBool::*type)() const; + + static type makeSafe( bool value ) { + return value ? &SafeBool::trueValue : 0; + } + private: + void trueValue() const {} + }; + + template<typename ContainerT> + inline void deleteAll( ContainerT& container ) { + typename ContainerT::const_iterator it = container.begin(); + typename ContainerT::const_iterator itEnd = container.end(); + for(; it != itEnd; ++it ) + delete *it; + } + template<typename AssociativeContainerT> + inline void deleteAllValues( AssociativeContainerT& container ) { + typename AssociativeContainerT::const_iterator it = container.begin(); + typename AssociativeContainerT::const_iterator itEnd = container.end(); + for(; it != itEnd; ++it ) + delete it->second; + } + + bool startsWith( std::string const& s, std::string const& prefix ); + bool startsWith( std::string const& s, char prefix ); + bool endsWith( std::string const& s, std::string const& suffix ); + bool endsWith( std::string const& s, char suffix ); + bool contains( std::string const& s, std::string const& infix ); + void toLowerInPlace( std::string& s ); + std::string toLower( std::string const& s ); + std::string trim( std::string const& str ); + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); + + struct pluralise { + pluralise( std::size_t count, std::string const& label ); + + friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); + + std::size_t m_count; + std::string m_label; + }; + + struct SourceLineInfo { + + SourceLineInfo(); + SourceLineInfo( char const* _file, std::size_t _line ); +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + SourceLineInfo(SourceLineInfo const& other) = default; + SourceLineInfo( SourceLineInfo && ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo& operator = ( SourceLineInfo && ) = default; +# endif + bool empty() const; + bool operator == ( SourceLineInfo const& other ) const; + bool operator < ( SourceLineInfo const& other ) const; + + char const* file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // This is just here to avoid compiler warnings with macro constants and boolean literals + inline bool isTrue( bool value ){ return value; } + inline bool alwaysTrue() { return true; } + inline bool alwaysFalse() { return false; } + + void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ); + + void seedRng( IConfig const& config ); + unsigned int rngSeed(); + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() { + return std::string(); + } + }; + template<typename T> + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO ::Catch::SourceLineInfo( __FILE__, static_cast<std::size_t>( __LINE__ ) ) +#define CATCH_INTERNAL_ERROR( msg ) ::Catch::throwLogicError( msg, CATCH_INTERNAL_LINEINFO ); + +namespace Catch { + + class NotImplementedException : public std::exception + { + public: + NotImplementedException( SourceLineInfo const& lineInfo ); + NotImplementedException( NotImplementedException const& ) {} + + virtual ~NotImplementedException() CATCH_NOEXCEPT {} + + virtual const char* what() const CATCH_NOEXCEPT; + + private: + std::string m_what; + SourceLineInfo m_lineInfo; + }; + +} // end namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define CATCH_NOT_IMPLEMENTED throw Catch::NotImplementedException( CATCH_INTERNAL_LINEINFO ) + +// #included from: internal/catch_context.h +#define TWOBLUECUBES_CATCH_CONTEXT_H_INCLUDED + +// #included from: catch_interfaces_generators.h +#define TWOBLUECUBES_CATCH_INTERFACES_GENERATORS_H_INCLUDED + +#include <string> + +namespace Catch { + + struct IGeneratorInfo { + virtual ~IGeneratorInfo(); + virtual bool moveNext() = 0; + virtual std::size_t getCurrentIndex() const = 0; + }; + + struct IGeneratorsForTest { + virtual ~IGeneratorsForTest(); + + virtual IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) = 0; + virtual bool moveNext() = 0; + }; + + IGeneratorsForTest* createGeneratorsForTest(); + +} // end namespace Catch + +// #included from: catch_ptr.hpp +#define TWOBLUECUBES_CATCH_PTR_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + // An intrusive reference counting smart pointer. + // T must implement addRef() and release() methods + // typically implementing the IShared interface + template<typename T> + class Ptr { + public: + Ptr() : m_p( CATCH_NULL ){} + Ptr( T* p ) : m_p( p ){ + if( m_p ) + m_p->addRef(); + } + Ptr( Ptr const& other ) : m_p( other.m_p ){ + if( m_p ) + m_p->addRef(); + } + ~Ptr(){ + if( m_p ) + m_p->release(); + } + void reset() { + if( m_p ) + m_p->release(); + m_p = CATCH_NULL; + } + Ptr& operator = ( T* p ){ + Ptr temp( p ); + swap( temp ); + return *this; + } + Ptr& operator = ( Ptr const& other ){ + Ptr temp( other ); + swap( temp ); + return *this; + } + void swap( Ptr& other ) { std::swap( m_p, other.m_p ); } + T* get() const{ return m_p; } + T& operator*() const { return *m_p; } + T* operator->() const { return m_p; } + bool operator !() const { return m_p == CATCH_NULL; } + operator SafeBool::type() const { return SafeBool::makeSafe( m_p != CATCH_NULL ); } + + private: + T* m_p; + }; + + struct IShared : NonCopyable { + virtual ~IShared(); + virtual void addRef() const = 0; + virtual void release() const = 0; + }; + + template<typename T = IShared> + struct SharedImpl : T { + + SharedImpl() : m_rc( 0 ){} + + virtual void addRef() const { + ++m_rc; + } + virtual void release() const { + if( --m_rc == 0 ) + delete this; + } + + mutable unsigned int m_rc; + }; + +} // end namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +namespace Catch { + + class TestCase; + class Stream; + struct IResultCapture; + struct IRunner; + struct IGeneratorsForTest; + struct IConfig; + + struct IContext + { + virtual ~IContext(); + + virtual IResultCapture* getResultCapture() = 0; + virtual IRunner* getRunner() = 0; + virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) = 0; + virtual bool advanceGeneratorsForCurrentTest() = 0; + virtual Ptr<IConfig const> getConfig() const = 0; + }; + + struct IMutableContext : IContext + { + virtual ~IMutableContext(); + virtual void setResultCapture( IResultCapture* resultCapture ) = 0; + virtual void setRunner( IRunner* runner ) = 0; + virtual void setConfig( Ptr<IConfig const> const& config ) = 0; + }; + + IContext& getCurrentContext(); + IMutableContext& getCurrentMutableContext(); + void cleanUpContext(); + Stream createStream( std::string const& streamName ); + +} + +// #included from: internal/catch_test_registry.hpp +#define TWOBLUECUBES_CATCH_TEST_REGISTRY_HPP_INCLUDED + +// #included from: catch_interfaces_testcase.h +#define TWOBLUECUBES_CATCH_INTERFACES_TESTCASE_H_INCLUDED + +#include <vector> + +namespace Catch { + + class TestSpec; + + struct ITestCase : IShared { + virtual void invoke () const = 0; + protected: + virtual ~ITestCase(); + }; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector<TestCase> const& getAllTests() const = 0; + virtual std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const = 0; + }; + + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ); + +} + +namespace Catch { + +template<typename C> +class MethodTestCase : public SharedImpl<ITestCase> { + +public: + MethodTestCase( void (C::*method)() ) : m_method( method ) {} + + virtual void invoke() const { + C obj; + (obj.*m_method)(); + } + +private: + virtual ~MethodTestCase() {} + + void (C::*m_method)(); +}; + +typedef void(*TestFunction)(); + +struct NameAndDesc { + NameAndDesc( const char* _name = "", const char* _description= "" ) + : name( _name ), description( _description ) + {} + + const char* name; + const char* description; +}; + +void registerTestCase + ( ITestCase* testCase, + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ); + +struct AutoReg { + + AutoReg + ( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ); + + template<typename C> + AutoReg + ( void (C::*method)(), + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ) { + + registerTestCase + ( new MethodTestCase<C>( method ), + className, + nameAndDesc, + lineInfo ); + } + + ~AutoReg(); + +private: + AutoReg( AutoReg const& ); + void operator= ( AutoReg const& ); +}; + +void registerTestCaseFunction + ( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ); + +} // end namespace Catch + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ + static void TestName(); \ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); } \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ + static void TestName() + #define INTERNAL_CATCH_TESTCASE( ... ) \ + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + namespace{ \ + struct TestName : ClassName{ \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestName::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); \ + } \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ + void TestName::test() + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS + +#else + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE2( TestName, Name, Desc ) \ + static void TestName(); \ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); }\ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ + static void TestName() + #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \ + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), Name, Desc ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestCaseName, ClassName, TestName, Desc )\ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + namespace{ \ + struct TestCaseName : ClassName{ \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestCaseName::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); \ + } \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ + void TestCaseName::test() + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\ + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, TestName, Desc ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, Name, Desc ) \ + CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ + Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); \ + CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS + +#endif + +// #included from: internal/catch_capture.hpp +#define TWOBLUECUBES_CATCH_CAPTURE_HPP_INCLUDED + +// #included from: catch_result_builder.h +#define TWOBLUECUBES_CATCH_RESULT_BUILDER_H_INCLUDED + +// #included from: catch_result_type.h +#define TWOBLUECUBES_CATCH_RESULT_TYPE_H_INCLUDED + +namespace Catch { + + // ResultWas::OfType enum + struct ResultWas { enum OfType { + Unknown = -1, + Ok = 0, + Info = 1, + Warning = 2, + + FailureBit = 0x10, + + ExpressionFailed = FailureBit | 1, + ExplicitFailure = FailureBit | 2, + + Exception = 0x100 | FailureBit, + + ThrewException = Exception | 1, + DidntThrowException = Exception | 2, + + FatalErrorCondition = 0x200 | FailureBit + + }; }; + + inline bool isOk( ResultWas::OfType resultType ) { + return ( resultType & ResultWas::FailureBit ) == 0; + } + inline bool isJustInfo( int flags ) { + return flags == ResultWas::Info; + } + + // ResultDisposition::Flags enum + struct ResultDisposition { enum Flags { + Normal = 0x01, + + ContinueOnFailure = 0x02, // Failures fail test, but execution continues + FalseTest = 0x04, // Prefix expression with ! + SuppressFail = 0x08 // Failures are reported but do not fail the test + }; }; + + inline ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { + return static_cast<ResultDisposition::Flags>( static_cast<int>( lhs ) | static_cast<int>( rhs ) ); + } + + inline bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } + inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } + inline bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } + +} // end namespace Catch + +// #included from: catch_assertionresult.h +#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_H_INCLUDED + +#include <string> + +namespace Catch { + + struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison; + + struct DecomposedExpression + { + virtual ~DecomposedExpression() {} + virtual bool isBinaryExpression() const { + return false; + } + virtual void reconstructExpression( std::string& dest ) const = 0; + + // Only simple binary comparisons can be decomposed. + // If more complex check is required then wrap sub-expressions in parentheses. + template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( T const& ); + template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( T const& ); + template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator * ( T const& ); + template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator / ( T const& ); + template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator % ( T const& ); + template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( T const& ); + template<typename T> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( T const& ); + + private: + DecomposedExpression& operator = (DecomposedExpression const&); + }; + + struct AssertionInfo + { + AssertionInfo() {} + AssertionInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + std::string const& _capturedExpression, + ResultDisposition::Flags _resultDisposition ); + + std::string macroName; + SourceLineInfo lineInfo; + std::string capturedExpression; + ResultDisposition::Flags resultDisposition; + }; + + struct AssertionResultData + { + AssertionResultData() : decomposedExpression( CATCH_NULL ) + , resultType( ResultWas::Unknown ) + , negated( false ) + , parenthesized( false ) {} + + void negate( bool parenthesize ) { + negated = !negated; + parenthesized = parenthesize; + if( resultType == ResultWas::Ok ) + resultType = ResultWas::ExpressionFailed; + else if( resultType == ResultWas::ExpressionFailed ) + resultType = ResultWas::Ok; + } + + std::string const& reconstructExpression() const { + if( decomposedExpression != CATCH_NULL ) { + decomposedExpression->reconstructExpression( reconstructedExpression ); + if( parenthesized ) { + reconstructedExpression.insert( 0, 1, '(' ); + reconstructedExpression.append( 1, ')' ); + } + if( negated ) { + reconstructedExpression.insert( 0, 1, '!' ); + } + decomposedExpression = CATCH_NULL; + } + return reconstructedExpression; + } + + mutable DecomposedExpression const* decomposedExpression; + mutable std::string reconstructedExpression; + std::string message; + ResultWas::OfType resultType; + bool negated; + bool parenthesized; + }; + + class AssertionResult { + public: + AssertionResult(); + AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); + ~AssertionResult(); +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + AssertionResult( AssertionResult const& ) = default; + AssertionResult( AssertionResult && ) = default; + AssertionResult& operator = ( AssertionResult const& ) = default; + AssertionResult& operator = ( AssertionResult && ) = default; +# endif + + bool isOk() const; + bool succeeded() const; + ResultWas::OfType getResultType() const; + bool hasExpression() const; + bool hasMessage() const; + std::string getExpression() const; + std::string getExpressionInMacro() const; + bool hasExpandedExpression() const; + std::string getExpandedExpression() const; + std::string getMessage() const; + SourceLineInfo getSourceInfo() const; + std::string getTestMacroName() const; + void discardDecomposedExpression() const; + void expandDecomposedExpression() const; + + protected: + AssertionInfo m_info; + AssertionResultData m_resultData; + }; + +} // end namespace Catch + +// #included from: catch_matchers.hpp +#define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED + +namespace Catch { +namespace Matchers { + namespace Impl { + + template<typename ArgT> struct MatchAllOf; + template<typename ArgT> struct MatchAnyOf; + template<typename ArgT> struct MatchNotOf; + + class MatcherUntypedBase { + public: + std::string toString() const { + if( m_cachedToString.empty() ) + m_cachedToString = describe(); + return m_cachedToString; + } + + protected: + virtual ~MatcherUntypedBase(); + virtual std::string describe() const = 0; + mutable std::string m_cachedToString; + private: + MatcherUntypedBase& operator = ( MatcherUntypedBase const& ); + }; + + template<typename ObjectT> + struct MatcherMethod { + virtual bool match( ObjectT const& arg ) const = 0; + }; + template<typename PtrT> + struct MatcherMethod<PtrT*> { + virtual bool match( PtrT* arg ) const = 0; + }; + + template<typename ObjectT, typename ComparatorT = ObjectT> + struct MatcherBase : MatcherUntypedBase, MatcherMethod<ObjectT> { + + MatchAllOf<ComparatorT> operator && ( MatcherBase const& other ) const; + MatchAnyOf<ComparatorT> operator || ( MatcherBase const& other ) const; + MatchNotOf<ComparatorT> operator ! () const; + }; + + template<typename ArgT> + struct MatchAllOf : MatcherBase<ArgT> { + virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if (!m_matchers[i]->match(arg)) + return false; + } + return true; + } + virtual std::string describe() const CATCH_OVERRIDE { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if( i != 0 ) + description += " and "; + description += m_matchers[i]->toString(); + } + description += " )"; + return description; + } + + MatchAllOf<ArgT>& operator && ( MatcherBase<ArgT> const& other ) { + m_matchers.push_back( &other ); + return *this; + } + + std::vector<MatcherBase<ArgT> const*> m_matchers; + }; + template<typename ArgT> + struct MatchAnyOf : MatcherBase<ArgT> { + + virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if (m_matchers[i]->match(arg)) + return true; + } + return false; + } + virtual std::string describe() const CATCH_OVERRIDE { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if( i != 0 ) + description += " or "; + description += m_matchers[i]->toString(); + } + description += " )"; + return description; + } + + MatchAnyOf<ArgT>& operator || ( MatcherBase<ArgT> const& other ) { + m_matchers.push_back( &other ); + return *this; + } + + std::vector<MatcherBase<ArgT> const*> m_matchers; + }; + + template<typename ArgT> + struct MatchNotOf : MatcherBase<ArgT> { + + MatchNotOf( MatcherBase<ArgT> const& underlyingMatcher ) : m_underlyingMatcher( underlyingMatcher ) {} + + virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE { + return !m_underlyingMatcher.match( arg ); + } + + virtual std::string describe() const CATCH_OVERRIDE { + return "not " + m_underlyingMatcher.toString(); + } + MatcherBase<ArgT> const& m_underlyingMatcher; + }; + + template<typename ObjectT, typename ComparatorT> + MatchAllOf<ComparatorT> MatcherBase<ObjectT, ComparatorT>::operator && ( MatcherBase const& other ) const { + return MatchAllOf<ComparatorT>() && *this && other; + } + template<typename ObjectT, typename ComparatorT> + MatchAnyOf<ComparatorT> MatcherBase<ObjectT, ComparatorT>::operator || ( MatcherBase const& other ) const { + return MatchAnyOf<ComparatorT>() || *this || other; + } + template<typename ObjectT, typename ComparatorT> + MatchNotOf<ComparatorT> MatcherBase<ObjectT, ComparatorT>::operator ! () const { + return MatchNotOf<ComparatorT>( *this ); + } + + } // namespace Impl + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + // - deprecated: prefer ||, && and ! + template<typename T> + inline Impl::MatchNotOf<T> Not( Impl::MatcherBase<T> const& underlyingMatcher ) { + return Impl::MatchNotOf<T>( underlyingMatcher ); + } + template<typename T> + inline Impl::MatchAllOf<T> AllOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2 ) { + return Impl::MatchAllOf<T>() && m1 && m2; + } + template<typename T> + inline Impl::MatchAllOf<T> AllOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2, Impl::MatcherBase<T> const& m3 ) { + return Impl::MatchAllOf<T>() && m1 && m2 && m3; + } + template<typename T> + inline Impl::MatchAnyOf<T> AnyOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2 ) { + return Impl::MatchAnyOf<T>() || m1 || m2; + } + template<typename T> + inline Impl::MatchAnyOf<T> AnyOf( Impl::MatcherBase<T> const& m1, Impl::MatcherBase<T> const& m2, Impl::MatcherBase<T> const& m3 ) { + return Impl::MatchAnyOf<T>() || m1 || m2 || m3; + } + +} // namespace Matchers + +using namespace Matchers; +using Matchers::Impl::MatcherBase; + +} // namespace Catch + +namespace Catch { + + struct TestFailureException{}; + + template<typename T> class ExpressionLhs; + + struct CopyableStream { + CopyableStream() {} + CopyableStream( CopyableStream const& other ) { + oss << other.oss.str(); + } + CopyableStream& operator=( CopyableStream const& other ) { + oss.str(std::string()); + oss << other.oss.str(); + return *this; + } + std::ostringstream oss; + }; + + class ResultBuilder : public DecomposedExpression { + public: + ResultBuilder( char const* macroName, + SourceLineInfo const& lineInfo, + char const* capturedExpression, + ResultDisposition::Flags resultDisposition, + char const* secondArg = "" ); + ~ResultBuilder(); + + template<typename T> + ExpressionLhs<T const&> operator <= ( T const& operand ); + ExpressionLhs<bool> operator <= ( bool value ); + + template<typename T> + ResultBuilder& operator << ( T const& value ) { + m_stream.oss << value; + return *this; + } + + ResultBuilder& setResultType( ResultWas::OfType result ); + ResultBuilder& setResultType( bool result ); + + void endExpression( DecomposedExpression const& expr ); + + virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE; + + AssertionResult build() const; + AssertionResult build( DecomposedExpression const& expr ) const; + + void useActiveException( ResultDisposition::Flags resultDisposition = ResultDisposition::Normal ); + void captureResult( ResultWas::OfType resultType ); + void captureExpression(); + void captureExpectedException( std::string const& expectedMessage ); + void captureExpectedException( Matchers::Impl::MatcherBase<std::string> const& matcher ); + void handleResult( AssertionResult const& result ); + void react(); + bool shouldDebugBreak() const; + bool allowThrows() const; + + template<typename ArgT, typename MatcherT> + void captureMatch( ArgT const& arg, MatcherT const& matcher, char const* matcherString ); + + void setExceptionGuard(); + void unsetExceptionGuard(); + + private: + AssertionInfo m_assertionInfo; + AssertionResultData m_data; + CopyableStream m_stream; + + bool m_shouldDebugBreak; + bool m_shouldThrow; + bool m_guardException; + }; + +} // namespace Catch + +// Include after due to circular dependency: +// #included from: catch_expression_lhs.hpp +#define TWOBLUECUBES_CATCH_EXPRESSION_LHS_HPP_INCLUDED + +// #included from: catch_evaluate.hpp +#define TWOBLUECUBES_CATCH_EVALUATE_HPP_INCLUDED + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4389) // '==' : signed/unsigned mismatch +#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform) +#endif + +#include <cstddef> + +namespace Catch { +namespace Internal { + + enum Operator { + IsEqualTo, + IsNotEqualTo, + IsLessThan, + IsGreaterThan, + IsLessThanOrEqualTo, + IsGreaterThanOrEqualTo + }; + + template<Operator Op> struct OperatorTraits { static const char* getName(){ return "*error*"; } }; + template<> struct OperatorTraits<IsEqualTo> { static const char* getName(){ return "=="; } }; + template<> struct OperatorTraits<IsNotEqualTo> { static const char* getName(){ return "!="; } }; + template<> struct OperatorTraits<IsLessThan> { static const char* getName(){ return "<"; } }; + template<> struct OperatorTraits<IsGreaterThan> { static const char* getName(){ return ">"; } }; + template<> struct OperatorTraits<IsLessThanOrEqualTo> { static const char* getName(){ return "<="; } }; + template<> struct OperatorTraits<IsGreaterThanOrEqualTo>{ static const char* getName(){ return ">="; } }; + + template<typename T> + inline T& opCast(T const& t) { return const_cast<T&>(t); } + +// nullptr_t support based on pull request #154 from Konstantin Baumann +#ifdef CATCH_CONFIG_CPP11_NULLPTR + inline std::nullptr_t opCast(std::nullptr_t) { return nullptr; } +#endif // CATCH_CONFIG_CPP11_NULLPTR + + // So the compare overloads can be operator agnostic we convey the operator as a template + // enum, which is used to specialise an Evaluator for doing the comparison. + template<typename T1, typename T2, Operator Op> + class Evaluator{}; + + template<typename T1, typename T2> + struct Evaluator<T1, T2, IsEqualTo> { + static bool evaluate( T1 const& lhs, T2 const& rhs) { + return bool( opCast( lhs ) == opCast( rhs ) ); + } + }; + template<typename T1, typename T2> + struct Evaluator<T1, T2, IsNotEqualTo> { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return bool( opCast( lhs ) != opCast( rhs ) ); + } + }; + template<typename T1, typename T2> + struct Evaluator<T1, T2, IsLessThan> { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return bool( opCast( lhs ) < opCast( rhs ) ); + } + }; + template<typename T1, typename T2> + struct Evaluator<T1, T2, IsGreaterThan> { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return bool( opCast( lhs ) > opCast( rhs ) ); + } + }; + template<typename T1, typename T2> + struct Evaluator<T1, T2, IsGreaterThanOrEqualTo> { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return bool( opCast( lhs ) >= opCast( rhs ) ); + } + }; + template<typename T1, typename T2> + struct Evaluator<T1, T2, IsLessThanOrEqualTo> { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return bool( opCast( lhs ) <= opCast( rhs ) ); + } + }; + + template<Operator Op, typename T1, typename T2> + bool applyEvaluator( T1 const& lhs, T2 const& rhs ) { + return Evaluator<T1, T2, Op>::evaluate( lhs, rhs ); + } + + // This level of indirection allows us to specialise for integer types + // to avoid signed/ unsigned warnings + + // "base" overload + template<Operator Op, typename T1, typename T2> + bool compare( T1 const& lhs, T2 const& rhs ) { + return Evaluator<T1, T2, Op>::evaluate( lhs, rhs ); + } + + // unsigned X to int + template<Operator Op> bool compare( unsigned int lhs, int rhs ) { + return applyEvaluator<Op>( lhs, static_cast<unsigned int>( rhs ) ); + } + template<Operator Op> bool compare( unsigned long lhs, int rhs ) { + return applyEvaluator<Op>( lhs, static_cast<unsigned int>( rhs ) ); + } + template<Operator Op> bool compare( unsigned char lhs, int rhs ) { + return applyEvaluator<Op>( lhs, static_cast<unsigned int>( rhs ) ); + } + + // unsigned X to long + template<Operator Op> bool compare( unsigned int lhs, long rhs ) { + return applyEvaluator<Op>( lhs, static_cast<unsigned long>( rhs ) ); + } + template<Operator Op> bool compare( unsigned long lhs, long rhs ) { + return applyEvaluator<Op>( lhs, static_cast<unsigned long>( rhs ) ); + } + template<Operator Op> bool compare( unsigned char lhs, long rhs ) { + return applyEvaluator<Op>( lhs, static_cast<unsigned long>( rhs ) ); + } + + // int to unsigned X + template<Operator Op> bool compare( int lhs, unsigned int rhs ) { + return applyEvaluator<Op>( static_cast<unsigned int>( lhs ), rhs ); + } + template<Operator Op> bool compare( int lhs, unsigned long rhs ) { + return applyEvaluator<Op>( static_cast<unsigned int>( lhs ), rhs ); + } + template<Operator Op> bool compare( int lhs, unsigned char rhs ) { + return applyEvaluator<Op>( static_cast<unsigned int>( lhs ), rhs ); + } + + // long to unsigned X + template<Operator Op> bool compare( long lhs, unsigned int rhs ) { + return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs ); + } + template<Operator Op> bool compare( long lhs, unsigned long rhs ) { + return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs ); + } + template<Operator Op> bool compare( long lhs, unsigned char rhs ) { + return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs ); + } + + // pointer to long (when comparing against NULL) + template<Operator Op, typename T> bool compare( long lhs, T* rhs ) { + return Evaluator<T*, T*, Op>::evaluate( reinterpret_cast<T*>( lhs ), rhs ); + } + template<Operator Op, typename T> bool compare( T* lhs, long rhs ) { + return Evaluator<T*, T*, Op>::evaluate( lhs, reinterpret_cast<T*>( rhs ) ); + } + + // pointer to int (when comparing against NULL) + template<Operator Op, typename T> bool compare( int lhs, T* rhs ) { + return Evaluator<T*, T*, Op>::evaluate( reinterpret_cast<T*>( lhs ), rhs ); + } + template<Operator Op, typename T> bool compare( T* lhs, int rhs ) { + return Evaluator<T*, T*, Op>::evaluate( lhs, reinterpret_cast<T*>( rhs ) ); + } + +#ifdef CATCH_CONFIG_CPP11_LONG_LONG + // long long to unsigned X + template<Operator Op> bool compare( long long lhs, unsigned int rhs ) { + return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs ); + } + template<Operator Op> bool compare( long long lhs, unsigned long rhs ) { + return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs ); + } + template<Operator Op> bool compare( long long lhs, unsigned long long rhs ) { + return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs ); + } + template<Operator Op> bool compare( long long lhs, unsigned char rhs ) { + return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs ); + } + + // unsigned long long to X + template<Operator Op> bool compare( unsigned long long lhs, int rhs ) { + return applyEvaluator<Op>( static_cast<long>( lhs ), rhs ); + } + template<Operator Op> bool compare( unsigned long long lhs, long rhs ) { + return applyEvaluator<Op>( static_cast<long>( lhs ), rhs ); + } + template<Operator Op> bool compare( unsigned long long lhs, long long rhs ) { + return applyEvaluator<Op>( static_cast<long>( lhs ), rhs ); + } + template<Operator Op> bool compare( unsigned long long lhs, char rhs ) { + return applyEvaluator<Op>( static_cast<long>( lhs ), rhs ); + } + + // pointer to long long (when comparing against NULL) + template<Operator Op, typename T> bool compare( long long lhs, T* rhs ) { + return Evaluator<T*, T*, Op>::evaluate( reinterpret_cast<T*>( lhs ), rhs ); + } + template<Operator Op, typename T> bool compare( T* lhs, long long rhs ) { + return Evaluator<T*, T*, Op>::evaluate( lhs, reinterpret_cast<T*>( rhs ) ); + } +#endif // CATCH_CONFIG_CPP11_LONG_LONG + +#ifdef CATCH_CONFIG_CPP11_NULLPTR + // pointer to nullptr_t (when comparing against nullptr) + template<Operator Op, typename T> bool compare( std::nullptr_t, T* rhs ) { + return Evaluator<T*, T*, Op>::evaluate( nullptr, rhs ); + } + template<Operator Op, typename T> bool compare( T* lhs, std::nullptr_t ) { + return Evaluator<T*, T*, Op>::evaluate( lhs, nullptr ); + } +#endif // CATCH_CONFIG_CPP11_NULLPTR + +} // end of namespace Internal +} // end of namespace Catch + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// #included from: catch_tostring.h +#define TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED + +#include <sstream> +#include <iomanip> +#include <limits> +#include <vector> +#include <cstddef> + +#ifdef __OBJC__ +// #included from: catch_objc_arc.hpp +#define TWOBLUECUBES_CATCH_OBJC_ARC_HPP_INCLUDED + +#import <Foundation/Foundation.h> + +#ifdef __has_feature +#define CATCH_ARC_ENABLED __has_feature(objc_arc) +#else +#define CATCH_ARC_ENABLED 0 +#endif + +void arcSafeRelease( NSObject* obj ); +id performOptionalSelector( id obj, SEL sel ); + +#if !CATCH_ARC_ENABLED +inline void arcSafeRelease( NSObject* obj ) { + [obj release]; +} +inline id performOptionalSelector( id obj, SEL sel ) { + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; + return nil; +} +#define CATCH_UNSAFE_UNRETAINED +#define CATCH_ARC_STRONG +#else +inline void arcSafeRelease( NSObject* ){} +inline id performOptionalSelector( id obj, SEL sel ) { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" +#endif + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + return nil; +} +#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained +#define CATCH_ARC_STRONG __strong +#endif + +#endif + +#ifdef CATCH_CONFIG_CPP11_TUPLE +#include <tuple> +#endif + +#ifdef CATCH_CONFIG_CPP11_IS_ENUM +#include <type_traits> +#endif + +namespace Catch { + +// Why we're here. +template<typename T> +std::string toString( T const& value ); + +// Built in overloads + +std::string toString( std::string const& value ); +std::string toString( std::wstring const& value ); +std::string toString( const char* const value ); +std::string toString( char* const value ); +std::string toString( const wchar_t* const value ); +std::string toString( wchar_t* const value ); +std::string toString( int value ); +std::string toString( unsigned long value ); +std::string toString( unsigned int value ); +std::string toString( const double value ); +std::string toString( const float value ); +std::string toString( bool value ); +std::string toString( char value ); +std::string toString( signed char value ); +std::string toString( unsigned char value ); + +#ifdef CATCH_CONFIG_CPP11_LONG_LONG +std::string toString( long long value ); +std::string toString( unsigned long long value ); +#endif + +#ifdef CATCH_CONFIG_CPP11_NULLPTR +std::string toString( std::nullptr_t ); +#endif + +#ifdef __OBJC__ + std::string toString( NSString const * const& nsstring ); + std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ); + std::string toString( NSObject* const& nsObject ); +#endif + +namespace Detail { + + extern const std::string unprintableString; + + #if !defined(CATCH_CONFIG_CPP11_STREAM_INSERTABLE_CHECK) + struct BorgType { + template<typename T> BorgType( T const& ); + }; + + struct TrueType { char sizer[1]; }; + struct FalseType { char sizer[2]; }; + + TrueType& testStreamable( std::ostream& ); + FalseType testStreamable( FalseType ); + + FalseType operator<<( std::ostream const&, BorgType const& ); + + template<typename T> + struct IsStreamInsertable { + static std::ostream &s; + static T const&t; + enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) }; + }; +#else + template<typename T> + class IsStreamInsertable { + template<typename SS, typename TT> + static auto test(int) + -> decltype( std::declval<SS&>() << std::declval<TT>(), std::true_type() ); + + template<typename, typename> + static auto test(...) -> std::false_type; + + public: + static const bool value = decltype(test<std::ostream,const T&>(0))::value; + }; +#endif + +#if defined(CATCH_CONFIG_CPP11_IS_ENUM) + template<typename T, + bool IsEnum = std::is_enum<T>::value + > + struct EnumStringMaker + { + static std::string convert( T const& ) { return unprintableString; } + }; + + template<typename T> + struct EnumStringMaker<T,true> + { + static std::string convert( T const& v ) + { + return ::Catch::toString( + static_cast<typename std::underlying_type<T>::type>(v) + ); + } + }; +#endif + template<bool C> + struct StringMakerBase { +#if defined(CATCH_CONFIG_CPP11_IS_ENUM) + template<typename T> + static std::string convert( T const& v ) + { + return EnumStringMaker<T>::convert( v ); + } +#else + template<typename T> + static std::string convert( T const& ) { return unprintableString; } +#endif + }; + + template<> + struct StringMakerBase<true> { + template<typename T> + static std::string convert( T const& _value ) { + std::ostringstream oss; + oss << _value; + return oss.str(); + } + }; + + std::string rawMemoryToString( const void *object, std::size_t size ); + + template<typename T> + inline std::string rawMemoryToString( const T& object ) { + return rawMemoryToString( &object, sizeof(object) ); + } + +} // end namespace Detail + +template<typename T> +struct StringMaker : + Detail::StringMakerBase<Detail::IsStreamInsertable<T>::value> {}; + +template<typename T> +struct StringMaker<T*> { + template<typename U> + static std::string convert( U* p ) { + if( !p ) + return "NULL"; + else + return Detail::rawMemoryToString( p ); + } +}; + +template<typename R, typename C> +struct StringMaker<R C::*> { + static std::string convert( R C::* p ) { + if( !p ) + return "NULL"; + else + return Detail::rawMemoryToString( p ); + } +}; + +namespace Detail { + template<typename InputIterator> + std::string rangeToString( InputIterator first, InputIterator last ); +} + +//template<typename T, typename Allocator> +//struct StringMaker<std::vector<T, Allocator> > { +// static std::string convert( std::vector<T,Allocator> const& v ) { +// return Detail::rangeToString( v.begin(), v.end() ); +// } +//}; + +template<typename T, typename Allocator> +std::string toString( std::vector<T,Allocator> const& v ) { + return Detail::rangeToString( v.begin(), v.end() ); +} + +#ifdef CATCH_CONFIG_CPP11_TUPLE + +// toString for tuples +namespace TupleDetail { + template< + typename Tuple, + std::size_t N = 0, + bool = (N < std::tuple_size<Tuple>::value) + > + struct ElementPrinter { + static void print( const Tuple& tuple, std::ostream& os ) + { + os << ( N ? ", " : " " ) + << Catch::toString(std::get<N>(tuple)); + ElementPrinter<Tuple,N+1>::print(tuple,os); + } + }; + + template< + typename Tuple, + std::size_t N + > + struct ElementPrinter<Tuple,N,false> { + static void print( const Tuple&, std::ostream& ) {} + }; + +} + +template<typename ...Types> +struct StringMaker<std::tuple<Types...>> { + + static std::string convert( const std::tuple<Types...>& tuple ) + { + std::ostringstream os; + os << '{'; + TupleDetail::ElementPrinter<std::tuple<Types...>>::print( tuple, os ); + os << " }"; + return os.str(); + } +}; +#endif // CATCH_CONFIG_CPP11_TUPLE + +namespace Detail { + template<typename T> + std::string makeString( T const& value ) { + return StringMaker<T>::convert( value ); + } +} // end namespace Detail + +/// \brief converts any type to a string +/// +/// The default template forwards on to ostringstream - except when an +/// ostringstream overload does not exist - in which case it attempts to detect +/// that and writes {?}. +/// Overload (not specialise) this template for custom typs that you don't want +/// to provide an ostream overload for. +template<typename T> +std::string toString( T const& value ) { + return StringMaker<T>::convert( value ); +} + + namespace Detail { + template<typename InputIterator> + std::string rangeToString( InputIterator first, InputIterator last ) { + std::ostringstream oss; + oss << "{ "; + if( first != last ) { + oss << Catch::toString( *first ); + for( ++first ; first != last ; ++first ) + oss << ", " << Catch::toString( *first ); + } + oss << " }"; + return oss.str(); + } +} + +} // end namespace Catch + +namespace Catch { + +template<typename LhsT, Internal::Operator Op, typename RhsT> +class BinaryExpression; + +template<typename ArgT, typename MatcherT> +class MatchExpression; + +// Wraps the LHS of an expression and overloads comparison operators +// for also capturing those and RHS (if any) +template<typename T> +class ExpressionLhs : public DecomposedExpression { +public: + ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ), m_truthy(false) {} + + ExpressionLhs& operator = ( const ExpressionLhs& ); + + template<typename RhsT> + BinaryExpression<T, Internal::IsEqualTo, RhsT const&> + operator == ( RhsT const& rhs ) { + return captureExpression<Internal::IsEqualTo>( rhs ); + } + + template<typename RhsT> + BinaryExpression<T, Internal::IsNotEqualTo, RhsT const&> + operator != ( RhsT const& rhs ) { + return captureExpression<Internal::IsNotEqualTo>( rhs ); + } + + template<typename RhsT> + BinaryExpression<T, Internal::IsLessThan, RhsT const&> + operator < ( RhsT const& rhs ) { + return captureExpression<Internal::IsLessThan>( rhs ); + } + + template<typename RhsT> + BinaryExpression<T, Internal::IsGreaterThan, RhsT const&> + operator > ( RhsT const& rhs ) { + return captureExpression<Internal::IsGreaterThan>( rhs ); + } + + template<typename RhsT> + BinaryExpression<T, Internal::IsLessThanOrEqualTo, RhsT const&> + operator <= ( RhsT const& rhs ) { + return captureExpression<Internal::IsLessThanOrEqualTo>( rhs ); + } + + template<typename RhsT> + BinaryExpression<T, Internal::IsGreaterThanOrEqualTo, RhsT const&> + operator >= ( RhsT const& rhs ) { + return captureExpression<Internal::IsGreaterThanOrEqualTo>( rhs ); + } + + BinaryExpression<T, Internal::IsEqualTo, bool> operator == ( bool rhs ) { + return captureExpression<Internal::IsEqualTo>( rhs ); + } + + BinaryExpression<T, Internal::IsNotEqualTo, bool> operator != ( bool rhs ) { + return captureExpression<Internal::IsNotEqualTo>( rhs ); + } + + void endExpression() { + m_truthy = m_lhs ? true : false; + m_rb + .setResultType( m_truthy ) + .endExpression( *this ); + } + + virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE { + dest = Catch::toString( m_truthy ); + } + +private: + template<Internal::Operator Op, typename RhsT> + BinaryExpression<T, Op, RhsT&> captureExpression( RhsT& rhs ) const { + return BinaryExpression<T, Op, RhsT&>( m_rb, m_lhs, rhs ); + } + + template<Internal::Operator Op> + BinaryExpression<T, Op, bool> captureExpression( bool rhs ) const { + return BinaryExpression<T, Op, bool>( m_rb, m_lhs, rhs ); + } + +private: + ResultBuilder& m_rb; + T m_lhs; + bool m_truthy; +}; + +template<typename LhsT, Internal::Operator Op, typename RhsT> +class BinaryExpression : public DecomposedExpression { +public: + BinaryExpression( ResultBuilder& rb, LhsT lhs, RhsT rhs ) + : m_rb( rb ), m_lhs( lhs ), m_rhs( rhs ) {} + + BinaryExpression& operator = ( BinaryExpression& ); + + void endExpression() const { + m_rb + .setResultType( Internal::compare<Op>( m_lhs, m_rhs ) ) + .endExpression( *this ); + } + + virtual bool isBinaryExpression() const CATCH_OVERRIDE { + return true; + } + + virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE { + std::string lhs = Catch::toString( m_lhs ); + std::string rhs = Catch::toString( m_rhs ); + char delim = lhs.size() + rhs.size() < 40 && + lhs.find('\n') == std::string::npos && + rhs.find('\n') == std::string::npos ? ' ' : '\n'; + dest.reserve( 7 + lhs.size() + rhs.size() ); + // 2 for spaces around operator + // 2 for operator + // 2 for parentheses (conditionally added later) + // 1 for negation (conditionally added later) + dest = lhs; + dest += delim; + dest += Internal::OperatorTraits<Op>::getName(); + dest += delim; + dest += rhs; + } + +private: + ResultBuilder& m_rb; + LhsT m_lhs; + RhsT m_rhs; +}; + +template<typename ArgT, typename MatcherT> +class MatchExpression : public DecomposedExpression { +public: + MatchExpression( ArgT arg, MatcherT matcher, char const* matcherString ) + : m_arg( arg ), m_matcher( matcher ), m_matcherString( matcherString ) {} + + virtual bool isBinaryExpression() const CATCH_OVERRIDE { + return true; + } + + virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE { + std::string matcherAsString = m_matcher.toString(); + dest = Catch::toString( m_arg ); + dest += ' '; + if( matcherAsString == Detail::unprintableString ) + dest += m_matcherString; + else + dest += matcherAsString; + } + +private: + ArgT m_arg; + MatcherT m_matcher; + char const* m_matcherString; +}; + +} // end namespace Catch + + +namespace Catch { + + template<typename T> + inline ExpressionLhs<T const&> ResultBuilder::operator <= ( T const& operand ) { + return ExpressionLhs<T const&>( *this, operand ); + } + + inline ExpressionLhs<bool> ResultBuilder::operator <= ( bool value ) { + return ExpressionLhs<bool>( *this, value ); + } + + template<typename ArgT, typename MatcherT> + inline void ResultBuilder::captureMatch( ArgT const& arg, MatcherT const& matcher, + char const* matcherString ) { + MatchExpression<ArgT const&, MatcherT const&> expr( arg, matcher, matcherString ); + setResultType( matcher.match( arg ) ); + endExpression( expr ); + } + +} // namespace Catch + +// #included from: catch_message.h +#define TWOBLUECUBES_CATCH_MESSAGE_H_INCLUDED + +#include <string> + +namespace Catch { + + struct MessageInfo { + MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ); + + std::string macroName; + SourceLineInfo lineInfo; + ResultWas::OfType type; + std::string message; + unsigned int sequence; + + bool operator == ( MessageInfo const& other ) const { + return sequence == other.sequence; + } + bool operator < ( MessageInfo const& other ) const { + return sequence < other.sequence; + } + private: + static unsigned int globalCount; + }; + + struct MessageBuilder { + MessageBuilder( std::string const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ) + : m_info( macroName, lineInfo, type ) + {} + + template<typename T> + MessageBuilder& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + MessageInfo m_info; + std::ostringstream m_stream; + }; + + class ScopedMessage { + public: + ScopedMessage( MessageBuilder const& builder ); + ScopedMessage( ScopedMessage const& other ); + ~ScopedMessage(); + + MessageInfo m_info; + }; + +} // end namespace Catch + +// #included from: catch_interfaces_capture.h +#define TWOBLUECUBES_CATCH_INTERFACES_CAPTURE_H_INCLUDED + +#include <string> + +namespace Catch { + + class TestCase; + class AssertionResult; + struct AssertionInfo; + struct SectionInfo; + struct SectionEndInfo; + struct MessageInfo; + class ScopedMessageBuilder; + struct Counts; + + struct IResultCapture { + + virtual ~IResultCapture(); + + virtual void assertionEnded( AssertionResult const& result ) = 0; + virtual bool sectionStarted( SectionInfo const& sectionInfo, + Counts& assertions ) = 0; + virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; + virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; + virtual void pushScopedMessage( MessageInfo const& message ) = 0; + virtual void popScopedMessage( MessageInfo const& message ) = 0; + + virtual std::string getCurrentTestName() const = 0; + virtual const AssertionResult* getLastResult() const = 0; + + virtual void exceptionEarlyReported() = 0; + + virtual void handleFatalErrorCondition( std::string const& message ) = 0; + }; + + IResultCapture& getResultCapture(); +} + +// #included from: catch_debugger.h +#define TWOBLUECUBES_CATCH_DEBUGGER_H_INCLUDED + +// #included from: catch_platform.h +#define TWOBLUECUBES_CATCH_PLATFORM_H_INCLUDED + +#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) +# define CATCH_PLATFORM_MAC +#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +# define CATCH_PLATFORM_IPHONE +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) +# define CATCH_PLATFORM_WINDOWS +# if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) +# define CATCH_DEFINES_NOMINMAX +# endif +# if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) +# define CATCH_DEFINES_WIN32_LEAN_AND_MEAN +# endif +#endif + +#include <string> + +namespace Catch{ + + bool isDebuggerActive(); + void writeToDebugConsole( std::string const& text ); +} + +#ifdef CATCH_PLATFORM_MAC + + // The following code snippet based on: + // http://cocoawithlove.com/2008/03/break-into-debugger.html + #if defined(__ppc64__) || defined(__ppc__) + #define CATCH_TRAP() \ + __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \ + : : : "memory","r0","r3","r4" ) + #else + #define CATCH_TRAP() __asm__("int $3\n" : : ) + #endif + +#elif defined(CATCH_PLATFORM_LINUX) + // If we can use inline assembler, do it because this allows us to break + // directly at the location of the failing check instead of breaking inside + // raise() called from it, i.e. one stack frame below. + #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) + #define CATCH_TRAP() asm volatile ("int $3") + #else // Fall back to the generic way. + #include <signal.h> + + #define CATCH_TRAP() raise(SIGTRAP) + #endif +#elif defined(_MSC_VER) + #define CATCH_TRAP() __debugbreak() +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) void __stdcall DebugBreak(); + #define CATCH_TRAP() DebugBreak() +#endif + +#ifdef CATCH_TRAP + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } +#else + #define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue(); +#endif + +// #included from: catch_interfaces_runner.h +#define TWOBLUECUBES_CATCH_INTERFACES_RUNNER_H_INCLUDED + +namespace Catch { + class TestCase; + + struct IRunner { + virtual ~IRunner(); + virtual bool aborting() const = 0; + }; +} + +#if defined(CATCH_CONFIG_FAST_COMPILE) +/////////////////////////////////////////////////////////////////////////////// +// We can speedup compilation significantly by breaking into debugger lower in +// the callstack, because then we don't have to expand CATCH_BREAK_INTO_DEBUGGER +// macro in each assertion +#define INTERNAL_CATCH_REACT( resultBuilder ) \ + resultBuilder.react(); + +/////////////////////////////////////////////////////////////////////////////// +// Another way to speed-up compilation is to omit local try-catch for REQUIRE* +// macros. +// This can potentially cause false negative, if the test code catches +// the exception before it propagates back up to the runner. +#define INTERNAL_CATCH_TEST_NO_TRY( macroName, resultDisposition, expr ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + __catchResult.setExceptionGuard(); \ + CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + ( __catchResult <= expr ).endExpression(); \ + CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + __catchResult.unsetExceptionGuard(); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::isTrue( false && static_cast<bool>( !!(expr) ) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look +// The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. + +#define INTERNAL_CHECK_THAT_NO_TRY( macroName, matcher, resultDisposition, arg ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg ", " #matcher, resultDisposition ); \ + __catchResult.setExceptionGuard(); \ + __catchResult.captureMatch( arg, matcher, #matcher ); \ + __catchResult.unsetExceptionGuard(); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +#else +/////////////////////////////////////////////////////////////////////////////// +// In the event of a failure works out if the debugger needs to be invoked +// and/or an exception thrown and takes appropriate action. +// This needs to be done as a macro so the debugger will stop in the user +// source code rather than in Catch library code +#define INTERNAL_CATCH_REACT( resultBuilder ) \ + if( resultBuilder.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \ + resultBuilder.react(); +#endif + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + try { \ + CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + ( __catchResult <= expr ).endExpression(); \ + CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( resultDisposition ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::isTrue( false && static_cast<bool>( !!(expr) ) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look + // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_IF( macroName, resultDisposition, expr ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ); \ + if( Catch::getResultCapture().getLastResult()->succeeded() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, expr ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ); \ + if( !Catch::getResultCapture().getLastResult()->succeeded() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, expr ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + try { \ + static_cast<void>(expr); \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( resultDisposition ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, matcher, expr ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition, #matcher ); \ + if( __catchResult.allowThrows() ) \ + try { \ + static_cast<void>(expr); \ + __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ + } \ + catch( ... ) { \ + __catchResult.captureExpectedException( matcher ); \ + } \ + else \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr ", " #exceptionType, resultDisposition ); \ + if( __catchResult.allowThrows() ) \ + try { \ + static_cast<void>(expr); \ + __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ + } \ + catch( exceptionType ) { \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( resultDisposition ); \ + } \ + else \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ + __catchResult << __VA_ARGS__ + ::Catch::StreamEndStop(); \ + __catchResult.captureResult( messageType ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) +#else + #define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, log ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ + __catchResult << log + ::Catch::StreamEndStop(); \ + __catchResult.captureResult( messageType ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) +#endif + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_INFO( macroName, log ) \ + Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log; + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg ", " #matcher, resultDisposition ); \ + try { \ + __catchResult.captureMatch( arg, matcher, #matcher ); \ + } catch( ... ) { \ + __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +// #included from: internal/catch_section.h +#define TWOBLUECUBES_CATCH_SECTION_H_INCLUDED + +// #included from: catch_section_info.h +#define TWOBLUECUBES_CATCH_SECTION_INFO_H_INCLUDED + +// #included from: catch_totals.hpp +#define TWOBLUECUBES_CATCH_TOTALS_HPP_INCLUDED + +#include <cstddef> + +namespace Catch { + + struct Counts { + Counts() : passed( 0 ), failed( 0 ), failedButOk( 0 ) {} + + Counts operator - ( Counts const& other ) const { + Counts diff; + diff.passed = passed - other.passed; + diff.failed = failed - other.failed; + diff.failedButOk = failedButOk - other.failedButOk; + return diff; + } + Counts& operator += ( Counts const& other ) { + passed += other.passed; + failed += other.failed; + failedButOk += other.failedButOk; + return *this; + } + + std::size_t total() const { + return passed + failed + failedButOk; + } + bool allPassed() const { + return failed == 0 && failedButOk == 0; + } + bool allOk() const { + return failed == 0; + } + + std::size_t passed; + std::size_t failed; + std::size_t failedButOk; + }; + + struct Totals { + + Totals operator - ( Totals const& other ) const { + Totals diff; + diff.assertions = assertions - other.assertions; + diff.testCases = testCases - other.testCases; + return diff; + } + + Totals delta( Totals const& prevTotals ) const { + Totals diff = *this - prevTotals; + if( diff.assertions.failed > 0 ) + ++diff.testCases.failed; + else if( diff.assertions.failedButOk > 0 ) + ++diff.testCases.failedButOk; + else + ++diff.testCases.passed; + return diff; + } + + Totals& operator += ( Totals const& other ) { + assertions += other.assertions; + testCases += other.testCases; + return *this; + } + + Counts assertions; + Counts testCases; + }; +} + +#include <string> + +namespace Catch { + + struct SectionInfo { + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& _description = std::string() ); + + std::string name; + std::string description; + SourceLineInfo lineInfo; + }; + + struct SectionEndInfo { + SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds ) + : sectionInfo( _sectionInfo ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) + {} + + SectionInfo sectionInfo; + Counts prevAssertions; + double durationInSeconds; + }; + +} // end namespace Catch + +// #included from: catch_timer.h +#define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED + +#ifdef _MSC_VER + +namespace Catch { + typedef unsigned long long UInt64; +} +#else +#include <stdint.h> +namespace Catch { + typedef uint64_t UInt64; +} +#endif + +namespace Catch { + class Timer { + public: + Timer() : m_ticks( 0 ) {} + void start(); + unsigned int getElapsedMicroseconds() const; + unsigned int getElapsedMilliseconds() const; + double getElapsedSeconds() const; + + private: + UInt64 m_ticks; + }; + +} // namespace Catch + +#include <string> + +namespace Catch { + + class Section : NonCopyable { + public: + Section( SectionInfo const& info ); + ~Section(); + + // This indicates whether the section should be executed or not + operator bool() const; + + private: + SectionInfo m_info; + + std::string m_name; + Counts m_assertions; + bool m_sectionIncluded; + Timer m_timer; + }; + +} // end namespace Catch + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define INTERNAL_CATCH_SECTION( ... ) \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) +#else + #define INTERNAL_CATCH_SECTION( name, desc ) \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, name, desc ) ) +#endif + +// #included from: internal/catch_generators.hpp +#define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED + +#include <vector> +#include <string> +#include <stdlib.h> + +namespace Catch { + +template<typename T> +struct IGenerator { + virtual ~IGenerator() {} + virtual T getValue( std::size_t index ) const = 0; + virtual std::size_t size () const = 0; +}; + +template<typename T> +class BetweenGenerator : public IGenerator<T> { +public: + BetweenGenerator( T from, T to ) : m_from( from ), m_to( to ){} + + virtual T getValue( std::size_t index ) const { + return m_from+static_cast<int>( index ); + } + + virtual std::size_t size() const { + return static_cast<std::size_t>( 1+m_to-m_from ); + } + +private: + + T m_from; + T m_to; +}; + +template<typename T> +class ValuesGenerator : public IGenerator<T> { +public: + ValuesGenerator(){} + + void add( T value ) { + m_values.push_back( value ); + } + + virtual T getValue( std::size_t index ) const { + return m_values[index]; + } + + virtual std::size_t size() const { + return m_values.size(); + } + +private: + std::vector<T> m_values; +}; + +template<typename T> +class CompositeGenerator { +public: + CompositeGenerator() : m_totalSize( 0 ) {} + + // *** Move semantics, similar to auto_ptr *** + CompositeGenerator( CompositeGenerator& other ) + : m_fileInfo( other.m_fileInfo ), + m_totalSize( 0 ) + { + move( other ); + } + + CompositeGenerator& setFileInfo( const char* fileInfo ) { + m_fileInfo = fileInfo; + return *this; + } + + ~CompositeGenerator() { + deleteAll( m_composed ); + } + + operator T () const { + size_t overallIndex = getCurrentContext().getGeneratorIndex( m_fileInfo, m_totalSize ); + + typename std::vector<const IGenerator<T>*>::const_iterator it = m_composed.begin(); + typename std::vector<const IGenerator<T>*>::const_iterator itEnd = m_composed.end(); + for( size_t index = 0; it != itEnd; ++it ) + { + const IGenerator<T>* generator = *it; + if( overallIndex >= index && overallIndex < index + generator->size() ) + { + return generator->getValue( overallIndex-index ); + } + index += generator->size(); + } + CATCH_INTERNAL_ERROR( "Indexed past end of generated range" ); + return T(); // Suppress spurious "not all control paths return a value" warning in Visual Studio - if you know how to fix this please do so + } + + void add( const IGenerator<T>* generator ) { + m_totalSize += generator->size(); + m_composed.push_back( generator ); + } + + CompositeGenerator& then( CompositeGenerator& other ) { + move( other ); + return *this; + } + + CompositeGenerator& then( T value ) { + ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>(); + valuesGen->add( value ); + add( valuesGen ); + return *this; + } + +private: + + void move( CompositeGenerator& other ) { + m_composed.insert( m_composed.end(), other.m_composed.begin(), other.m_composed.end() ); + m_totalSize += other.m_totalSize; + other.m_composed.clear(); + } + + std::vector<const IGenerator<T>*> m_composed; + std::string m_fileInfo; + size_t m_totalSize; +}; + +namespace Generators +{ + template<typename T> + CompositeGenerator<T> between( T from, T to ) { + CompositeGenerator<T> generators; + generators.add( new BetweenGenerator<T>( from, to ) ); + return generators; + } + + template<typename T> + CompositeGenerator<T> values( T val1, T val2 ) { + CompositeGenerator<T> generators; + ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + generators.add( valuesGen ); + return generators; + } + + template<typename T> + CompositeGenerator<T> values( T val1, T val2, T val3 ){ + CompositeGenerator<T> generators; + ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + valuesGen->add( val3 ); + generators.add( valuesGen ); + return generators; + } + + template<typename T> + CompositeGenerator<T> values( T val1, T val2, T val3, T val4 ) { + CompositeGenerator<T> generators; + ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + valuesGen->add( val3 ); + valuesGen->add( val4 ); + generators.add( valuesGen ); + return generators; + } + +} // end namespace Generators + +using namespace Generators; + +} // end namespace Catch + +#define INTERNAL_CATCH_LINESTR2( line ) #line +#define INTERNAL_CATCH_LINESTR( line ) INTERNAL_CATCH_LINESTR2( line ) + +#define INTERNAL_CATCH_GENERATE( expr ) expr.setFileInfo( __FILE__ "(" INTERNAL_CATCH_LINESTR( __LINE__ ) ")" ) + +// #included from: internal/catch_interfaces_exception.h +#define TWOBLUECUBES_CATCH_INTERFACES_EXCEPTION_H_INCLUDED + +#include <string> +#include <vector> + +// #included from: catch_interfaces_registry_hub.h +#define TWOBLUECUBES_CATCH_INTERFACES_REGISTRY_HUB_H_INCLUDED + +#include <string> + +namespace Catch { + + class TestCase; + struct ITestCaseRegistry; + struct IExceptionTranslatorRegistry; + struct IExceptionTranslator; + struct IReporterRegistry; + struct IReporterFactory; + struct ITagAliasRegistry; + + struct IRegistryHub { + virtual ~IRegistryHub(); + + virtual IReporterRegistry const& getReporterRegistry() const = 0; + virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; + virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; + + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; + }; + + struct IMutableRegistryHub { + virtual ~IMutableRegistryHub(); + virtual void registerReporter( std::string const& name, Ptr<IReporterFactory> const& factory ) = 0; + virtual void registerListener( Ptr<IReporterFactory> const& factory ) = 0; + virtual void registerTest( TestCase const& testInfo ) = 0; + virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; + virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0; + }; + + IRegistryHub& getRegistryHub(); + IMutableRegistryHub& getMutableRegistryHub(); + void cleanUp(); + std::string translateActiveException(); + +} + +namespace Catch { + + typedef std::string(*exceptionTranslateFunction)(); + + struct IExceptionTranslator; + typedef std::vector<const IExceptionTranslator*> ExceptionTranslators; + + struct IExceptionTranslator { + virtual ~IExceptionTranslator(); + virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0; + }; + + struct IExceptionTranslatorRegistry { + virtual ~IExceptionTranslatorRegistry(); + + virtual std::string translateActiveException() const = 0; + }; + + class ExceptionTranslatorRegistrar { + template<typename T> + class ExceptionTranslator : public IExceptionTranslator { + public: + + ExceptionTranslator( std::string(*translateFunction)( T& ) ) + : m_translateFunction( translateFunction ) + {} + + virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const CATCH_OVERRIDE { + try { + if( it == itEnd ) + throw; + else + return (*it)->translate( it+1, itEnd ); + } + catch( T& ex ) { + return m_translateFunction( ex ); + } + } + + protected: + std::string(*m_translateFunction)( T& ); + }; + + public: + template<typename T> + ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) { + getMutableRegistryHub().registerTranslator + ( new ExceptionTranslator<T>( translateFunction ) ); + } + }; +} + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \ + static std::string translatorName( signature ); \ + namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); }\ + static std::string translatorName( signature ) + +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) + +// #included from: internal/catch_approx.hpp +#define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED + +#include <cmath> +#include <limits> + +#if defined(CATCH_CONFIG_CPP11_TYPE_TRAITS) +#include <type_traits> +#endif + +namespace Catch { +namespace Detail { + + class Approx { + public: + explicit Approx ( double value ) + : m_epsilon( std::numeric_limits<float>::epsilon()*100 ), + m_margin( 0.0 ), + m_scale( 1.0 ), + m_value( value ) + {} + + Approx( Approx const& other ) + : m_epsilon( other.m_epsilon ), + m_margin( other.m_margin ), + m_scale( other.m_scale ), + m_value( other.m_value ) + {} + + static Approx custom() { + return Approx( 0 ); + } + +#if defined(CATCH_CONFIG_CPP11_TYPE_TRAITS) + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + Approx operator()( T value ) { + Approx approx( static_cast<double>(value) ); + approx.epsilon( m_epsilon ); + approx.margin( m_margin ); + approx.scale( m_scale ); + return approx; + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + explicit Approx( T value ): Approx(static_cast<double>(value)) + {} + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator == ( const T& lhs, Approx const& rhs ) { + // Thanks to Richard Harris for his help refining this formula + auto lhs_v = double(lhs); + bool relativeOK = std::fabs(lhs_v - rhs.m_value) < rhs.m_epsilon * (rhs.m_scale + (std::max)(std::fabs(lhs_v), std::fabs(rhs.m_value))); + if (relativeOK) { + return true; + } + return std::fabs(lhs_v - rhs.m_value) < rhs.m_margin; + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator == ( Approx const& lhs, const T& rhs ) { + return operator==( rhs, lhs ); + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator != ( T lhs, Approx const& rhs ) { + return !operator==( lhs, rhs ); + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator != ( Approx const& lhs, T rhs ) { + return !operator==( rhs, lhs ); + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator <= ( T lhs, Approx const& rhs ) { + return double(lhs) < rhs.m_value || lhs == rhs; + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator <= ( Approx const& lhs, T rhs ) { + return lhs.m_value < double(rhs) || lhs == rhs; + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator >= ( T lhs, Approx const& rhs ) { + return double(lhs) > rhs.m_value || lhs == rhs; + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator >= ( Approx const& lhs, T rhs ) { + return lhs.m_value > double(rhs) || lhs == rhs; + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + Approx& epsilon( T newEpsilon ) { + m_epsilon = double(newEpsilon); + return *this; + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + Approx& margin( T newMargin ) { + m_margin = double(newMargin); + return *this; + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + Approx& scale( T newScale ) { + m_scale = double(newScale); + return *this; + } + +#else + + Approx operator()( double value ) { + Approx approx( value ); + approx.epsilon( m_epsilon ); + approx.margin( m_margin ); + approx.scale( m_scale ); + return approx; + } + + friend bool operator == ( double lhs, Approx const& rhs ) { + // Thanks to Richard Harris for his help refining this formula + bool relativeOK = std::fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( std::fabs(lhs), std::fabs(rhs.m_value) ) ); + if (relativeOK) { + return true; + } + return std::fabs(lhs - rhs.m_value) < rhs.m_margin; + } + + friend bool operator == ( Approx const& lhs, double rhs ) { + return operator==( rhs, lhs ); + } + + friend bool operator != ( double lhs, Approx const& rhs ) { + return !operator==( lhs, rhs ); + } + + friend bool operator != ( Approx const& lhs, double rhs ) { + return !operator==( rhs, lhs ); + } + + friend bool operator <= ( double lhs, Approx const& rhs ) { + return lhs < rhs.m_value || lhs == rhs; + } + + friend bool operator <= ( Approx const& lhs, double rhs ) { + return lhs.m_value < rhs || lhs == rhs; + } + + friend bool operator >= ( double lhs, Approx const& rhs ) { + return lhs > rhs.m_value || lhs == rhs; + } + + friend bool operator >= ( Approx const& lhs, double rhs ) { + return lhs.m_value > rhs || lhs == rhs; + } + + Approx& epsilon( double newEpsilon ) { + m_epsilon = newEpsilon; + return *this; + } + + Approx& margin( double newMargin ) { + m_margin = newMargin; + return *this; + } + + Approx& scale( double newScale ) { + m_scale = newScale; + return *this; + } +#endif + + std::string toString() const { + std::ostringstream oss; + oss << "Approx( " << Catch::toString( m_value ) << " )"; + return oss.str(); + } + + private: + double m_epsilon; + double m_margin; + double m_scale; + double m_value; + }; +} + +template<> +inline std::string toString<Detail::Approx>( Detail::Approx const& value ) { + return value.toString(); +} + +} // end namespace Catch + +// #included from: internal/catch_matchers_string.h +#define TWOBLUECUBES_CATCH_MATCHERS_STRING_H_INCLUDED + +namespace Catch { +namespace Matchers { + + namespace StdString { + + struct CasedString + { + CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ); + std::string adjustString( std::string const& str ) const; + std::string caseSensitivitySuffix() const; + + CaseSensitive::Choice m_caseSensitivity; + std::string m_str; + }; + + struct StringMatcherBase : MatcherBase<std::string> { + StringMatcherBase( std::string const& operation, CasedString const& comparator ); + virtual std::string describe() const CATCH_OVERRIDE; + + CasedString m_comparator; + std::string m_operation; + }; + + struct EqualsMatcher : StringMatcherBase { + EqualsMatcher( CasedString const& comparator ); + virtual bool match( std::string const& source ) const CATCH_OVERRIDE; + }; + struct ContainsMatcher : StringMatcherBase { + ContainsMatcher( CasedString const& comparator ); + virtual bool match( std::string const& source ) const CATCH_OVERRIDE; + }; + struct StartsWithMatcher : StringMatcherBase { + StartsWithMatcher( CasedString const& comparator ); + virtual bool match( std::string const& source ) const CATCH_OVERRIDE; + }; + struct EndsWithMatcher : StringMatcherBase { + EndsWithMatcher( CasedString const& comparator ); + virtual bool match( std::string const& source ) const CATCH_OVERRIDE; + }; + + } // namespace StdString + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + + StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + +} // namespace Matchers +} // namespace Catch + +// #included from: internal/catch_matchers_vector.h +#define TWOBLUECUBES_CATCH_MATCHERS_VECTOR_H_INCLUDED + +namespace Catch { +namespace Matchers { + + namespace Vector { + + template<typename T> + struct ContainsElementMatcher : MatcherBase<std::vector<T>, T> { + + ContainsElementMatcher(T const &comparator) : m_comparator( comparator) {} + + bool match(std::vector<T> const &v) const CATCH_OVERRIDE { + return std::find(v.begin(), v.end(), m_comparator) != v.end(); + } + + virtual std::string describe() const CATCH_OVERRIDE { + return "Contains: " + Catch::toString( m_comparator ); + } + + T const& m_comparator; + }; + + template<typename T> + struct ContainsMatcher : MatcherBase<std::vector<T>, std::vector<T> > { + + ContainsMatcher(std::vector<T> const &comparator) : m_comparator( comparator ) {} + + bool match(std::vector<T> const &v) const CATCH_OVERRIDE { + // !TBD: see note in EqualsMatcher + if (m_comparator.size() > v.size()) + return false; + for (size_t i = 0; i < m_comparator.size(); ++i) + if (std::find(v.begin(), v.end(), m_comparator[i]) == v.end()) + return false; + return true; + } + virtual std::string describe() const CATCH_OVERRIDE { + return "Contains: " + Catch::toString( m_comparator ); + } + + std::vector<T> const& m_comparator; + }; + + template<typename T> + struct EqualsMatcher : MatcherBase<std::vector<T>, std::vector<T> > { + + EqualsMatcher(std::vector<T> const &comparator) : m_comparator( comparator ) {} + + bool match(std::vector<T> const &v) const CATCH_OVERRIDE { + // !TBD: This currently works if all elements can be compared using != + // - a more general approach would be via a compare template that defaults + // to using !=. but could be specialised for, e.g. std::vector<T> etc + // - then just call that directly + if (m_comparator.size() != v.size()) + return false; + for (size_t i = 0; i < v.size(); ++i) + if (m_comparator[i] != v[i]) + return false; + return true; + } + virtual std::string describe() const CATCH_OVERRIDE { + return "Equals: " + Catch::toString( m_comparator ); + } + std::vector<T> const& m_comparator; + }; + + } // namespace Vector + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + + template<typename T> + Vector::ContainsMatcher<T> Contains( std::vector<T> const& comparator ) { + return Vector::ContainsMatcher<T>( comparator ); + } + + template<typename T> + Vector::ContainsElementMatcher<T> VectorContains( T const& comparator ) { + return Vector::ContainsElementMatcher<T>( comparator ); + } + + template<typename T> + Vector::EqualsMatcher<T> Equals( std::vector<T> const& comparator ) { + return Vector::EqualsMatcher<T>( comparator ); + } + +} // namespace Matchers +} // namespace Catch + +// #included from: internal/catch_interfaces_tag_alias_registry.h +#define TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED + +// #included from: catch_tag_alias.h +#define TWOBLUECUBES_CATCH_TAG_ALIAS_H_INCLUDED + +#include <string> + +namespace Catch { + + struct TagAlias { + TagAlias( std::string const& _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {} + + std::string tag; + SourceLineInfo lineInfo; + }; + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } +// #included from: catch_option.hpp +#define TWOBLUECUBES_CATCH_OPTION_HPP_INCLUDED + +namespace Catch { + + // An optional type + template<typename T> + class Option { + public: + Option() : nullableValue( CATCH_NULL ) {} + Option( T const& _value ) + : nullableValue( new( storage ) T( _value ) ) + {} + Option( Option const& _other ) + : nullableValue( _other ? new( storage ) T( *_other ) : CATCH_NULL ) + {} + + ~Option() { + reset(); + } + + Option& operator= ( Option const& _other ) { + if( &_other != this ) { + reset(); + if( _other ) + nullableValue = new( storage ) T( *_other ); + } + return *this; + } + Option& operator = ( T const& _value ) { + reset(); + nullableValue = new( storage ) T( _value ); + return *this; + } + + void reset() { + if( nullableValue ) + nullableValue->~T(); + nullableValue = CATCH_NULL; + } + + T& operator*() { return *nullableValue; } + T const& operator*() const { return *nullableValue; } + T* operator->() { return nullableValue; } + const T* operator->() const { return nullableValue; } + + T valueOr( T const& defaultValue ) const { + return nullableValue ? *nullableValue : defaultValue; + } + + bool some() const { return nullableValue != CATCH_NULL; } + bool none() const { return nullableValue == CATCH_NULL; } + + bool operator !() const { return nullableValue == CATCH_NULL; } + operator SafeBool::type() const { + return SafeBool::makeSafe( some() ); + } + + private: + T *nullableValue; + union { + char storage[sizeof(T)]; + + // These are here to force alignment for the storage + long double dummy1; + void (*dummy2)(); + long double dummy3; +#ifdef CATCH_CONFIG_CPP11_LONG_LONG + long long dummy4; +#endif + }; + }; + +} // end namespace Catch + +namespace Catch { + + struct ITagAliasRegistry { + virtual ~ITagAliasRegistry(); + virtual Option<TagAlias> find( std::string const& alias ) const = 0; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; + + static ITagAliasRegistry const& get(); + }; + +} // end namespace Catch + +// These files are included here so the single_include script doesn't put them +// in the conditionally compiled sections +// #included from: internal/catch_test_case_info.h +#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_H_INCLUDED + +#include <string> +#include <set> + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + struct ITestCase; + + struct TestCaseInfo { + enum SpecialProperties{ + None = 0, + IsHidden = 1 << 1, + ShouldFail = 1 << 2, + MayFail = 1 << 3, + Throws = 1 << 4, + NonPortable = 1 << 5 + }; + + TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::set<std::string> const& _tags, + SourceLineInfo const& _lineInfo ); + + TestCaseInfo( TestCaseInfo const& other ); + + friend void setTags( TestCaseInfo& testCaseInfo, std::set<std::string> const& tags ); + + bool isHidden() const; + bool throws() const; + bool okToFail() const; + bool expectedToFail() const; + + std::string name; + std::string className; + std::string description; + std::set<std::string> tags; + std::set<std::string> lcaseTags; + std::string tagsAsString; + SourceLineInfo lineInfo; + SpecialProperties properties; + }; + + class TestCase : public TestCaseInfo { + public: + + TestCase( ITestCase* testCase, TestCaseInfo const& info ); + TestCase( TestCase const& other ); + + TestCase withName( std::string const& _newName ) const; + + void invoke() const; + + TestCaseInfo const& getTestCaseInfo() const; + + void swap( TestCase& other ); + bool operator == ( TestCase const& other ) const; + bool operator < ( TestCase const& other ) const; + TestCase& operator = ( TestCase const& other ); + + private: + Ptr<ITestCase> test; + }; + + TestCase makeTestCase( ITestCase* testCase, + std::string const& className, + std::string const& name, + std::string const& description, + SourceLineInfo const& lineInfo ); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + +#ifdef __OBJC__ +// #included from: internal/catch_objc.hpp +#define TWOBLUECUBES_CATCH_OBJC_HPP_INCLUDED + +#import <objc/runtime.h> + +#include <string> + +// NB. Any general catch headers included here must be included +// in catch.hpp first to make sure they are included by the single +// header for non obj-usage + +/////////////////////////////////////////////////////////////////////////////// +// This protocol is really only here for (self) documenting purposes, since +// all its methods are optional. +@protocol OcFixture + +@optional + +-(void) setUp; +-(void) tearDown; + +@end + +namespace Catch { + + class OcMethod : public SharedImpl<ITestCase> { + + public: + OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} + + virtual void invoke() const { + id obj = [[m_cls alloc] init]; + + performOptionalSelector( obj, @selector(setUp) ); + performOptionalSelector( obj, m_sel ); + performOptionalSelector( obj, @selector(tearDown) ); + + arcSafeRelease( obj ); + } + private: + virtual ~OcMethod() {} + + Class m_cls; + SEL m_sel; + }; + + namespace Detail{ + + inline std::string getAnnotation( Class cls, + std::string const& annotationName, + std::string const& testCaseName ) { + NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; + SEL sel = NSSelectorFromString( selStr ); + arcSafeRelease( selStr ); + id value = performOptionalSelector( cls, sel ); + if( value ) + return [(NSString*)value UTF8String]; + return ""; + } + } + + inline size_t registerTestMethods() { + size_t noTestMethods = 0; + int noClasses = objc_getClassList( CATCH_NULL, 0 ); + + Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); + objc_getClassList( classes, noClasses ); + + for( int c = 0; c < noClasses; c++ ) { + Class cls = classes[c]; + { + u_int count; + Method* methods = class_copyMethodList( cls, &count ); + for( u_int m = 0; m < count ; m++ ) { + SEL selector = method_getName(methods[m]); + std::string methodName = sel_getName(selector); + if( startsWith( methodName, "Catch_TestCase_" ) ) { + std::string testCaseName = methodName.substr( 15 ); + std::string name = Detail::getAnnotation( cls, "Name", testCaseName ); + std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); + const char* className = class_getName( cls ); + + getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo() ) ); + noTestMethods++; + } + } + free(methods); + } + } + return noTestMethods; + } + + namespace Matchers { + namespace Impl { + namespace NSStringMatchers { + + struct StringHolder : MatcherBase<NSString*>{ + StringHolder( NSString* substr ) : m_substr( [substr copy] ){} + StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} + StringHolder() { + arcSafeRelease( m_substr ); + } + + virtual bool match( NSString* arg ) const CATCH_OVERRIDE { + return false; + } + + NSString* m_substr; + }; + + struct Equals : StringHolder { + Equals( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( NSString* str ) const CATCH_OVERRIDE { + return (str != nil || m_substr == nil ) && + [str isEqualToString:m_substr]; + } + + virtual std::string describe() const CATCH_OVERRIDE { + return "equals string: " + Catch::toString( m_substr ); + } + }; + + struct Contains : StringHolder { + Contains( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( NSString* str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location != NSNotFound; + } + + virtual std::string describe() const CATCH_OVERRIDE { + return "contains string: " + Catch::toString( m_substr ); + } + }; + + struct StartsWith : StringHolder { + StartsWith( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( NSString* str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == 0; + } + + virtual std::string describe() const CATCH_OVERRIDE { + return "starts with: " + Catch::toString( m_substr ); + } + }; + struct EndsWith : StringHolder { + EndsWith( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( NSString* str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == [str length] - [m_substr length]; + } + + virtual std::string describe() const CATCH_OVERRIDE { + return "ends with: " + Catch::toString( m_substr ); + } + }; + + } // namespace NSStringMatchers + } // namespace Impl + + inline Impl::NSStringMatchers::Equals + Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); } + + inline Impl::NSStringMatchers::Contains + Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); } + + inline Impl::NSStringMatchers::StartsWith + StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); } + + inline Impl::NSStringMatchers::EndsWith + EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); } + + } // namespace Matchers + + using namespace Matchers; + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define OC_TEST_CASE( name, desc )\ ++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Name_test ) \ +{\ +return @ name; \ +}\ ++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Description_test ) \ +{ \ +return @ desc; \ +} \ +-(void) INTERNAL_CATCH_UNIQUE_NAME( Catch_TestCase_test ) + +#endif + +#ifdef CATCH_IMPL + +// !TBD: Move the leak detector code into a separate header +#ifdef CATCH_CONFIG_WINDOWS_CRTDBG +#include <crtdbg.h> +class LeakDetector { +public: + LeakDetector() { + int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + flag |= _CRTDBG_LEAK_CHECK_DF; + flag |= _CRTDBG_ALLOC_MEM_DF; + _CrtSetDbgFlag(flag); + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + // Change this to leaking allocation's number to break there + _CrtSetBreakAlloc(-1); + } +}; +#else +class LeakDetector {}; +#endif + +LeakDetector leakDetector; + +// #included from: internal/catch_impl.hpp +#define TWOBLUECUBES_CATCH_IMPL_HPP_INCLUDED + +// Collect all the implementation files together here +// These are the equivalent of what would usually be cpp files + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +// #included from: ../catch_session.hpp +#define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED + +// #included from: internal/catch_commandline.hpp +#define TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED + +// #included from: catch_config.hpp +#define TWOBLUECUBES_CATCH_CONFIG_HPP_INCLUDED + +// #included from: catch_test_spec_parser.hpp +#define TWOBLUECUBES_CATCH_TEST_SPEC_PARSER_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// #included from: catch_test_spec.hpp +#define TWOBLUECUBES_CATCH_TEST_SPEC_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// #included from: catch_wildcard_pattern.hpp +#define TWOBLUECUBES_CATCH_WILDCARD_PATTERN_HPP_INCLUDED + +#include <stdexcept> + +namespace Catch +{ + class WildcardPattern { + enum WildcardPosition { + NoWildcard = 0, + WildcardAtStart = 1, + WildcardAtEnd = 2, + WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd + }; + + public: + + WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_wildcard( NoWildcard ), + m_pattern( adjustCase( pattern ) ) + { + if( startsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 1 ); + m_wildcard = WildcardAtStart; + } + if( endsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 0, m_pattern.size()-1 ); + m_wildcard = static_cast<WildcardPosition>( m_wildcard | WildcardAtEnd ); + } + } + virtual ~WildcardPattern(); + virtual bool matches( std::string const& str ) const { + switch( m_wildcard ) { + case NoWildcard: + return m_pattern == adjustCase( str ); + case WildcardAtStart: + return endsWith( adjustCase( str ), m_pattern ); + case WildcardAtEnd: + return startsWith( adjustCase( str ), m_pattern ); + case WildcardAtBothEnds: + return contains( adjustCase( str ), m_pattern ); + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + throw std::logic_error( "Unknown enum" ); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + } + private: + std::string adjustCase( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; + } + CaseSensitive::Choice m_caseSensitivity; + WildcardPosition m_wildcard; + std::string m_pattern; + }; +} + +#include <string> +#include <vector> + +namespace Catch { + + class TestSpec { + struct Pattern : SharedImpl<> { + virtual ~Pattern(); + virtual bool matches( TestCaseInfo const& testCase ) const = 0; + }; + class NamePattern : public Pattern { + public: + NamePattern( std::string const& name ) + : m_wildcardPattern( toLower( name ), CaseSensitive::No ) + {} + virtual ~NamePattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { + return m_wildcardPattern.matches( toLower( testCase.name ) ); + } + private: + WildcardPattern m_wildcardPattern; + }; + + class TagPattern : public Pattern { + public: + TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} + virtual ~TagPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { + return testCase.lcaseTags.find( m_tag ) != testCase.lcaseTags.end(); + } + private: + std::string m_tag; + }; + + class ExcludedPattern : public Pattern { + public: + ExcludedPattern( Ptr<Pattern> const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} + virtual ~ExcludedPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } + private: + Ptr<Pattern> m_underlyingPattern; + }; + + struct Filter { + std::vector<Ptr<Pattern> > m_patterns; + + bool matches( TestCaseInfo const& testCase ) const { + // All patterns in a filter must match for the filter to be a match + for( std::vector<Ptr<Pattern> >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it ) { + if( !(*it)->matches( testCase ) ) + return false; + } + return true; + } + }; + + public: + bool hasFilters() const { + return !m_filters.empty(); + } + bool matches( TestCaseInfo const& testCase ) const { + // A TestSpec matches if any filter matches + for( std::vector<Filter>::const_iterator it = m_filters.begin(), itEnd = m_filters.end(); it != itEnd; ++it ) + if( it->matches( testCase ) ) + return true; + return false; + } + + private: + std::vector<Filter> m_filters; + + friend class TestSpecParser; + }; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +namespace Catch { + + class TestSpecParser { + enum Mode{ None, Name, QuotedName, Tag, EscapedName }; + Mode m_mode; + bool m_exclusion; + std::size_t m_start, m_pos; + std::string m_arg; + std::vector<std::size_t> m_escapeChars; + TestSpec::Filter m_currentFilter; + TestSpec m_testSpec; + ITagAliasRegistry const* m_tagAliases; + + public: + TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} + + TestSpecParser& parse( std::string const& arg ) { + m_mode = None; + m_exclusion = false; + m_start = std::string::npos; + m_arg = m_tagAliases->expandAliases( arg ); + m_escapeChars.clear(); + for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) + visitChar( m_arg[m_pos] ); + if( m_mode == Name ) + addPattern<TestSpec::NamePattern>(); + return *this; + } + TestSpec testSpec() { + addFilter(); + return m_testSpec; + } + private: + void visitChar( char c ) { + if( m_mode == None ) { + switch( c ) { + case ' ': return; + case '~': m_exclusion = true; return; + case '[': return startNewMode( Tag, ++m_pos ); + case '"': return startNewMode( QuotedName, ++m_pos ); + case '\\': return escape(); + default: startNewMode( Name, m_pos ); break; + } + } + if( m_mode == Name ) { + if( c == ',' ) { + addPattern<TestSpec::NamePattern>(); + addFilter(); + } + else if( c == '[' ) { + if( subString() == "exclude:" ) + m_exclusion = true; + else + addPattern<TestSpec::NamePattern>(); + startNewMode( Tag, ++m_pos ); + } + else if( c == '\\' ) + escape(); + } + else if( m_mode == EscapedName ) + m_mode = Name; + else if( m_mode == QuotedName && c == '"' ) + addPattern<TestSpec::NamePattern>(); + else if( m_mode == Tag && c == ']' ) + addPattern<TestSpec::TagPattern>(); + } + void startNewMode( Mode mode, std::size_t start ) { + m_mode = mode; + m_start = start; + } + void escape() { + if( m_mode == None ) + m_start = m_pos; + m_mode = EscapedName; + m_escapeChars.push_back( m_pos ); + } + std::string subString() const { return m_arg.substr( m_start, m_pos - m_start ); } + template<typename T> + void addPattern() { + std::string token = subString(); + for( size_t i = 0; i < m_escapeChars.size(); ++i ) + token = token.substr( 0, m_escapeChars[i]-m_start-i ) + token.substr( m_escapeChars[i]-m_start-i+1 ); + m_escapeChars.clear(); + if( startsWith( token, "exclude:" ) ) { + m_exclusion = true; + token = token.substr( 8 ); + } + if( !token.empty() ) { + Ptr<TestSpec::Pattern> pattern = new T( token ); + if( m_exclusion ) + pattern = new TestSpec::ExcludedPattern( pattern ); + m_currentFilter.m_patterns.push_back( pattern ); + } + m_exclusion = false; + m_mode = None; + } + void addFilter() { + if( !m_currentFilter.m_patterns.empty() ) { + m_testSpec.m_filters.push_back( m_currentFilter ); + m_currentFilter = TestSpec::Filter(); + } + } + }; + inline TestSpec parseTestSpec( std::string const& arg ) { + return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); + } + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// #included from: catch_interfaces_config.h +#define TWOBLUECUBES_CATCH_INTERFACES_CONFIG_H_INCLUDED + +#include <iosfwd> +#include <string> +#include <vector> + +namespace Catch { + + struct Verbosity { enum Level { + NoOutput = 0, + Quiet, + Normal + }; }; + + struct WarnAbout { enum What { + Nothing = 0x00, + NoAssertions = 0x01 + }; }; + + struct ShowDurations { enum OrNot { + DefaultForReporter, + Always, + Never + }; }; + struct RunTests { enum InWhatOrder { + InDeclarationOrder, + InLexicographicalOrder, + InRandomOrder + }; }; + struct UseColour { enum YesOrNo { + Auto, + Yes, + No + }; }; + + class TestSpec; + + struct IConfig : IShared { + + virtual ~IConfig(); + + virtual bool allowThrows() const = 0; + virtual std::ostream& stream() const = 0; + virtual std::string name() const = 0; + virtual bool includeSuccessfulResults() const = 0; + virtual bool shouldDebugBreak() const = 0; + virtual bool warnAboutMissingAssertions() const = 0; + virtual int abortAfter() const = 0; + virtual bool showInvisibles() const = 0; + virtual ShowDurations::OrNot showDurations() const = 0; + virtual TestSpec const& testSpec() const = 0; + virtual RunTests::InWhatOrder runOrder() const = 0; + virtual unsigned int rngSeed() const = 0; + virtual UseColour::YesOrNo useColour() const = 0; + virtual std::vector<std::string> const& getSectionsToRun() const = 0; + + }; +} + +// #included from: catch_stream.h +#define TWOBLUECUBES_CATCH_STREAM_H_INCLUDED + +// #included from: catch_streambuf.h +#define TWOBLUECUBES_CATCH_STREAMBUF_H_INCLUDED + +#include <streambuf> + +namespace Catch { + + class StreamBufBase : public std::streambuf { + public: + virtual ~StreamBufBase() CATCH_NOEXCEPT; + }; +} + +#include <streambuf> +#include <ostream> +#include <fstream> +#include <memory> + +namespace Catch { + + std::ostream& cout(); + std::ostream& cerr(); + + struct IStream { + virtual ~IStream() CATCH_NOEXCEPT; + virtual std::ostream& stream() const = 0; + }; + + class FileStream : public IStream { + mutable std::ofstream m_ofs; + public: + FileStream( std::string const& filename ); + virtual ~FileStream() CATCH_NOEXCEPT; + public: // IStream + virtual std::ostream& stream() const CATCH_OVERRIDE; + }; + + class CoutStream : public IStream { + mutable std::ostream m_os; + public: + CoutStream(); + virtual ~CoutStream() CATCH_NOEXCEPT; + + public: // IStream + virtual std::ostream& stream() const CATCH_OVERRIDE; + }; + + class DebugOutStream : public IStream { + CATCH_AUTO_PTR( StreamBufBase ) m_streamBuf; + mutable std::ostream m_os; + public: + DebugOutStream(); + virtual ~DebugOutStream() CATCH_NOEXCEPT; + + public: // IStream + virtual std::ostream& stream() const CATCH_OVERRIDE; + }; +} + +#include <memory> +#include <vector> +#include <string> +#include <stdexcept> + +#ifndef CATCH_CONFIG_CONSOLE_WIDTH +#define CATCH_CONFIG_CONSOLE_WIDTH 80 +#endif + +namespace Catch { + + struct ConfigData { + + ConfigData() + : listTests( false ), + listTags( false ), + listReporters( false ), + listTestNamesOnly( false ), + showSuccessfulTests( false ), + shouldDebugBreak( false ), + noThrow( false ), + showHelp( false ), + showInvisibles( false ), + filenamesAsTags( false ), + abortAfter( -1 ), + rngSeed( 0 ), + verbosity( Verbosity::Normal ), + warnings( WarnAbout::Nothing ), + showDurations( ShowDurations::DefaultForReporter ), + runOrder( RunTests::InDeclarationOrder ), + useColour( UseColour::Auto ) + {} + + bool listTests; + bool listTags; + bool listReporters; + bool listTestNamesOnly; + + bool showSuccessfulTests; + bool shouldDebugBreak; + bool noThrow; + bool showHelp; + bool showInvisibles; + bool filenamesAsTags; + + int abortAfter; + unsigned int rngSeed; + + Verbosity::Level verbosity; + WarnAbout::What warnings; + ShowDurations::OrNot showDurations; + RunTests::InWhatOrder runOrder; + UseColour::YesOrNo useColour; + + std::string outputFilename; + std::string name; + std::string processName; + + std::vector<std::string> reporterNames; + std::vector<std::string> testsOrTags; + std::vector<std::string> sectionsToRun; + }; + + class Config : public SharedImpl<IConfig> { + private: + Config( Config const& other ); + Config& operator = ( Config const& other ); + virtual void dummy(); + public: + + Config() + {} + + Config( ConfigData const& data ) + : m_data( data ), + m_stream( openStream() ) + { + if( !data.testsOrTags.empty() ) { + TestSpecParser parser( ITagAliasRegistry::get() ); + for( std::size_t i = 0; i < data.testsOrTags.size(); ++i ) + parser.parse( data.testsOrTags[i] ); + m_testSpec = parser.testSpec(); + } + } + + virtual ~Config() {} + + std::string const& getFilename() const { + return m_data.outputFilename ; + } + + bool listTests() const { return m_data.listTests; } + bool listTestNamesOnly() const { return m_data.listTestNamesOnly; } + bool listTags() const { return m_data.listTags; } + bool listReporters() const { return m_data.listReporters; } + + std::string getProcessName() const { return m_data.processName; } + + std::vector<std::string> const& getReporterNames() const { return m_data.reporterNames; } + std::vector<std::string> const& getSectionsToRun() const CATCH_OVERRIDE { return m_data.sectionsToRun; } + + virtual TestSpec const& testSpec() const CATCH_OVERRIDE { return m_testSpec; } + + bool showHelp() const { return m_data.showHelp; } + + // IConfig interface + virtual bool allowThrows() const CATCH_OVERRIDE { return !m_data.noThrow; } + virtual std::ostream& stream() const CATCH_OVERRIDE { return m_stream->stream(); } + virtual std::string name() const CATCH_OVERRIDE { return m_data.name.empty() ? m_data.processName : m_data.name; } + virtual bool includeSuccessfulResults() const CATCH_OVERRIDE { return m_data.showSuccessfulTests; } + virtual bool warnAboutMissingAssertions() const CATCH_OVERRIDE { return m_data.warnings & WarnAbout::NoAssertions; } + virtual ShowDurations::OrNot showDurations() const CATCH_OVERRIDE { return m_data.showDurations; } + virtual RunTests::InWhatOrder runOrder() const CATCH_OVERRIDE { return m_data.runOrder; } + virtual unsigned int rngSeed() const CATCH_OVERRIDE { return m_data.rngSeed; } + virtual UseColour::YesOrNo useColour() const CATCH_OVERRIDE { return m_data.useColour; } + virtual bool shouldDebugBreak() const CATCH_OVERRIDE { return m_data.shouldDebugBreak; } + virtual int abortAfter() const CATCH_OVERRIDE { return m_data.abortAfter; } + virtual bool showInvisibles() const CATCH_OVERRIDE { return m_data.showInvisibles; } + + private: + + IStream const* openStream() { + if( m_data.outputFilename.empty() ) + return new CoutStream(); + else if( m_data.outputFilename[0] == '%' ) { + if( m_data.outputFilename == "%debug" ) + return new DebugOutStream(); + else + throw std::domain_error( "Unrecognised stream: " + m_data.outputFilename ); + } + else + return new FileStream( m_data.outputFilename ); + } + ConfigData m_data; + + CATCH_AUTO_PTR( IStream const ) m_stream; + TestSpec m_testSpec; + }; + +} // end namespace Catch + +// #included from: catch_clara.h +#define TWOBLUECUBES_CATCH_CLARA_H_INCLUDED + +// Use Catch's value for console width (store Clara's off to the side, if present) +#ifdef CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH +#undef CLARA_CONFIG_CONSOLE_WIDTH +#endif +#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH + +// Declare Clara inside the Catch namespace +#define STITCH_CLARA_OPEN_NAMESPACE namespace Catch { +// #included from: ../external/clara.h + +// Version 0.0.2.4 + +// Only use header guard if we are not using an outer namespace +#if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE) + +#ifndef STITCH_CLARA_OPEN_NAMESPACE +#define TWOBLUECUBES_CLARA_H_INCLUDED +#define STITCH_CLARA_OPEN_NAMESPACE +#define STITCH_CLARA_CLOSE_NAMESPACE +#else +#define STITCH_CLARA_CLOSE_NAMESPACE } +#endif + +#define STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE + +// ----------- #included from tbc_text_format.h ----------- + +// Only use header guard if we are not using an outer namespace +#if !defined(TBC_TEXT_FORMAT_H_INCLUDED) || defined(STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE) +#ifndef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +#define TBC_TEXT_FORMAT_H_INCLUDED +#endif + +#include <string> +#include <vector> +#include <sstream> +#include <algorithm> +#include <cctype> + +// Use optional outer namespace +#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +namespace STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE { +#endif + +namespace Tbc { + +#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH + const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + struct TextAttributes { + TextAttributes() + : initialIndent( std::string::npos ), + indent( 0 ), + width( consoleWidth-1 ), + tabChar( '\t' ) + {} + + TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } + TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } + TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } + TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } + + std::size_t initialIndent; // indent of first line, or npos + std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos + std::size_t width; // maximum width of text, including indent. Longer text will wrap + char tabChar; // If this char is seen the indent is changed to current pos + }; + + class Text { + public: + Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) + : attr( _attr ) + { + std::string wrappableChars = " [({.,/|\\-"; + std::size_t indent = _attr.initialIndent != std::string::npos + ? _attr.initialIndent + : _attr.indent; + std::string remainder = _str; + + while( !remainder.empty() ) { + if( lines.size() >= 1000 ) { + lines.push_back( "... message truncated due to excessive size" ); + return; + } + std::size_t tabPos = std::string::npos; + std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); + std::size_t pos = remainder.find_first_of( '\n' ); + if( pos <= width ) { + width = pos; + } + pos = remainder.find_last_of( _attr.tabChar, width ); + if( pos != std::string::npos ) { + tabPos = pos; + if( remainder[width] == '\n' ) + width--; + remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); + } + + if( width == remainder.size() ) { + spliceLine( indent, remainder, width ); + } + else if( remainder[width] == '\n' ) { + spliceLine( indent, remainder, width ); + if( width <= 1 || remainder.size() != 1 ) + remainder = remainder.substr( 1 ); + indent = _attr.indent; + } + else { + pos = remainder.find_last_of( wrappableChars, width ); + if( pos != std::string::npos && pos > 0 ) { + spliceLine( indent, remainder, pos ); + if( remainder[0] == ' ' ) + remainder = remainder.substr( 1 ); + } + else { + spliceLine( indent, remainder, width-1 ); + lines.back() += "-"; + } + if( lines.size() == 1 ) + indent = _attr.indent; + if( tabPos != std::string::npos ) + indent += tabPos; + } + } + } + + void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { + lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); + _remainder = _remainder.substr( _pos ); + } + + typedef std::vector<std::string>::const_iterator const_iterator; + + const_iterator begin() const { return lines.begin(); } + const_iterator end() const { return lines.end(); } + std::string const& last() const { return lines.back(); } + std::size_t size() const { return lines.size(); } + std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } + std::string toString() const { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + + inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { + for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); + it != itEnd; ++it ) { + if( it != _text.begin() ) + _stream << "\n"; + _stream << *it; + } + return _stream; + } + + private: + std::string str; + TextAttributes attr; + std::vector<std::string> lines; + }; + +} // end namespace Tbc + +#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +} // end outer namespace +#endif + +#endif // TBC_TEXT_FORMAT_H_INCLUDED + +// ----------- end of #include from tbc_text_format.h ----------- +// ........... back in clara.h + +#undef STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE + +// ----------- #included from clara_compilers.h ----------- + +#ifndef TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED +#define TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED + +// Detect a number of compiler features - mostly C++11/14 conformance - by compiler +// The following features are defined: +// +// CLARA_CONFIG_CPP11_NULLPTR : is nullptr supported? +// CLARA_CONFIG_CPP11_NOEXCEPT : is noexcept supported? +// CLARA_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods +// CLARA_CONFIG_CPP11_OVERRIDE : is override supported? +// CLARA_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr) + +// CLARA_CONFIG_CPP11_OR_GREATER : Is C++11 supported? + +// CLARA_CONFIG_VARIADIC_MACROS : are variadic macros supported? + +// In general each macro has a _NO_<feature name> form +// (e.g. CLARA_CONFIG_CPP11_NO_NULLPTR) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +// All the C++11 features can be disabled with CLARA_CONFIG_NO_CPP11 + +#ifdef __clang__ + +#if __has_feature(cxx_nullptr) +#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR +#endif + +#if __has_feature(cxx_noexcept) +#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT +#endif + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// GCC +#ifdef __GNUC__ + +#if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) +#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR +#endif + +// - otherwise more recent versions define __cplusplus >= 201103L +// and will get picked up below + +#endif // __GNUC__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#ifdef _MSC_VER + +#if (_MSC_VER >= 1600) +#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR +#define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR +#endif + +#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) +#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT +#define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +#endif + +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// C++ language feature support + +// catch all support for C++11 +#if defined(__cplusplus) && __cplusplus >= 201103L + +#define CLARA_CPP11_OR_GREATER + +#if !defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) +#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR +#endif + +#ifndef CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT +#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT +#endif + +#ifndef CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +#define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +#endif + +#if !defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) +#define CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE +#endif +#if !defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) +#define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR +#endif + +#endif // __cplusplus >= 201103L + +// Now set the actual defines based on the above + anything the user has configured +#if defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NO_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_NULLPTR +#endif +#if defined(CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_NOEXCEPT +#endif +#if defined(CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_GENERATED_METHODS +#endif +#if defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_OVERRIDE) && !defined(CLARA_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_OVERRIDE +#endif +#if defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_UNIQUE_PTR) && !defined(CLARA_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_CPP11) +#define CLARA_CONFIG_CPP11_UNIQUE_PTR +#endif + +// noexcept support: +#if defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_NOEXCEPT) +#define CLARA_NOEXCEPT noexcept +# define CLARA_NOEXCEPT_IS(x) noexcept(x) +#else +#define CLARA_NOEXCEPT throw() +# define CLARA_NOEXCEPT_IS(x) +#endif + +// nullptr support +#ifdef CLARA_CONFIG_CPP11_NULLPTR +#define CLARA_NULL nullptr +#else +#define CLARA_NULL NULL +#endif + +// override support +#ifdef CLARA_CONFIG_CPP11_OVERRIDE +#define CLARA_OVERRIDE override +#else +#define CLARA_OVERRIDE +#endif + +// unique_ptr support +#ifdef CLARA_CONFIG_CPP11_UNIQUE_PTR +# define CLARA_AUTO_PTR( T ) std::unique_ptr<T> +#else +# define CLARA_AUTO_PTR( T ) std::auto_ptr<T> +#endif + +#endif // TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED + +// ----------- end of #include from clara_compilers.h ----------- +// ........... back in clara.h + +#include <map> +#include <stdexcept> +#include <memory> + +#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) +#define CLARA_PLATFORM_WINDOWS +#endif + +// Use optional outer namespace +#ifdef STITCH_CLARA_OPEN_NAMESPACE +STITCH_CLARA_OPEN_NAMESPACE +#endif + +namespace Clara { + + struct UnpositionalTag {}; + + extern UnpositionalTag _; + +#ifdef CLARA_CONFIG_MAIN + UnpositionalTag _; +#endif + + namespace Detail { + +#ifdef CLARA_CONSOLE_WIDTH + const unsigned int consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + using namespace Tbc; + + inline bool startsWith( std::string const& str, std::string const& prefix ) { + return str.size() >= prefix.size() && str.substr( 0, prefix.size() ) == prefix; + } + + template<typename T> struct RemoveConstRef{ typedef T type; }; + template<typename T> struct RemoveConstRef<T&>{ typedef T type; }; + template<typename T> struct RemoveConstRef<T const&>{ typedef T type; }; + template<typename T> struct RemoveConstRef<T const>{ typedef T type; }; + + template<typename T> struct IsBool { static const bool value = false; }; + template<> struct IsBool<bool> { static const bool value = true; }; + + template<typename T> + void convertInto( std::string const& _source, T& _dest ) { + std::stringstream ss; + ss << _source; + ss >> _dest; + if( ss.fail() ) + throw std::runtime_error( "Unable to convert " + _source + " to destination type" ); + } + inline void convertInto( std::string const& _source, std::string& _dest ) { + _dest = _source; + } + char toLowerCh(char c) { + return static_cast<char>( std::tolower( c ) ); + } + inline void convertInto( std::string const& _source, bool& _dest ) { + std::string sourceLC = _source; + std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), toLowerCh ); + if( sourceLC == "y" || sourceLC == "1" || sourceLC == "true" || sourceLC == "yes" || sourceLC == "on" ) + _dest = true; + else if( sourceLC == "n" || sourceLC == "0" || sourceLC == "false" || sourceLC == "no" || sourceLC == "off" ) + _dest = false; + else + throw std::runtime_error( "Expected a boolean value but did not recognise:\n '" + _source + "'" ); + } + + template<typename ConfigT> + struct IArgFunction { + virtual ~IArgFunction() {} +#ifdef CLARA_CONFIG_CPP11_GENERATED_METHODS + IArgFunction() = default; + IArgFunction( IArgFunction const& ) = default; +#endif + virtual void set( ConfigT& config, std::string const& value ) const = 0; + virtual bool takesArg() const = 0; + virtual IArgFunction* clone() const = 0; + }; + + template<typename ConfigT> + class BoundArgFunction { + public: + BoundArgFunction() : functionObj( CLARA_NULL ) {} + BoundArgFunction( IArgFunction<ConfigT>* _functionObj ) : functionObj( _functionObj ) {} + BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : CLARA_NULL ) {} + BoundArgFunction& operator = ( BoundArgFunction const& other ) { + IArgFunction<ConfigT>* newFunctionObj = other.functionObj ? other.functionObj->clone() : CLARA_NULL; + delete functionObj; + functionObj = newFunctionObj; + return *this; + } + ~BoundArgFunction() { delete functionObj; } + + void set( ConfigT& config, std::string const& value ) const { + functionObj->set( config, value ); + } + bool takesArg() const { return functionObj->takesArg(); } + + bool isSet() const { + return functionObj != CLARA_NULL; + } + private: + IArgFunction<ConfigT>* functionObj; + }; + + template<typename C> + struct NullBinder : IArgFunction<C>{ + virtual void set( C&, std::string const& ) const {} + virtual bool takesArg() const { return true; } + virtual IArgFunction<C>* clone() const { return new NullBinder( *this ); } + }; + + template<typename C, typename M> + struct BoundDataMember : IArgFunction<C>{ + BoundDataMember( M C::* _member ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + convertInto( stringValue, p.*member ); + } + virtual bool takesArg() const { return !IsBool<M>::value; } + virtual IArgFunction<C>* clone() const { return new BoundDataMember( *this ); } + M C::* member; + }; + template<typename C, typename M> + struct BoundUnaryMethod : IArgFunction<C>{ + BoundUnaryMethod( void (C::*_member)( M ) ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + typename RemoveConstRef<M>::type value; + convertInto( stringValue, value ); + (p.*member)( value ); + } + virtual bool takesArg() const { return !IsBool<M>::value; } + virtual IArgFunction<C>* clone() const { return new BoundUnaryMethod( *this ); } + void (C::*member)( M ); + }; + template<typename C> + struct BoundNullaryMethod : IArgFunction<C>{ + BoundNullaryMethod( void (C::*_member)() ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + bool value; + convertInto( stringValue, value ); + if( value ) + (p.*member)(); + } + virtual bool takesArg() const { return false; } + virtual IArgFunction<C>* clone() const { return new BoundNullaryMethod( *this ); } + void (C::*member)(); + }; + + template<typename C> + struct BoundUnaryFunction : IArgFunction<C>{ + BoundUnaryFunction( void (*_function)( C& ) ) : function( _function ) {} + virtual void set( C& obj, std::string const& stringValue ) const { + bool value; + convertInto( stringValue, value ); + if( value ) + function( obj ); + } + virtual bool takesArg() const { return false; } + virtual IArgFunction<C>* clone() const { return new BoundUnaryFunction( *this ); } + void (*function)( C& ); + }; + + template<typename C, typename T> + struct BoundBinaryFunction : IArgFunction<C>{ + BoundBinaryFunction( void (*_function)( C&, T ) ) : function( _function ) {} + virtual void set( C& obj, std::string const& stringValue ) const { + typename RemoveConstRef<T>::type value; + convertInto( stringValue, value ); + function( obj, value ); + } + virtual bool takesArg() const { return !IsBool<T>::value; } + virtual IArgFunction<C>* clone() const { return new BoundBinaryFunction( *this ); } + void (*function)( C&, T ); + }; + + } // namespace Detail + + inline std::vector<std::string> argsToVector( int argc, char const* const* const argv ) { + std::vector<std::string> args( static_cast<std::size_t>( argc ) ); + for( std::size_t i = 0; i < static_cast<std::size_t>( argc ); ++i ) + args[i] = argv[i]; + + return args; + } + + class Parser { + enum Mode { None, MaybeShortOpt, SlashOpt, ShortOpt, LongOpt, Positional }; + Mode mode; + std::size_t from; + bool inQuotes; + public: + + struct Token { + enum Type { Positional, ShortOpt, LongOpt }; + Token( Type _type, std::string const& _data ) : type( _type ), data( _data ) {} + Type type; + std::string data; + }; + + Parser() : mode( None ), from( 0 ), inQuotes( false ){} + + void parseIntoTokens( std::vector<std::string> const& args, std::vector<Token>& tokens ) { + const std::string doubleDash = "--"; + for( std::size_t i = 1; i < args.size() && args[i] != doubleDash; ++i ) + parseIntoTokens( args[i], tokens); + } + + void parseIntoTokens( std::string const& arg, std::vector<Token>& tokens ) { + for( std::size_t i = 0; i < arg.size(); ++i ) { + char c = arg[i]; + if( c == '"' ) + inQuotes = !inQuotes; + mode = handleMode( i, c, arg, tokens ); + } + mode = handleMode( arg.size(), '\0', arg, tokens ); + } + Mode handleMode( std::size_t i, char c, std::string const& arg, std::vector<Token>& tokens ) { + switch( mode ) { + case None: return handleNone( i, c ); + case MaybeShortOpt: return handleMaybeShortOpt( i, c ); + case ShortOpt: + case LongOpt: + case SlashOpt: return handleOpt( i, c, arg, tokens ); + case Positional: return handlePositional( i, c, arg, tokens ); + default: throw std::logic_error( "Unknown mode" ); + } + } + + Mode handleNone( std::size_t i, char c ) { + if( inQuotes ) { + from = i; + return Positional; + } + switch( c ) { + case '-': return MaybeShortOpt; +#ifdef CLARA_PLATFORM_WINDOWS + case '/': from = i+1; return SlashOpt; +#endif + default: from = i; return Positional; + } + } + Mode handleMaybeShortOpt( std::size_t i, char c ) { + switch( c ) { + case '-': from = i+1; return LongOpt; + default: from = i; return ShortOpt; + } + } + + Mode handleOpt( std::size_t i, char c, std::string const& arg, std::vector<Token>& tokens ) { + if( std::string( ":=\0", 3 ).find( c ) == std::string::npos ) + return mode; + + std::string optName = arg.substr( from, i-from ); + if( mode == ShortOpt ) + for( std::size_t j = 0; j < optName.size(); ++j ) + tokens.push_back( Token( Token::ShortOpt, optName.substr( j, 1 ) ) ); + else if( mode == SlashOpt && optName.size() == 1 ) + tokens.push_back( Token( Token::ShortOpt, optName ) ); + else + tokens.push_back( Token( Token::LongOpt, optName ) ); + return None; + } + Mode handlePositional( std::size_t i, char c, std::string const& arg, std::vector<Token>& tokens ) { + if( inQuotes || std::string( "\0", 1 ).find( c ) == std::string::npos ) + return mode; + + std::string data = arg.substr( from, i-from ); + tokens.push_back( Token( Token::Positional, data ) ); + return None; + } + }; + + template<typename ConfigT> + struct CommonArgProperties { + CommonArgProperties() {} + CommonArgProperties( Detail::BoundArgFunction<ConfigT> const& _boundField ) : boundField( _boundField ) {} + + Detail::BoundArgFunction<ConfigT> boundField; + std::string description; + std::string detail; + std::string placeholder; // Only value if boundField takes an arg + + bool takesArg() const { + return !placeholder.empty(); + } + void validate() const { + if( !boundField.isSet() ) + throw std::logic_error( "option not bound" ); + } + }; + struct OptionArgProperties { + std::vector<std::string> shortNames; + std::string longName; + + bool hasShortName( std::string const& shortName ) const { + return std::find( shortNames.begin(), shortNames.end(), shortName ) != shortNames.end(); + } + bool hasLongName( std::string const& _longName ) const { + return _longName == longName; + } + }; + struct PositionalArgProperties { + PositionalArgProperties() : position( -1 ) {} + int position; // -1 means non-positional (floating) + + bool isFixedPositional() const { + return position != -1; + } + }; + + template<typename ConfigT> + class CommandLine { + + struct Arg : CommonArgProperties<ConfigT>, OptionArgProperties, PositionalArgProperties { + Arg() {} + Arg( Detail::BoundArgFunction<ConfigT> const& _boundField ) : CommonArgProperties<ConfigT>( _boundField ) {} + + using CommonArgProperties<ConfigT>::placeholder; // !TBD + + std::string dbgName() const { + if( !longName.empty() ) + return "--" + longName; + if( !shortNames.empty() ) + return "-" + shortNames[0]; + return "positional args"; + } + std::string commands() const { + std::ostringstream oss; + bool first = true; + std::vector<std::string>::const_iterator it = shortNames.begin(), itEnd = shortNames.end(); + for(; it != itEnd; ++it ) { + if( first ) + first = false; + else + oss << ", "; + oss << "-" << *it; + } + if( !longName.empty() ) { + if( !first ) + oss << ", "; + oss << "--" << longName; + } + if( !placeholder.empty() ) + oss << " <" << placeholder << ">"; + return oss.str(); + } + }; + + typedef CLARA_AUTO_PTR( Arg ) ArgAutoPtr; + + friend void addOptName( Arg& arg, std::string const& optName ) + { + if( optName.empty() ) + return; + if( Detail::startsWith( optName, "--" ) ) { + if( !arg.longName.empty() ) + throw std::logic_error( "Only one long opt may be specified. '" + + arg.longName + + "' already specified, now attempting to add '" + + optName + "'" ); + arg.longName = optName.substr( 2 ); + } + else if( Detail::startsWith( optName, "-" ) ) + arg.shortNames.push_back( optName.substr( 1 ) ); + else + throw std::logic_error( "option must begin with - or --. Option was: '" + optName + "'" ); + } + friend void setPositionalArg( Arg& arg, int position ) + { + arg.position = position; + } + + class ArgBuilder { + public: + ArgBuilder( Arg* arg ) : m_arg( arg ) {} + + // Bind a non-boolean data member (requires placeholder string) + template<typename C, typename M> + void bind( M C::* field, std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundDataMember<C,M>( field ); + m_arg->placeholder = placeholder; + } + // Bind a boolean data member (no placeholder required) + template<typename C> + void bind( bool C::* field ) { + m_arg->boundField = new Detail::BoundDataMember<C,bool>( field ); + } + + // Bind a method taking a single, non-boolean argument (requires a placeholder string) + template<typename C, typename M> + void bind( void (C::* unaryMethod)( M ), std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundUnaryMethod<C,M>( unaryMethod ); + m_arg->placeholder = placeholder; + } + + // Bind a method taking a single, boolean argument (no placeholder string required) + template<typename C> + void bind( void (C::* unaryMethod)( bool ) ) { + m_arg->boundField = new Detail::BoundUnaryMethod<C,bool>( unaryMethod ); + } + + // Bind a method that takes no arguments (will be called if opt is present) + template<typename C> + void bind( void (C::* nullaryMethod)() ) { + m_arg->boundField = new Detail::BoundNullaryMethod<C>( nullaryMethod ); + } + + // Bind a free function taking a single argument - the object to operate on (no placeholder string required) + template<typename C> + void bind( void (* unaryFunction)( C& ) ) { + m_arg->boundField = new Detail::BoundUnaryFunction<C>( unaryFunction ); + } + + // Bind a free function taking a single argument - the object to operate on (requires a placeholder string) + template<typename C, typename T> + void bind( void (* binaryFunction)( C&, T ), std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundBinaryFunction<C, T>( binaryFunction ); + m_arg->placeholder = placeholder; + } + + ArgBuilder& describe( std::string const& description ) { + m_arg->description = description; + return *this; + } + ArgBuilder& detail( std::string const& detail ) { + m_arg->detail = detail; + return *this; + } + + protected: + Arg* m_arg; + }; + + class OptBuilder : public ArgBuilder { + public: + OptBuilder( Arg* arg ) : ArgBuilder( arg ) {} + OptBuilder( OptBuilder& other ) : ArgBuilder( other ) {} + + OptBuilder& operator[]( std::string const& optName ) { + addOptName( *ArgBuilder::m_arg, optName ); + return *this; + } + }; + + public: + + CommandLine() + : m_boundProcessName( new Detail::NullBinder<ConfigT>() ), + m_highestSpecifiedArgPosition( 0 ), + m_throwOnUnrecognisedTokens( false ) + {} + CommandLine( CommandLine const& other ) + : m_boundProcessName( other.m_boundProcessName ), + m_options ( other.m_options ), + m_positionalArgs( other.m_positionalArgs ), + m_highestSpecifiedArgPosition( other.m_highestSpecifiedArgPosition ), + m_throwOnUnrecognisedTokens( other.m_throwOnUnrecognisedTokens ) + { + if( other.m_floatingArg.get() ) + m_floatingArg.reset( new Arg( *other.m_floatingArg ) ); + } + + CommandLine& setThrowOnUnrecognisedTokens( bool shouldThrow = true ) { + m_throwOnUnrecognisedTokens = shouldThrow; + return *this; + } + + OptBuilder operator[]( std::string const& optName ) { + m_options.push_back( Arg() ); + addOptName( m_options.back(), optName ); + OptBuilder builder( &m_options.back() ); + return builder; + } + + ArgBuilder operator[]( int position ) { + m_positionalArgs.insert( std::make_pair( position, Arg() ) ); + if( position > m_highestSpecifiedArgPosition ) + m_highestSpecifiedArgPosition = position; + setPositionalArg( m_positionalArgs[position], position ); + ArgBuilder builder( &m_positionalArgs[position] ); + return builder; + } + + // Invoke this with the _ instance + ArgBuilder operator[]( UnpositionalTag ) { + if( m_floatingArg.get() ) + throw std::logic_error( "Only one unpositional argument can be added" ); + m_floatingArg.reset( new Arg() ); + ArgBuilder builder( m_floatingArg.get() ); + return builder; + } + + template<typename C, typename M> + void bindProcessName( M C::* field ) { + m_boundProcessName = new Detail::BoundDataMember<C,M>( field ); + } + template<typename C, typename M> + void bindProcessName( void (C::*_unaryMethod)( M ) ) { + m_boundProcessName = new Detail::BoundUnaryMethod<C,M>( _unaryMethod ); + } + + void optUsage( std::ostream& os, std::size_t indent = 0, std::size_t width = Detail::consoleWidth ) const { + typename std::vector<Arg>::const_iterator itBegin = m_options.begin(), itEnd = m_options.end(), it; + std::size_t maxWidth = 0; + for( it = itBegin; it != itEnd; ++it ) + maxWidth = (std::max)( maxWidth, it->commands().size() ); + + for( it = itBegin; it != itEnd; ++it ) { + Detail::Text usage( it->commands(), Detail::TextAttributes() + .setWidth( maxWidth+indent ) + .setIndent( indent ) ); + Detail::Text desc( it->description, Detail::TextAttributes() + .setWidth( width - maxWidth - 3 ) ); + + for( std::size_t i = 0; i < (std::max)( usage.size(), desc.size() ); ++i ) { + std::string usageCol = i < usage.size() ? usage[i] : ""; + os << usageCol; + + if( i < desc.size() && !desc[i].empty() ) + os << std::string( indent + 2 + maxWidth - usageCol.size(), ' ' ) + << desc[i]; + os << "\n"; + } + } + } + std::string optUsage() const { + std::ostringstream oss; + optUsage( oss ); + return oss.str(); + } + + void argSynopsis( std::ostream& os ) const { + for( int i = 1; i <= m_highestSpecifiedArgPosition; ++i ) { + if( i > 1 ) + os << " "; + typename std::map<int, Arg>::const_iterator it = m_positionalArgs.find( i ); + if( it != m_positionalArgs.end() ) + os << "<" << it->second.placeholder << ">"; + else if( m_floatingArg.get() ) + os << "<" << m_floatingArg->placeholder << ">"; + else + throw std::logic_error( "non consecutive positional arguments with no floating args" ); + } + // !TBD No indication of mandatory args + if( m_floatingArg.get() ) { + if( m_highestSpecifiedArgPosition > 1 ) + os << " "; + os << "[<" << m_floatingArg->placeholder << "> ...]"; + } + } + std::string argSynopsis() const { + std::ostringstream oss; + argSynopsis( oss ); + return oss.str(); + } + + void usage( std::ostream& os, std::string const& procName ) const { + validate(); + os << "usage:\n " << procName << " "; + argSynopsis( os ); + if( !m_options.empty() ) { + os << " [options]\n\nwhere options are: \n"; + optUsage( os, 2 ); + } + os << "\n"; + } + std::string usage( std::string const& procName ) const { + std::ostringstream oss; + usage( oss, procName ); + return oss.str(); + } + + ConfigT parse( std::vector<std::string> const& args ) const { + ConfigT config; + parseInto( args, config ); + return config; + } + + std::vector<Parser::Token> parseInto( std::vector<std::string> const& args, ConfigT& config ) const { + std::string processName = args.empty() ? std::string() : args[0]; + std::size_t lastSlash = processName.find_last_of( "/\\" ); + if( lastSlash != std::string::npos ) + processName = processName.substr( lastSlash+1 ); + m_boundProcessName.set( config, processName ); + std::vector<Parser::Token> tokens; + Parser parser; + parser.parseIntoTokens( args, tokens ); + return populate( tokens, config ); + } + + std::vector<Parser::Token> populate( std::vector<Parser::Token> const& tokens, ConfigT& config ) const { + validate(); + std::vector<Parser::Token> unusedTokens = populateOptions( tokens, config ); + unusedTokens = populateFixedArgs( unusedTokens, config ); + unusedTokens = populateFloatingArgs( unusedTokens, config ); + return unusedTokens; + } + + std::vector<Parser::Token> populateOptions( std::vector<Parser::Token> const& tokens, ConfigT& config ) const { + std::vector<Parser::Token> unusedTokens; + std::vector<std::string> errors; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + typename std::vector<Arg>::const_iterator it = m_options.begin(), itEnd = m_options.end(); + for(; it != itEnd; ++it ) { + Arg const& arg = *it; + + try { + if( ( token.type == Parser::Token::ShortOpt && arg.hasShortName( token.data ) ) || + ( token.type == Parser::Token::LongOpt && arg.hasLongName( token.data ) ) ) { + if( arg.takesArg() ) { + if( i == tokens.size()-1 || tokens[i+1].type != Parser::Token::Positional ) + errors.push_back( "Expected argument to option: " + token.data ); + else + arg.boundField.set( config, tokens[++i].data ); + } + else { + arg.boundField.set( config, "true" ); + } + break; + } + } + catch( std::exception& ex ) { + errors.push_back( std::string( ex.what() ) + "\n- while parsing: (" + arg.commands() + ")" ); + } + } + if( it == itEnd ) { + if( token.type == Parser::Token::Positional || !m_throwOnUnrecognisedTokens ) + unusedTokens.push_back( token ); + else if( errors.empty() && m_throwOnUnrecognisedTokens ) + errors.push_back( "unrecognised option: " + token.data ); + } + } + if( !errors.empty() ) { + std::ostringstream oss; + for( std::vector<std::string>::const_iterator it = errors.begin(), itEnd = errors.end(); + it != itEnd; + ++it ) { + if( it != errors.begin() ) + oss << "\n"; + oss << *it; + } + throw std::runtime_error( oss.str() ); + } + return unusedTokens; + } + std::vector<Parser::Token> populateFixedArgs( std::vector<Parser::Token> const& tokens, ConfigT& config ) const { + std::vector<Parser::Token> unusedTokens; + int position = 1; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + typename std::map<int, Arg>::const_iterator it = m_positionalArgs.find( position ); + if( it != m_positionalArgs.end() ) + it->second.boundField.set( config, token.data ); + else + unusedTokens.push_back( token ); + if( token.type == Parser::Token::Positional ) + position++; + } + return unusedTokens; + } + std::vector<Parser::Token> populateFloatingArgs( std::vector<Parser::Token> const& tokens, ConfigT& config ) const { + if( !m_floatingArg.get() ) + return tokens; + std::vector<Parser::Token> unusedTokens; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + if( token.type == Parser::Token::Positional ) + m_floatingArg->boundField.set( config, token.data ); + else + unusedTokens.push_back( token ); + } + return unusedTokens; + } + + void validate() const + { + if( m_options.empty() && m_positionalArgs.empty() && !m_floatingArg.get() ) + throw std::logic_error( "No options or arguments specified" ); + + for( typename std::vector<Arg>::const_iterator it = m_options.begin(), + itEnd = m_options.end(); + it != itEnd; ++it ) + it->validate(); + } + + private: + Detail::BoundArgFunction<ConfigT> m_boundProcessName; + std::vector<Arg> m_options; + std::map<int, Arg> m_positionalArgs; + ArgAutoPtr m_floatingArg; + int m_highestSpecifiedArgPosition; + bool m_throwOnUnrecognisedTokens; + }; + +} // end namespace Clara + +STITCH_CLARA_CLOSE_NAMESPACE +#undef STITCH_CLARA_OPEN_NAMESPACE +#undef STITCH_CLARA_CLOSE_NAMESPACE + +#endif // TWOBLUECUBES_CLARA_H_INCLUDED +#undef STITCH_CLARA_OPEN_NAMESPACE + +// Restore Clara's value for console width, if present +#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +#include <fstream> +#include <ctime> + +namespace Catch { + + inline void abortAfterFirst( ConfigData& config ) { config.abortAfter = 1; } + inline void abortAfterX( ConfigData& config, int x ) { + if( x < 1 ) + throw std::runtime_error( "Value after -x or --abortAfter must be greater than zero" ); + config.abortAfter = x; + } + inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); } + inline void addSectionToRun( ConfigData& config, std::string const& sectionName ) { config.sectionsToRun.push_back( sectionName ); } + inline void addReporterName( ConfigData& config, std::string const& _reporterName ) { config.reporterNames.push_back( _reporterName ); } + + inline void addWarning( ConfigData& config, std::string const& _warning ) { + if( _warning == "NoAssertions" ) + config.warnings = static_cast<WarnAbout::What>( config.warnings | WarnAbout::NoAssertions ); + else + throw std::runtime_error( "Unrecognised warning: '" + _warning + '\'' ); + } + inline void setOrder( ConfigData& config, std::string const& order ) { + if( startsWith( "declared", order ) ) + config.runOrder = RunTests::InDeclarationOrder; + else if( startsWith( "lexical", order ) ) + config.runOrder = RunTests::InLexicographicalOrder; + else if( startsWith( "random", order ) ) + config.runOrder = RunTests::InRandomOrder; + else + throw std::runtime_error( "Unrecognised ordering: '" + order + '\'' ); + } + inline void setRngSeed( ConfigData& config, std::string const& seed ) { + if( seed == "time" ) { + config.rngSeed = static_cast<unsigned int>( std::time(0) ); + } + else { + std::stringstream ss; + ss << seed; + ss >> config.rngSeed; + if( ss.fail() ) + throw std::runtime_error( "Argument to --rng-seed should be the word 'time' or a number" ); + } + } + inline void setVerbosity( ConfigData& config, int level ) { + // !TBD: accept strings? + config.verbosity = static_cast<Verbosity::Level>( level ); + } + inline void setShowDurations( ConfigData& config, bool _showDurations ) { + config.showDurations = _showDurations + ? ShowDurations::Always + : ShowDurations::Never; + } + inline void setUseColour( ConfigData& config, std::string const& value ) { + std::string mode = toLower( value ); + + if( mode == "yes" ) + config.useColour = UseColour::Yes; + else if( mode == "no" ) + config.useColour = UseColour::No; + else if( mode == "auto" ) + config.useColour = UseColour::Auto; + else + throw std::runtime_error( "colour mode must be one of: auto, yes or no" ); + } + inline void forceColour( ConfigData& config ) { + config.useColour = UseColour::Yes; + } + inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) { + std::ifstream f( _filename.c_str() ); + if( !f.is_open() ) + throw std::domain_error( "Unable to load input file: " + _filename ); + + std::string line; + while( std::getline( f, line ) ) { + line = trim(line); + if( !line.empty() && !startsWith( line, '#' ) ) { + if( !startsWith( line, '"' ) ) + line = '"' + line + '"'; + addTestOrTags( config, line + ',' ); + } + } + } + + inline Clara::CommandLine<ConfigData> makeCommandLineParser() { + + using namespace Clara; + CommandLine<ConfigData> cli; + + cli.bindProcessName( &ConfigData::processName ); + + cli["-?"]["-h"]["--help"] + .describe( "display usage information" ) + .bind( &ConfigData::showHelp ); + + cli["-l"]["--list-tests"] + .describe( "list all/matching test cases" ) + .bind( &ConfigData::listTests ); + + cli["-t"]["--list-tags"] + .describe( "list all/matching tags" ) + .bind( &ConfigData::listTags ); + + cli["-s"]["--success"] + .describe( "include successful tests in output" ) + .bind( &ConfigData::showSuccessfulTests ); + + cli["-b"]["--break"] + .describe( "break into debugger on failure" ) + .bind( &ConfigData::shouldDebugBreak ); + + cli["-e"]["--nothrow"] + .describe( "skip exception tests" ) + .bind( &ConfigData::noThrow ); + + cli["-i"]["--invisibles"] + .describe( "show invisibles (tabs, newlines)" ) + .bind( &ConfigData::showInvisibles ); + + cli["-o"]["--out"] + .describe( "output filename" ) + .bind( &ConfigData::outputFilename, "filename" ); + + cli["-r"]["--reporter"] +// .placeholder( "name[:filename]" ) + .describe( "reporter to use (defaults to console)" ) + .bind( &addReporterName, "name" ); + + cli["-n"]["--name"] + .describe( "suite name" ) + .bind( &ConfigData::name, "name" ); + + cli["-a"]["--abort"] + .describe( "abort at first failure" ) + .bind( &abortAfterFirst ); + + cli["-x"]["--abortx"] + .describe( "abort after x failures" ) + .bind( &abortAfterX, "no. failures" ); + + cli["-w"]["--warn"] + .describe( "enable warnings" ) + .bind( &addWarning, "warning name" ); + +// - needs updating if reinstated +// cli.into( &setVerbosity ) +// .describe( "level of verbosity (0=no output)" ) +// .shortOpt( "v") +// .longOpt( "verbosity" ) +// .placeholder( "level" ); + + cli[_] + .describe( "which test or tests to use" ) + .bind( &addTestOrTags, "test name, pattern or tags" ); + + cli["-d"]["--durations"] + .describe( "show test durations" ) + .bind( &setShowDurations, "yes|no" ); + + cli["-f"]["--input-file"] + .describe( "load test names to run from a file" ) + .bind( &loadTestNamesFromFile, "filename" ); + + cli["-#"]["--filenames-as-tags"] + .describe( "adds a tag for the filename" ) + .bind( &ConfigData::filenamesAsTags ); + + cli["-c"]["--section"] + .describe( "specify section to run" ) + .bind( &addSectionToRun, "section name" ); + + // Less common commands which don't have a short form + cli["--list-test-names-only"] + .describe( "list all/matching test cases names only" ) + .bind( &ConfigData::listTestNamesOnly ); + + cli["--list-reporters"] + .describe( "list all reporters" ) + .bind( &ConfigData::listReporters ); + + cli["--order"] + .describe( "test case order (defaults to decl)" ) + .bind( &setOrder, "decl|lex|rand" ); + + cli["--rng-seed"] + .describe( "set a specific seed for random numbers" ) + .bind( &setRngSeed, "'time'|number" ); + + cli["--force-colour"] + .describe( "force colourised output (deprecated)" ) + .bind( &forceColour ); + + cli["--use-colour"] + .describe( "should output be colourised" ) + .bind( &setUseColour, "yes|no" ); + + return cli; + } + +} // end namespace Catch + +// #included from: internal/catch_list.hpp +#define TWOBLUECUBES_CATCH_LIST_HPP_INCLUDED + +// #included from: catch_text.h +#define TWOBLUECUBES_CATCH_TEXT_H_INCLUDED + +#define TBC_TEXT_FORMAT_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH + +#define CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE Catch +// #included from: ../external/tbc_text_format.h +// Only use header guard if we are not using an outer namespace +#ifndef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +# ifdef TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED +# ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +# define TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +# endif +# else +# define TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED +# endif +#endif +#ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +#include <string> +#include <vector> +#include <sstream> + +// Use optional outer namespace +#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +namespace CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE { +#endif + +namespace Tbc { + +#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH + const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + struct TextAttributes { + TextAttributes() + : initialIndent( std::string::npos ), + indent( 0 ), + width( consoleWidth-1 ) + {} + + TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } + TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } + TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } + + std::size_t initialIndent; // indent of first line, or npos + std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos + std::size_t width; // maximum width of text, including indent. Longer text will wrap + }; + + class Text { + public: + Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) + : attr( _attr ) + { + const std::string wrappableBeforeChars = "[({<\t"; + const std::string wrappableAfterChars = "])}>-,./|\\"; + const std::string wrappableInsteadOfChars = " \n\r"; + std::string indent = _attr.initialIndent != std::string::npos + ? std::string( _attr.initialIndent, ' ' ) + : std::string( _attr.indent, ' ' ); + + typedef std::string::const_iterator iterator; + iterator it = _str.begin(); + const iterator strEnd = _str.end(); + + while( it != strEnd ) { + + if( lines.size() >= 1000 ) { + lines.push_back( "... message truncated due to excessive size" ); + return; + } + + std::string suffix; + std::size_t width = (std::min)( static_cast<size_t>( strEnd-it ), _attr.width-static_cast<size_t>( indent.size() ) ); + iterator itEnd = it+width; + iterator itNext = _str.end(); + + iterator itNewLine = std::find( it, itEnd, '\n' ); + if( itNewLine != itEnd ) + itEnd = itNewLine; + + if( itEnd != strEnd ) { + bool foundWrapPoint = false; + iterator findIt = itEnd; + do { + if( wrappableAfterChars.find( *findIt ) != std::string::npos && findIt != itEnd ) { + itEnd = findIt+1; + itNext = findIt+1; + foundWrapPoint = true; + } + else if( findIt > it && wrappableBeforeChars.find( *findIt ) != std::string::npos ) { + itEnd = findIt; + itNext = findIt; + foundWrapPoint = true; + } + else if( wrappableInsteadOfChars.find( *findIt ) != std::string::npos ) { + itNext = findIt+1; + itEnd = findIt; + foundWrapPoint = true; + } + if( findIt == it ) + break; + else + --findIt; + } + while( !foundWrapPoint ); + + if( !foundWrapPoint ) { + // No good wrap char, so we'll break mid word and add a hyphen + --itEnd; + itNext = itEnd; + suffix = "-"; + } + else { + while( itEnd > it && wrappableInsteadOfChars.find( *(itEnd-1) ) != std::string::npos ) + --itEnd; + } + } + lines.push_back( indent + std::string( it, itEnd ) + suffix ); + + if( indent.size() != _attr.indent ) + indent = std::string( _attr.indent, ' ' ); + it = itNext; + } + } + + typedef std::vector<std::string>::const_iterator const_iterator; + + const_iterator begin() const { return lines.begin(); } + const_iterator end() const { return lines.end(); } + std::string const& last() const { return lines.back(); } + std::size_t size() const { return lines.size(); } + std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } + std::string toString() const { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + + inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { + for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); + it != itEnd; ++it ) { + if( it != _text.begin() ) + _stream << "\n"; + _stream << *it; + } + return _stream; + } + + private: + std::string str; + TextAttributes attr; + std::vector<std::string> lines; + }; + +} // end namespace Tbc + +#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +} // end outer namespace +#endif + +#endif // TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +#undef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE + +namespace Catch { + using Tbc::Text; + using Tbc::TextAttributes; +} + +// #included from: catch_console_colour.hpp +#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_HPP_INCLUDED + +namespace Catch { + + struct Colour { + enum Code { + None = 0, + + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White, + + // By intention + FileName = LightGrey, + Warning = Yellow, + ResultError = BrightRed, + ResultSuccess = BrightGreen, + ResultExpectedFailure = Warning, + + Error = BrightRed, + Success = Green, + + OriginalExpression = Cyan, + ReconstructedExpression = Yellow, + + SecondaryText = LightGrey, + Headers = White + }; + + // Use constructed object for RAII guard + Colour( Code _colourCode ); + Colour( Colour const& other ); + ~Colour(); + + // Use static method for one-shot changes + static void use( Code _colourCode ); + + private: + bool m_moved; + }; + + inline std::ostream& operator << ( std::ostream& os, Colour const& ) { return os; } + +} // end namespace Catch + +// #included from: catch_interfaces_reporter.h +#define TWOBLUECUBES_CATCH_INTERFACES_REPORTER_H_INCLUDED + +#include <string> +#include <ostream> +#include <map> + +namespace Catch +{ + struct ReporterConfig { + explicit ReporterConfig( Ptr<IConfig const> const& _fullConfig ) + : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} + + ReporterConfig( Ptr<IConfig const> const& _fullConfig, std::ostream& _stream ) + : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} + + std::ostream& stream() const { return *m_stream; } + Ptr<IConfig const> fullConfig() const { return m_fullConfig; } + + private: + std::ostream* m_stream; + Ptr<IConfig const> m_fullConfig; + }; + + struct ReporterPreferences { + ReporterPreferences() + : shouldRedirectStdOut( false ) + {} + + bool shouldRedirectStdOut; + }; + + template<typename T> + struct LazyStat : Option<T> { + LazyStat() : used( false ) {} + LazyStat& operator=( T const& _value ) { + Option<T>::operator=( _value ); + used = false; + return *this; + } + void reset() { + Option<T>::reset(); + used = false; + } + bool used; + }; + + struct TestRunInfo { + TestRunInfo( std::string const& _name ) : name( _name ) {} + std::string name; + }; + struct GroupInfo { + GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ) + : name( _name ), + groupIndex( _groupIndex ), + groupsCounts( _groupsCount ) + {} + + std::string name; + std::size_t groupIndex; + std::size_t groupsCounts; + }; + + struct AssertionStats { + AssertionStats( AssertionResult const& _assertionResult, + std::vector<MessageInfo> const& _infoMessages, + Totals const& _totals ) + : assertionResult( _assertionResult ), + infoMessages( _infoMessages ), + totals( _totals ) + { + if( assertionResult.hasMessage() ) { + // Copy message into messages list. + // !TBD This should have been done earlier, somewhere + MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); + builder << assertionResult.getMessage(); + builder.m_info.message = builder.m_stream.str(); + + infoMessages.push_back( builder.m_info ); + } + } + virtual ~AssertionStats(); + +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + AssertionStats( AssertionStats const& ) = default; + AssertionStats( AssertionStats && ) = default; + AssertionStats& operator = ( AssertionStats const& ) = default; + AssertionStats& operator = ( AssertionStats && ) = default; +# endif + + AssertionResult assertionResult; + std::vector<MessageInfo> infoMessages; + Totals totals; + }; + + struct SectionStats { + SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ) + : sectionInfo( _sectionInfo ), + assertions( _assertions ), + durationInSeconds( _durationInSeconds ), + missingAssertions( _missingAssertions ) + {} + virtual ~SectionStats(); +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + SectionStats( SectionStats const& ) = default; + SectionStats( SectionStats && ) = default; + SectionStats& operator = ( SectionStats const& ) = default; + SectionStats& operator = ( SectionStats && ) = default; +# endif + + SectionInfo sectionInfo; + Counts assertions; + double durationInSeconds; + bool missingAssertions; + }; + + struct TestCaseStats { + TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ) + : testInfo( _testInfo ), + totals( _totals ), + stdOut( _stdOut ), + stdErr( _stdErr ), + aborting( _aborting ) + {} + virtual ~TestCaseStats(); + +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + TestCaseStats( TestCaseStats const& ) = default; + TestCaseStats( TestCaseStats && ) = default; + TestCaseStats& operator = ( TestCaseStats const& ) = default; + TestCaseStats& operator = ( TestCaseStats && ) = default; +# endif + + TestCaseInfo testInfo; + Totals totals; + std::string stdOut; + std::string stdErr; + bool aborting; + }; + + struct TestGroupStats { + TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ) + : groupInfo( _groupInfo ), + totals( _totals ), + aborting( _aborting ) + {} + TestGroupStats( GroupInfo const& _groupInfo ) + : groupInfo( _groupInfo ), + aborting( false ) + {} + virtual ~TestGroupStats(); + +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + TestGroupStats( TestGroupStats const& ) = default; + TestGroupStats( TestGroupStats && ) = default; + TestGroupStats& operator = ( TestGroupStats const& ) = default; + TestGroupStats& operator = ( TestGroupStats && ) = default; +# endif + + GroupInfo groupInfo; + Totals totals; + bool aborting; + }; + + struct TestRunStats { + TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ) + : runInfo( _runInfo ), + totals( _totals ), + aborting( _aborting ) + {} + virtual ~TestRunStats(); + +# ifndef CATCH_CONFIG_CPP11_GENERATED_METHODS + TestRunStats( TestRunStats const& _other ) + : runInfo( _other.runInfo ), + totals( _other.totals ), + aborting( _other.aborting ) + {} +# else + TestRunStats( TestRunStats const& ) = default; + TestRunStats( TestRunStats && ) = default; + TestRunStats& operator = ( TestRunStats const& ) = default; + TestRunStats& operator = ( TestRunStats && ) = default; +# endif + + TestRunInfo runInfo; + Totals totals; + bool aborting; + }; + + class MultipleReporters; + + struct IStreamingReporter : IShared { + virtual ~IStreamingReporter(); + + // Implementing class must also provide the following static method: + // static std::string getDescription(); + + virtual ReporterPreferences getPreferences() const = 0; + + virtual void noMatchingTestCases( std::string const& spec ) = 0; + + virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; + virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; + virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; + + virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; + + // The return value indicates if the messages buffer should be cleared: + virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; + + virtual void sectionEnded( SectionStats const& sectionStats ) = 0; + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; + virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; + + virtual void skipTest( TestCaseInfo const& testInfo ) = 0; + + virtual MultipleReporters* tryAsMulti() { return CATCH_NULL; } + }; + + struct IReporterFactory : IShared { + virtual ~IReporterFactory(); + virtual IStreamingReporter* create( ReporterConfig const& config ) const = 0; + virtual std::string getDescription() const = 0; + }; + + struct IReporterRegistry { + typedef std::map<std::string, Ptr<IReporterFactory> > FactoryMap; + typedef std::vector<Ptr<IReporterFactory> > Listeners; + + virtual ~IReporterRegistry(); + virtual IStreamingReporter* create( std::string const& name, Ptr<IConfig const> const& config ) const = 0; + virtual FactoryMap const& getFactories() const = 0; + virtual Listeners const& getListeners() const = 0; + }; + + Ptr<IStreamingReporter> addReporter( Ptr<IStreamingReporter> const& existingReporter, Ptr<IStreamingReporter> const& additionalReporter ); + +} + +#include <limits> +#include <algorithm> + +namespace Catch { + + inline std::size_t listTests( Config const& config ) { + + TestSpec testSpec = config.testSpec(); + if( config.testSpec().hasFilters() ) + Catch::cout() << "Matching test cases:\n"; + else { + Catch::cout() << "All available test cases:\n"; + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + } + + std::size_t matchedTests = 0; + TextAttributes nameAttr, tagsAttr; + nameAttr.setInitialIndent( 2 ).setIndent( 4 ); + tagsAttr.setIndent( 6 ); + + std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( std::vector<TestCase>::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + matchedTests++; + TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); + Colour::Code colour = testCaseInfo.isHidden() + ? Colour::SecondaryText + : Colour::None; + Colour colourGuard( colour ); + + Catch::cout() << Text( testCaseInfo.name, nameAttr ) << std::endl; + if( !testCaseInfo.tags.empty() ) + Catch::cout() << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl; + } + + if( !config.testSpec().hasFilters() ) + Catch::cout() << pluralise( matchedTests, "test case" ) << '\n' << std::endl; + else + Catch::cout() << pluralise( matchedTests, "matching test case" ) << '\n' << std::endl; + return matchedTests; + } + + inline std::size_t listTestsNamesOnly( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( !config.testSpec().hasFilters() ) + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + std::size_t matchedTests = 0; + std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( std::vector<TestCase>::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + matchedTests++; + TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); + if( startsWith( testCaseInfo.name, '#' ) ) + Catch::cout() << '"' << testCaseInfo.name << '"' << std::endl; + else + Catch::cout() << testCaseInfo.name << std::endl; + } + return matchedTests; + } + + struct TagInfo { + TagInfo() : count ( 0 ) {} + void add( std::string const& spelling ) { + ++count; + spellings.insert( spelling ); + } + std::string all() const { + std::string out; + for( std::set<std::string>::const_iterator it = spellings.begin(), itEnd = spellings.end(); + it != itEnd; + ++it ) + out += "[" + *it + "]"; + return out; + } + std::set<std::string> spellings; + std::size_t count; + }; + + inline std::size_t listTags( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( config.testSpec().hasFilters() ) + Catch::cout() << "Tags for matching test cases:\n"; + else { + Catch::cout() << "All available tags:\n"; + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + } + + std::map<std::string, TagInfo> tagCounts; + + std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( std::vector<TestCase>::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + for( std::set<std::string>::const_iterator tagIt = it->getTestCaseInfo().tags.begin(), + tagItEnd = it->getTestCaseInfo().tags.end(); + tagIt != tagItEnd; + ++tagIt ) { + std::string tagName = *tagIt; + std::string lcaseTagName = toLower( tagName ); + std::map<std::string, TagInfo>::iterator countIt = tagCounts.find( lcaseTagName ); + if( countIt == tagCounts.end() ) + countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; + countIt->second.add( tagName ); + } + } + + for( std::map<std::string, TagInfo>::const_iterator countIt = tagCounts.begin(), + countItEnd = tagCounts.end(); + countIt != countItEnd; + ++countIt ) { + std::ostringstream oss; + oss << " " << std::setw(2) << countIt->second.count << " "; + Text wrapper( countIt->second.all(), TextAttributes() + .setInitialIndent( 0 ) + .setIndent( oss.str().size() ) + .setWidth( CATCH_CONFIG_CONSOLE_WIDTH-10 ) ); + Catch::cout() << oss.str() << wrapper << '\n'; + } + Catch::cout() << pluralise( tagCounts.size(), "tag" ) << '\n' << std::endl; + return tagCounts.size(); + } + + inline std::size_t listReporters( Config const& /*config*/ ) { + Catch::cout() << "Available reporters:\n"; + IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); + IReporterRegistry::FactoryMap::const_iterator itBegin = factories.begin(), itEnd = factories.end(), it; + std::size_t maxNameLen = 0; + for(it = itBegin; it != itEnd; ++it ) + maxNameLen = (std::max)( maxNameLen, it->first.size() ); + + for(it = itBegin; it != itEnd; ++it ) { + Text wrapper( it->second->getDescription(), TextAttributes() + .setInitialIndent( 0 ) + .setIndent( 7+maxNameLen ) + .setWidth( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) ); + Catch::cout() << " " + << it->first + << ':' + << std::string( maxNameLen - it->first.size() + 2, ' ' ) + << wrapper << '\n'; + } + Catch::cout() << std::endl; + return factories.size(); + } + + inline Option<std::size_t> list( Config const& config ) { + Option<std::size_t> listedCount; + if( config.listTests() ) + listedCount = listedCount.valueOr(0) + listTests( config ); + if( config.listTestNamesOnly() ) + listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); + if( config.listTags() ) + listedCount = listedCount.valueOr(0) + listTags( config ); + if( config.listReporters() ) + listedCount = listedCount.valueOr(0) + listReporters( config ); + return listedCount; + } + +} // end namespace Catch + +// #included from: internal/catch_run_context.hpp +#define TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED + +// #included from: catch_test_case_tracker.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED + +#include <algorithm> +#include <string> +#include <assert.h> +#include <vector> +#include <stdexcept> + +CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS + +namespace Catch { +namespace TestCaseTracking { + + struct NameAndLocation { + std::string name; + SourceLineInfo location; + + NameAndLocation( std::string const& _name, SourceLineInfo const& _location ) + : name( _name ), + location( _location ) + {} + }; + + struct ITracker : SharedImpl<> { + virtual ~ITracker(); + + // static queries + virtual NameAndLocation const& nameAndLocation() const = 0; + + // dynamic queries + virtual bool isComplete() const = 0; // Successfully completed or failed + virtual bool isSuccessfullyCompleted() const = 0; + virtual bool isOpen() const = 0; // Started but not complete + virtual bool hasChildren() const = 0; + + virtual ITracker& parent() = 0; + + // actions + virtual void close() = 0; // Successfully complete + virtual void fail() = 0; + virtual void markAsNeedingAnotherRun() = 0; + + virtual void addChild( Ptr<ITracker> const& child ) = 0; + virtual ITracker* findChild( NameAndLocation const& nameAndLocation ) = 0; + virtual void openChild() = 0; + + // Debug/ checking + virtual bool isSectionTracker() const = 0; + virtual bool isIndexTracker() const = 0; + }; + + class TrackerContext { + + enum RunState { + NotStarted, + Executing, + CompletedCycle + }; + + Ptr<ITracker> m_rootTracker; + ITracker* m_currentTracker; + RunState m_runState; + + public: + + static TrackerContext& instance() { + static TrackerContext s_instance; + return s_instance; + } + + TrackerContext() + : m_currentTracker( CATCH_NULL ), + m_runState( NotStarted ) + {} + + ITracker& startRun(); + + void endRun() { + m_rootTracker.reset(); + m_currentTracker = CATCH_NULL; + m_runState = NotStarted; + } + + void startCycle() { + m_currentTracker = m_rootTracker.get(); + m_runState = Executing; + } + void completeCycle() { + m_runState = CompletedCycle; + } + + bool completedCycle() const { + return m_runState == CompletedCycle; + } + ITracker& currentTracker() { + return *m_currentTracker; + } + void setCurrentTracker( ITracker* tracker ) { + m_currentTracker = tracker; + } + }; + + class TrackerBase : public ITracker { + protected: + enum CycleState { + NotStarted, + Executing, + ExecutingChildren, + NeedsAnotherRun, + CompletedSuccessfully, + Failed + }; + class TrackerHasName { + NameAndLocation m_nameAndLocation; + public: + TrackerHasName( NameAndLocation const& nameAndLocation ) : m_nameAndLocation( nameAndLocation ) {} + bool operator ()( Ptr<ITracker> const& tracker ) { + return + tracker->nameAndLocation().name == m_nameAndLocation.name && + tracker->nameAndLocation().location == m_nameAndLocation.location; + } + }; + typedef std::vector<Ptr<ITracker> > Children; + NameAndLocation m_nameAndLocation; + TrackerContext& m_ctx; + ITracker* m_parent; + Children m_children; + CycleState m_runState; + public: + TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : m_nameAndLocation( nameAndLocation ), + m_ctx( ctx ), + m_parent( parent ), + m_runState( NotStarted ) + {} + virtual ~TrackerBase(); + + virtual NameAndLocation const& nameAndLocation() const CATCH_OVERRIDE { + return m_nameAndLocation; + } + virtual bool isComplete() const CATCH_OVERRIDE { + return m_runState == CompletedSuccessfully || m_runState == Failed; + } + virtual bool isSuccessfullyCompleted() const CATCH_OVERRIDE { + return m_runState == CompletedSuccessfully; + } + virtual bool isOpen() const CATCH_OVERRIDE { + return m_runState != NotStarted && !isComplete(); + } + virtual bool hasChildren() const CATCH_OVERRIDE { + return !m_children.empty(); + } + + virtual void addChild( Ptr<ITracker> const& child ) CATCH_OVERRIDE { + m_children.push_back( child ); + } + + virtual ITracker* findChild( NameAndLocation const& nameAndLocation ) CATCH_OVERRIDE { + Children::const_iterator it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( nameAndLocation ) ); + return( it != m_children.end() ) + ? it->get() + : CATCH_NULL; + } + virtual ITracker& parent() CATCH_OVERRIDE { + assert( m_parent ); // Should always be non-null except for root + return *m_parent; + } + + virtual void openChild() CATCH_OVERRIDE { + if( m_runState != ExecutingChildren ) { + m_runState = ExecutingChildren; + if( m_parent ) + m_parent->openChild(); + } + } + + virtual bool isSectionTracker() const CATCH_OVERRIDE { return false; } + virtual bool isIndexTracker() const CATCH_OVERRIDE { return false; } + + void open() { + m_runState = Executing; + moveToThis(); + if( m_parent ) + m_parent->openChild(); + } + + virtual void close() CATCH_OVERRIDE { + + // Close any still open children (e.g. generators) + while( &m_ctx.currentTracker() != this ) + m_ctx.currentTracker().close(); + + switch( m_runState ) { + case NotStarted: + case CompletedSuccessfully: + case Failed: + throw std::logic_error( "Illogical state" ); + + case NeedsAnotherRun: + break;; + + case Executing: + m_runState = CompletedSuccessfully; + break; + case ExecutingChildren: + if( m_children.empty() || m_children.back()->isComplete() ) + m_runState = CompletedSuccessfully; + break; + + default: + throw std::logic_error( "Unexpected state" ); + } + moveToParent(); + m_ctx.completeCycle(); + } + virtual void fail() CATCH_OVERRIDE { + m_runState = Failed; + if( m_parent ) + m_parent->markAsNeedingAnotherRun(); + moveToParent(); + m_ctx.completeCycle(); + } + virtual void markAsNeedingAnotherRun() CATCH_OVERRIDE { + m_runState = NeedsAnotherRun; + } + private: + void moveToParent() { + assert( m_parent ); + m_ctx.setCurrentTracker( m_parent ); + } + void moveToThis() { + m_ctx.setCurrentTracker( this ); + } + }; + + class SectionTracker : public TrackerBase { + std::vector<std::string> m_filters; + public: + SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : TrackerBase( nameAndLocation, ctx, parent ) + { + if( parent ) { + while( !parent->isSectionTracker() ) + parent = &parent->parent(); + + SectionTracker& parentSection = static_cast<SectionTracker&>( *parent ); + addNextFilters( parentSection.m_filters ); + } + } + virtual ~SectionTracker(); + + virtual bool isSectionTracker() const CATCH_OVERRIDE { return true; } + + static SectionTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) { + SectionTracker* section = CATCH_NULL; + + ITracker& currentTracker = ctx.currentTracker(); + if( ITracker* childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isSectionTracker() ); + section = static_cast<SectionTracker*>( childTracker ); + } + else { + section = new SectionTracker( nameAndLocation, ctx, ¤tTracker ); + currentTracker.addChild( section ); + } + if( !ctx.completedCycle() ) + section->tryOpen(); + return *section; + } + + void tryOpen() { + if( !isComplete() && (m_filters.empty() || m_filters[0].empty() || m_filters[0] == m_nameAndLocation.name ) ) + open(); + } + + void addInitialFilters( std::vector<std::string> const& filters ) { + if( !filters.empty() ) { + m_filters.push_back(""); // Root - should never be consulted + m_filters.push_back(""); // Test Case - not a section filter + m_filters.insert( m_filters.end(), filters.begin(), filters.end() ); + } + } + void addNextFilters( std::vector<std::string> const& filters ) { + if( filters.size() > 1 ) + m_filters.insert( m_filters.end(), ++filters.begin(), filters.end() ); + } + }; + + class IndexTracker : public TrackerBase { + int m_size; + int m_index; + public: + IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size ) + : TrackerBase( nameAndLocation, ctx, parent ), + m_size( size ), + m_index( -1 ) + {} + virtual ~IndexTracker(); + + virtual bool isIndexTracker() const CATCH_OVERRIDE { return true; } + + static IndexTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ) { + IndexTracker* tracker = CATCH_NULL; + + ITracker& currentTracker = ctx.currentTracker(); + if( ITracker* childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isIndexTracker() ); + tracker = static_cast<IndexTracker*>( childTracker ); + } + else { + tracker = new IndexTracker( nameAndLocation, ctx, ¤tTracker, size ); + currentTracker.addChild( tracker ); + } + + if( !ctx.completedCycle() && !tracker->isComplete() ) { + if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) + tracker->moveNext(); + tracker->open(); + } + + return *tracker; + } + + int index() const { return m_index; } + + void moveNext() { + m_index++; + m_children.clear(); + } + + virtual void close() CATCH_OVERRIDE { + TrackerBase::close(); + if( m_runState == CompletedSuccessfully && m_index < m_size-1 ) + m_runState = Executing; + } + }; + + inline ITracker& TrackerContext::startRun() { + m_rootTracker = new SectionTracker( NameAndLocation( "{root}", CATCH_INTERNAL_LINEINFO ), *this, CATCH_NULL ); + m_currentTracker = CATCH_NULL; + m_runState = Executing; + return *m_rootTracker; + } + +} // namespace TestCaseTracking + +using TestCaseTracking::ITracker; +using TestCaseTracking::TrackerContext; +using TestCaseTracking::SectionTracker; +using TestCaseTracking::IndexTracker; + +} // namespace Catch + +CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS + +// #included from: catch_fatal_condition.hpp +#define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED + +namespace Catch { + + // Report the error condition + inline void reportFatal( std::string const& message ) { + IContext& context = Catch::getCurrentContext(); + IResultCapture* resultCapture = context.getResultCapture(); + resultCapture->handleFatalErrorCondition( message ); + } + +} // namespace Catch + +#if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// +// #included from: catch_windows_h_proxy.h + +#define TWOBLUECUBES_CATCH_WINDOWS_H_PROXY_H_INCLUDED + +#ifdef CATCH_DEFINES_NOMINMAX +# define NOMINMAX +#endif +#ifdef CATCH_DEFINES_WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif + +#ifdef __AFXDLL +#include <AfxWin.h> +#else +#include <windows.h> +#endif + +#ifdef CATCH_DEFINES_NOMINMAX +# undef NOMINMAX +#endif +#ifdef CATCH_DEFINES_WIN32_LEAN_AND_MEAN +# undef WIN32_LEAN_AND_MEAN +#endif + + +# if !defined ( CATCH_CONFIG_WINDOWS_SEH ) + +namespace Catch { + struct FatalConditionHandler { + void reset() {} + }; +} + +# else // CATCH_CONFIG_WINDOWS_SEH is defined + +namespace Catch { + + struct SignalDefs { DWORD id; const char* name; }; + extern SignalDefs signalDefs[]; + // There is no 1-1 mapping between signals and windows exceptions. + // Windows can easily distinguish between SO and SigSegV, + // but SigInt, SigTerm, etc are handled differently. + SignalDefs signalDefs[] = { + { EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal" }, + { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" }, + { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" }, + { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" }, + }; + + struct FatalConditionHandler { + + static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { + for (int i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { + if (ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) { + reportFatal(signalDefs[i].name); + } + } + // If its not an exception we care about, pass it along. + // This stops us from eating debugger breaks etc. + return EXCEPTION_CONTINUE_SEARCH; + } + + FatalConditionHandler() { + isSet = true; + // 32k seems enough for Catch to handle stack overflow, + // but the value was found experimentally, so there is no strong guarantee + guaranteeSize = 32 * 1024; + exceptionHandlerHandle = CATCH_NULL; + // Register as first handler in current chain + exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); + // Pass in guarantee size to be filled + SetThreadStackGuarantee(&guaranteeSize); + } + + static void reset() { + if (isSet) { + // Unregister handler and restore the old guarantee + RemoveVectoredExceptionHandler(exceptionHandlerHandle); + SetThreadStackGuarantee(&guaranteeSize); + exceptionHandlerHandle = CATCH_NULL; + isSet = false; + } + } + + ~FatalConditionHandler() { + reset(); + } + private: + static bool isSet; + static ULONG guaranteeSize; + static PVOID exceptionHandlerHandle; + }; + + bool FatalConditionHandler::isSet = false; + ULONG FatalConditionHandler::guaranteeSize = 0; + PVOID FatalConditionHandler::exceptionHandlerHandle = CATCH_NULL; + +} // namespace Catch + +# endif // CATCH_CONFIG_WINDOWS_SEH + +#else // Not Windows - assumed to be POSIX compatible ////////////////////////// + +# if !defined(CATCH_CONFIG_POSIX_SIGNALS) + +namespace Catch { + struct FatalConditionHandler { + void reset() {} + }; +} + +# else // CATCH_CONFIG_POSIX_SIGNALS is defined + +#include <signal.h> + +namespace Catch { + + struct SignalDefs { + int id; + const char* name; + }; + extern SignalDefs signalDefs[]; + SignalDefs signalDefs[] = { + { SIGINT, "SIGINT - Terminal interrupt signal" }, + { SIGILL, "SIGILL - Illegal instruction signal" }, + { SIGFPE, "SIGFPE - Floating point error signal" }, + { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, + { SIGTERM, "SIGTERM - Termination request signal" }, + { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } + }; + + struct FatalConditionHandler { + + static bool isSet; + static struct sigaction oldSigActions [sizeof(signalDefs)/sizeof(SignalDefs)]; + static stack_t oldSigStack; + static char altStackMem[SIGSTKSZ]; + + static void handleSignal( int sig ) { + std::string name = "<unknown signal>"; + for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { + SignalDefs &def = signalDefs[i]; + if (sig == def.id) { + name = def.name; + break; + } + } + reset(); + reportFatal(name); + raise( sig ); + } + + FatalConditionHandler() { + isSet = true; + stack_t sigStack; + sigStack.ss_sp = altStackMem; + sigStack.ss_size = SIGSTKSZ; + sigStack.ss_flags = 0; + sigaltstack(&sigStack, &oldSigStack); + struct sigaction sa = { 0 }; + + sa.sa_handler = handleSignal; + sa.sa_flags = SA_ONSTACK; + for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) { + sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); + } + } + + ~FatalConditionHandler() { + reset(); + } + static void reset() { + if( isSet ) { + // Set signals back to previous values -- hopefully nobody overwrote them in the meantime + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) { + sigaction(signalDefs[i].id, &oldSigActions[i], CATCH_NULL); + } + // Return the old stack + sigaltstack(&oldSigStack, CATCH_NULL); + isSet = false; + } + } + }; + + bool FatalConditionHandler::isSet = false; + struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {}; + stack_t FatalConditionHandler::oldSigStack = {}; + char FatalConditionHandler::altStackMem[SIGSTKSZ] = {}; + +} // namespace Catch + +# endif // CATCH_CONFIG_POSIX_SIGNALS + +#endif // not Windows + +#include <set> +#include <string> + +namespace Catch { + + class StreamRedirect { + + public: + StreamRedirect( std::ostream& stream, std::string& targetString ) + : m_stream( stream ), + m_prevBuf( stream.rdbuf() ), + m_targetString( targetString ) + { + stream.rdbuf( m_oss.rdbuf() ); + } + + ~StreamRedirect() { + m_targetString += m_oss.str(); + m_stream.rdbuf( m_prevBuf ); + } + + private: + std::ostream& m_stream; + std::streambuf* m_prevBuf; + std::ostringstream m_oss; + std::string& m_targetString; + }; + + /////////////////////////////////////////////////////////////////////////// + + class RunContext : public IResultCapture, public IRunner { + + RunContext( RunContext const& ); + void operator =( RunContext const& ); + + public: + + explicit RunContext( Ptr<IConfig const> const& _config, Ptr<IStreamingReporter> const& reporter ) + : m_runInfo( _config->name() ), + m_context( getCurrentMutableContext() ), + m_activeTestCase( CATCH_NULL ), + m_config( _config ), + m_reporter( reporter ), + m_shouldReportUnexpected ( true ) + { + m_context.setRunner( this ); + m_context.setConfig( m_config ); + m_context.setResultCapture( this ); + m_reporter->testRunStarting( m_runInfo ); + } + + virtual ~RunContext() { + m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, aborting() ) ); + } + + void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) { + m_reporter->testGroupStarting( GroupInfo( testSpec, groupIndex, groupsCount ) ); + } + void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ) { + m_reporter->testGroupEnded( TestGroupStats( GroupInfo( testSpec, groupIndex, groupsCount ), totals, aborting() ) ); + } + + Totals runTest( TestCase const& testCase ) { + Totals prevTotals = m_totals; + + std::string redirectedCout; + std::string redirectedCerr; + + TestCaseInfo testInfo = testCase.getTestCaseInfo(); + + m_reporter->testCaseStarting( testInfo ); + + m_activeTestCase = &testCase; + + do { + ITracker& rootTracker = m_trackerContext.startRun(); + assert( rootTracker.isSectionTracker() ); + static_cast<SectionTracker&>( rootTracker ).addInitialFilters( m_config->getSectionsToRun() ); + do { + m_trackerContext.startCycle(); + m_testCaseTracker = &SectionTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocation( testInfo.name, testInfo.lineInfo ) ); + runCurrentTest( redirectedCout, redirectedCerr ); + } + while( !m_testCaseTracker->isSuccessfullyCompleted() && !aborting() ); + } + // !TBD: deprecated - this will be replaced by indexed trackers + while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() ); + + Totals deltaTotals = m_totals.delta( prevTotals ); + if( testInfo.expectedToFail() && deltaTotals.testCases.passed > 0 ) { + deltaTotals.assertions.failed++; + deltaTotals.testCases.passed--; + deltaTotals.testCases.failed++; + } + m_totals.testCases += deltaTotals.testCases; + m_reporter->testCaseEnded( TestCaseStats( testInfo, + deltaTotals, + redirectedCout, + redirectedCerr, + aborting() ) ); + + m_activeTestCase = CATCH_NULL; + m_testCaseTracker = CATCH_NULL; + + return deltaTotals; + } + + Ptr<IConfig const> config() const { + return m_config; + } + + private: // IResultCapture + + virtual void assertionEnded( AssertionResult const& result ) { + if( result.getResultType() == ResultWas::Ok ) { + m_totals.assertions.passed++; + } + else if( !result.isOk() ) { + m_totals.assertions.failed++; + } + + // We have no use for the return value (whether messages should be cleared), because messages were made scoped + // and should be let to clear themselves out. + static_cast<void>(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals))); + + // Reset working state + m_lastAssertionInfo = AssertionInfo( std::string(), m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition ); + m_lastResult = result; + } + + virtual bool sectionStarted ( + SectionInfo const& sectionInfo, + Counts& assertions + ) + { + ITracker& sectionTracker = SectionTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocation( sectionInfo.name, sectionInfo.lineInfo ) ); + if( !sectionTracker.isOpen() ) + return false; + m_activeSections.push_back( §ionTracker ); + + m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; + + m_reporter->sectionStarting( sectionInfo ); + + assertions = m_totals.assertions; + + return true; + } + bool testForMissingAssertions( Counts& assertions ) { + if( assertions.total() != 0 ) + return false; + if( !m_config->warnAboutMissingAssertions() ) + return false; + if( m_trackerContext.currentTracker().hasChildren() ) + return false; + m_totals.assertions.failed++; + assertions.failed++; + return true; + } + + virtual void sectionEnded( SectionEndInfo const& endInfo ) { + Counts assertions = m_totals.assertions - endInfo.prevAssertions; + bool missingAssertions = testForMissingAssertions( assertions ); + + if( !m_activeSections.empty() ) { + m_activeSections.back()->close(); + m_activeSections.pop_back(); + } + + m_reporter->sectionEnded( SectionStats( endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions ) ); + m_messages.clear(); + } + + virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) { + if( m_unfinishedSections.empty() ) + m_activeSections.back()->fail(); + else + m_activeSections.back()->close(); + m_activeSections.pop_back(); + + m_unfinishedSections.push_back( endInfo ); + } + + virtual void pushScopedMessage( MessageInfo const& message ) { + m_messages.push_back( message ); + } + + virtual void popScopedMessage( MessageInfo const& message ) { + m_messages.erase( std::remove( m_messages.begin(), m_messages.end(), message ), m_messages.end() ); + } + + virtual std::string getCurrentTestName() const { + return m_activeTestCase + ? m_activeTestCase->getTestCaseInfo().name + : std::string(); + } + + virtual const AssertionResult* getLastResult() const { + return &m_lastResult; + } + + virtual void exceptionEarlyReported() { + m_shouldReportUnexpected = false; + } + + virtual void handleFatalErrorCondition( std::string const& message ) { + // Don't rebuild the result -- the stringification itself can cause more fatal errors + // Instead, fake a result data. + AssertionResultData tempResult; + tempResult.resultType = ResultWas::FatalErrorCondition; + tempResult.message = message; + AssertionResult result(m_lastAssertionInfo, tempResult); + + getResultCapture().assertionEnded(result); + + handleUnfinishedSections(); + + // Recreate section for test case (as we will lose the one that was in scope) + TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); + + Counts assertions; + assertions.failed = 1; + SectionStats testCaseSectionStats( testCaseSection, assertions, 0, false ); + m_reporter->sectionEnded( testCaseSectionStats ); + + TestCaseInfo testInfo = m_activeTestCase->getTestCaseInfo(); + + Totals deltaTotals; + deltaTotals.testCases.failed = 1; + m_reporter->testCaseEnded( TestCaseStats( testInfo, + deltaTotals, + std::string(), + std::string(), + false ) ); + m_totals.testCases.failed++; + testGroupEnded( std::string(), m_totals, 1, 1 ); + m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, false ) ); + } + + public: + // !TBD We need to do this another way! + bool aborting() const { + return m_totals.assertions.failed == static_cast<std::size_t>( m_config->abortAfter() ); + } + + private: + + void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ) { + TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); + m_reporter->sectionStarting( testCaseSection ); + Counts prevAssertions = m_totals.assertions; + double duration = 0; + m_shouldReportUnexpected = true; + try { + m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, std::string(), ResultDisposition::Normal ); + + seedRng( *m_config ); + + Timer timer; + timer.start(); + if( m_reporter->getPreferences().shouldRedirectStdOut ) { + StreamRedirect coutRedir( Catch::cout(), redirectedCout ); + StreamRedirect cerrRedir( Catch::cerr(), redirectedCerr ); + invokeActiveTestCase(); + } + else { + invokeActiveTestCase(); + } + duration = timer.getElapsedSeconds(); + } + catch( TestFailureException& ) { + // This just means the test was aborted due to failure + } + catch(...) { + // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions + // are reported without translation at the point of origin. + if (m_shouldReportUnexpected) { + makeUnexpectedResultBuilder().useActiveException(); + } + } + m_testCaseTracker->close(); + handleUnfinishedSections(); + m_messages.clear(); + + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions( assertions ); + + if( testCaseInfo.okToFail() ) { + std::swap( assertions.failedButOk, assertions.failed ); + m_totals.assertions.failed -= assertions.failedButOk; + m_totals.assertions.failedButOk += assertions.failedButOk; + } + + SectionStats testCaseSectionStats( testCaseSection, assertions, duration, missingAssertions ); + m_reporter->sectionEnded( testCaseSectionStats ); + } + + void invokeActiveTestCase() { + FatalConditionHandler fatalConditionHandler; // Handle signals + m_activeTestCase->invoke(); + fatalConditionHandler.reset(); + } + + private: + + ResultBuilder makeUnexpectedResultBuilder() const { + return ResultBuilder( m_lastAssertionInfo.macroName.c_str(), + m_lastAssertionInfo.lineInfo, + m_lastAssertionInfo.capturedExpression.c_str(), + m_lastAssertionInfo.resultDisposition ); + } + + void handleUnfinishedSections() { + // If sections ended prematurely due to an exception we stored their + // infos here so we can tear them down outside the unwind process. + for( std::vector<SectionEndInfo>::const_reverse_iterator it = m_unfinishedSections.rbegin(), + itEnd = m_unfinishedSections.rend(); + it != itEnd; + ++it ) + sectionEnded( *it ); + m_unfinishedSections.clear(); + } + + TestRunInfo m_runInfo; + IMutableContext& m_context; + TestCase const* m_activeTestCase; + ITracker* m_testCaseTracker; + ITracker* m_currentSectionTracker; + AssertionResult m_lastResult; + + Ptr<IConfig const> m_config; + Totals m_totals; + Ptr<IStreamingReporter> m_reporter; + std::vector<MessageInfo> m_messages; + AssertionInfo m_lastAssertionInfo; + std::vector<SectionEndInfo> m_unfinishedSections; + std::vector<ITracker*> m_activeSections; + TrackerContext m_trackerContext; + bool m_shouldReportUnexpected; + }; + + IResultCapture& getResultCapture() { + if( IResultCapture* capture = getCurrentContext().getResultCapture() ) + return *capture; + else + throw std::logic_error( "No result capture instance" ); + } + +} // end namespace Catch + +// #included from: internal/catch_version.h +#define TWOBLUECUBES_CATCH_VERSION_H_INCLUDED + +namespace Catch { + + // Versioning information + struct Version { + Version( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ); + + unsigned int const majorVersion; + unsigned int const minorVersion; + unsigned int const patchNumber; + + // buildNumber is only used if branchName is not null + char const * const branchName; + unsigned int const buildNumber; + + friend std::ostream& operator << ( std::ostream& os, Version const& version ); + + private: + void operator=( Version const& ); + }; + + inline Version libraryVersion(); +} + +#include <fstream> +#include <stdlib.h> +#include <limits> + +namespace Catch { + + Ptr<IStreamingReporter> createReporter( std::string const& reporterName, Ptr<Config> const& config ) { + Ptr<IStreamingReporter> reporter = getRegistryHub().getReporterRegistry().create( reporterName, config.get() ); + if( !reporter ) { + std::ostringstream oss; + oss << "No reporter registered with name: '" << reporterName << "'"; + throw std::domain_error( oss.str() ); + } + return reporter; + } + + Ptr<IStreamingReporter> makeReporter( Ptr<Config> const& config ) { + std::vector<std::string> reporters = config->getReporterNames(); + if( reporters.empty() ) + reporters.push_back( "console" ); + + Ptr<IStreamingReporter> reporter; + for( std::vector<std::string>::const_iterator it = reporters.begin(), itEnd = reporters.end(); + it != itEnd; + ++it ) + reporter = addReporter( reporter, createReporter( *it, config ) ); + return reporter; + } + Ptr<IStreamingReporter> addListeners( Ptr<IConfig const> const& config, Ptr<IStreamingReporter> reporters ) { + IReporterRegistry::Listeners listeners = getRegistryHub().getReporterRegistry().getListeners(); + for( IReporterRegistry::Listeners::const_iterator it = listeners.begin(), itEnd = listeners.end(); + it != itEnd; + ++it ) + reporters = addReporter(reporters, (*it)->create( ReporterConfig( config ) ) ); + return reporters; + } + + Totals runTests( Ptr<Config> const& config ) { + + Ptr<IConfig const> iconfig = config.get(); + + Ptr<IStreamingReporter> reporter = makeReporter( config ); + reporter = addListeners( iconfig, reporter ); + + RunContext context( iconfig, reporter ); + + Totals totals; + + context.testGroupStarting( config->name(), 1, 1 ); + + TestSpec testSpec = config->testSpec(); + if( !testSpec.hasFilters() ) + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests + + std::vector<TestCase> const& allTestCases = getAllTestCasesSorted( *iconfig ); + for( std::vector<TestCase>::const_iterator it = allTestCases.begin(), itEnd = allTestCases.end(); + it != itEnd; + ++it ) { + if( !context.aborting() && matchTest( *it, testSpec, *iconfig ) ) + totals += context.runTest( *it ); + else + reporter->skipTest( *it ); + } + + context.testGroupEnded( iconfig->name(), totals, 1, 1 ); + return totals; + } + + void applyFilenamesAsTags( IConfig const& config ) { + std::vector<TestCase> const& tests = getAllTestCasesSorted( config ); + for(std::size_t i = 0; i < tests.size(); ++i ) { + TestCase& test = const_cast<TestCase&>( tests[i] ); + std::set<std::string> tags = test.tags; + + std::string filename = test.lineInfo.file; + std::string::size_type lastSlash = filename.find_last_of( "\\/" ); + if( lastSlash != std::string::npos ) + filename = filename.substr( lastSlash+1 ); + + std::string::size_type lastDot = filename.find_last_of( "." ); + if( lastDot != std::string::npos ) + filename = filename.substr( 0, lastDot ); + + tags.insert( "#" + filename ); + setTags( test, tags ); + } + } + + class Session : NonCopyable { + static bool alreadyInstantiated; + + public: + + struct OnUnusedOptions { enum DoWhat { Ignore, Fail }; }; + + Session() + : m_cli( makeCommandLineParser() ) { + if( alreadyInstantiated ) { + std::string msg = "Only one instance of Catch::Session can ever be used"; + Catch::cerr() << msg << std::endl; + throw std::logic_error( msg ); + } + alreadyInstantiated = true; + } + ~Session() { + Catch::cleanUp(); + } + + void showHelp( std::string const& processName ) { + Catch::cout() << "\nCatch v" << libraryVersion() << "\n"; + + m_cli.usage( Catch::cout(), processName ); + Catch::cout() << "For more detail usage please see the project docs\n" << std::endl; + } + + int applyCommandLine( int argc, char const* const* const argv, OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { + try { + m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail ); + m_unusedTokens = m_cli.parseInto( Clara::argsToVector( argc, argv ), m_configData ); + if( m_configData.showHelp ) + showHelp( m_configData.processName ); + m_config.reset(); + } + catch( std::exception& ex ) { + { + Colour colourGuard( Colour::Red ); + Catch::cerr() + << "\nError(s) in input:\n" + << Text( ex.what(), TextAttributes().setIndent(2) ) + << "\n\n"; + } + m_cli.usage( Catch::cout(), m_configData.processName ); + return (std::numeric_limits<int>::max)(); + } + return 0; + } + + void useConfigData( ConfigData const& _configData ) { + m_configData = _configData; + m_config.reset(); + } + + int run( int argc, char const* const* const argv ) { + + int returnCode = applyCommandLine( argc, argv ); + if( returnCode == 0 ) + returnCode = run(); + return returnCode; + } + + #if defined(WIN32) && defined(UNICODE) + int run( int argc, wchar_t const* const* const argv ) { + + char **utf8Argv = new char *[ argc ]; + + for ( int i = 0; i < argc; ++i ) { + int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL ); + + utf8Argv[ i ] = new char[ bufSize ]; + + WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, NULL, NULL ); + } + + int returnCode = applyCommandLine( argc, utf8Argv ); + if( returnCode == 0 ) + returnCode = run(); + + for ( int i = 0; i < argc; ++i ) + delete [] utf8Argv[ i ]; + + delete [] utf8Argv; + + return returnCode; + } + #endif + + int run() { + if( m_configData.showHelp ) + return 0; + + try + { + config(); // Force config to be constructed + + seedRng( *m_config ); + + if( m_configData.filenamesAsTags ) + applyFilenamesAsTags( *m_config ); + + // Handle list request + if( Option<std::size_t> listed = list( config() ) ) + return static_cast<int>( *listed ); + + return static_cast<int>( runTests( m_config ).assertions.failed ); + } + catch( std::exception& ex ) { + Catch::cerr() << ex.what() << std::endl; + return (std::numeric_limits<int>::max)(); + } + } + + Clara::CommandLine<ConfigData> const& cli() const { + return m_cli; + } + std::vector<Clara::Parser::Token> const& unusedTokens() const { + return m_unusedTokens; + } + ConfigData& configData() { + return m_configData; + } + Config& config() { + if( !m_config ) + m_config = new Config( m_configData ); + return *m_config; + } + private: + Clara::CommandLine<ConfigData> m_cli; + std::vector<Clara::Parser::Token> m_unusedTokens; + ConfigData m_configData; + Ptr<Config> m_config; + }; + + bool Session::alreadyInstantiated = false; + +} // end namespace Catch + +// #included from: catch_registry_hub.hpp +#define TWOBLUECUBES_CATCH_REGISTRY_HUB_HPP_INCLUDED + +// #included from: catch_test_case_registry_impl.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED + +#include <vector> +#include <set> +#include <sstream> +#include <algorithm> + +namespace Catch { + + struct RandomNumberGenerator { + typedef std::ptrdiff_t result_type; + + result_type operator()( result_type n ) const { return std::rand() % n; } + +#ifdef CATCH_CONFIG_CPP11_SHUFFLE + static constexpr result_type min() { return 0; } + static constexpr result_type max() { return 1000000; } + result_type operator()() const { return std::rand() % max(); } +#endif + template<typename V> + static void shuffle( V& vector ) { + RandomNumberGenerator rng; +#ifdef CATCH_CONFIG_CPP11_SHUFFLE + std::shuffle( vector.begin(), vector.end(), rng ); +#else + std::random_shuffle( vector.begin(), vector.end(), rng ); +#endif + } + }; + + inline std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases ) { + + std::vector<TestCase> sorted = unsortedTestCases; + + switch( config.runOrder() ) { + case RunTests::InLexicographicalOrder: + std::sort( sorted.begin(), sorted.end() ); + break; + case RunTests::InRandomOrder: + { + seedRng( config ); + RandomNumberGenerator::shuffle( sorted ); + } + break; + case RunTests::InDeclarationOrder: + // already in declaration order + break; + } + return sorted; + } + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) { + return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() ); + } + + void enforceNoDuplicateTestCases( std::vector<TestCase> const& functions ) { + std::set<TestCase> seenFunctions; + for( std::vector<TestCase>::const_iterator it = functions.begin(), itEnd = functions.end(); + it != itEnd; + ++it ) { + std::pair<std::set<TestCase>::const_iterator, bool> prev = seenFunctions.insert( *it ); + if( !prev.second ) { + std::ostringstream ss; + + ss << Colour( Colour::Red ) + << "error: TEST_CASE( \"" << it->name << "\" ) already defined.\n" + << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << '\n' + << "\tRedefined at " << it->getTestCaseInfo().lineInfo << std::endl; + + throw std::runtime_error(ss.str()); + } + } + } + + std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ) { + std::vector<TestCase> filtered; + filtered.reserve( testCases.size() ); + for( std::vector<TestCase>::const_iterator it = testCases.begin(), itEnd = testCases.end(); + it != itEnd; + ++it ) + if( matchTest( *it, testSpec, config ) ) + filtered.push_back( *it ); + return filtered; + } + std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ) { + return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); + } + + class TestRegistry : public ITestCaseRegistry { + public: + TestRegistry() + : m_currentSortOrder( RunTests::InDeclarationOrder ), + m_unnamedCount( 0 ) + {} + virtual ~TestRegistry(); + + virtual void registerTest( TestCase const& testCase ) { + std::string name = testCase.getTestCaseInfo().name; + if( name.empty() ) { + std::ostringstream oss; + oss << "Anonymous test case " << ++m_unnamedCount; + return registerTest( testCase.withName( oss.str() ) ); + } + m_functions.push_back( testCase ); + } + + virtual std::vector<TestCase> const& getAllTests() const { + return m_functions; + } + virtual std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const { + if( m_sortedFunctions.empty() ) + enforceNoDuplicateTestCases( m_functions ); + + if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) { + m_sortedFunctions = sortTests( config, m_functions ); + m_currentSortOrder = config.runOrder(); + } + return m_sortedFunctions; + } + + private: + std::vector<TestCase> m_functions; + mutable RunTests::InWhatOrder m_currentSortOrder; + mutable std::vector<TestCase> m_sortedFunctions; + size_t m_unnamedCount; + std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised + }; + + /////////////////////////////////////////////////////////////////////////// + + class FreeFunctionTestCase : public SharedImpl<ITestCase> { + public: + + FreeFunctionTestCase( TestFunction fun ) : m_fun( fun ) {} + + virtual void invoke() const { + m_fun(); + } + + private: + virtual ~FreeFunctionTestCase(); + + TestFunction m_fun; + }; + + inline std::string extractClassName( std::string const& classOrQualifiedMethodName ) { + std::string className = classOrQualifiedMethodName; + if( startsWith( className, '&' ) ) + { + std::size_t lastColons = className.rfind( "::" ); + std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); + if( penultimateColons == std::string::npos ) + penultimateColons = 1; + className = className.substr( penultimateColons, lastColons-penultimateColons ); + } + return className; + } + + void registerTestCase + ( ITestCase* testCase, + char const* classOrQualifiedMethodName, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ) { + + getMutableRegistryHub().registerTest + ( makeTestCase + ( testCase, + extractClassName( classOrQualifiedMethodName ), + nameAndDesc.name, + nameAndDesc.description, + lineInfo ) ); + } + void registerTestCaseFunction + ( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ) { + registerTestCase( new FreeFunctionTestCase( function ), "", nameAndDesc, lineInfo ); + } + + /////////////////////////////////////////////////////////////////////////// + + AutoReg::AutoReg + ( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ) { + registerTestCaseFunction( function, lineInfo, nameAndDesc ); + } + + AutoReg::~AutoReg() {} + +} // end namespace Catch + +// #included from: catch_reporter_registry.hpp +#define TWOBLUECUBES_CATCH_REPORTER_REGISTRY_HPP_INCLUDED + +#include <map> + +namespace Catch { + + class ReporterRegistry : public IReporterRegistry { + + public: + + virtual ~ReporterRegistry() CATCH_OVERRIDE {} + + virtual IStreamingReporter* create( std::string const& name, Ptr<IConfig const> const& config ) const CATCH_OVERRIDE { + FactoryMap::const_iterator it = m_factories.find( name ); + if( it == m_factories.end() ) + return CATCH_NULL; + return it->second->create( ReporterConfig( config ) ); + } + + void registerReporter( std::string const& name, Ptr<IReporterFactory> const& factory ) { + m_factories.insert( std::make_pair( name, factory ) ); + } + void registerListener( Ptr<IReporterFactory> const& factory ) { + m_listeners.push_back( factory ); + } + + virtual FactoryMap const& getFactories() const CATCH_OVERRIDE { + return m_factories; + } + virtual Listeners const& getListeners() const CATCH_OVERRIDE { + return m_listeners; + } + + private: + FactoryMap m_factories; + Listeners m_listeners; + }; +} + +// #included from: catch_exception_translator_registry.hpp +#define TWOBLUECUBES_CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED + +#ifdef __OBJC__ +#import "Foundation/Foundation.h" +#endif + +namespace Catch { + + class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { + public: + ~ExceptionTranslatorRegistry() { + deleteAll( m_translators ); + } + + virtual void registerTranslator( const IExceptionTranslator* translator ) { + m_translators.push_back( translator ); + } + + virtual std::string translateActiveException() const { + try { +#ifdef __OBJC__ + // In Objective-C try objective-c exceptions first + @try { + return tryTranslators(); + } + @catch (NSException *exception) { + return Catch::toString( [exception description] ); + } +#else + return tryTranslators(); +#endif + } + catch( TestFailureException& ) { + throw; + } + catch( std::exception& ex ) { + return ex.what(); + } + catch( std::string& msg ) { + return msg; + } + catch( const char* msg ) { + return msg; + } + catch(...) { + return "Unknown exception"; + } + } + + std::string tryTranslators() const { + if( m_translators.empty() ) + throw; + else + return m_translators[0]->translate( m_translators.begin()+1, m_translators.end() ); + } + + private: + std::vector<const IExceptionTranslator*> m_translators; + }; +} + +// #included from: catch_tag_alias_registry.h +#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED + +#include <map> + +namespace Catch { + + class TagAliasRegistry : public ITagAliasRegistry { + public: + virtual ~TagAliasRegistry(); + virtual Option<TagAlias> find( std::string const& alias ) const; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const; + void add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ); + + private: + std::map<std::string, TagAlias> m_registry; + }; + +} // end namespace Catch + +namespace Catch { + + namespace { + + class RegistryHub : public IRegistryHub, public IMutableRegistryHub { + + RegistryHub( RegistryHub const& ); + void operator=( RegistryHub const& ); + + public: // IRegistryHub + RegistryHub() { + } + virtual IReporterRegistry const& getReporterRegistry() const CATCH_OVERRIDE { + return m_reporterRegistry; + } + virtual ITestCaseRegistry const& getTestCaseRegistry() const CATCH_OVERRIDE { + return m_testCaseRegistry; + } + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() CATCH_OVERRIDE { + return m_exceptionTranslatorRegistry; + } + virtual ITagAliasRegistry const& getTagAliasRegistry() const CATCH_OVERRIDE { + return m_tagAliasRegistry; + } + + public: // IMutableRegistryHub + virtual void registerReporter( std::string const& name, Ptr<IReporterFactory> const& factory ) CATCH_OVERRIDE { + m_reporterRegistry.registerReporter( name, factory ); + } + virtual void registerListener( Ptr<IReporterFactory> const& factory ) CATCH_OVERRIDE { + m_reporterRegistry.registerListener( factory ); + } + virtual void registerTest( TestCase const& testInfo ) CATCH_OVERRIDE { + m_testCaseRegistry.registerTest( testInfo ); + } + virtual void registerTranslator( const IExceptionTranslator* translator ) CATCH_OVERRIDE { + m_exceptionTranslatorRegistry.registerTranslator( translator ); + } + virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) CATCH_OVERRIDE { + m_tagAliasRegistry.add( alias, tag, lineInfo ); + } + + private: + TestRegistry m_testCaseRegistry; + ReporterRegistry m_reporterRegistry; + ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; + TagAliasRegistry m_tagAliasRegistry; + }; + + // Single, global, instance + inline RegistryHub*& getTheRegistryHub() { + static RegistryHub* theRegistryHub = CATCH_NULL; + if( !theRegistryHub ) + theRegistryHub = new RegistryHub(); + return theRegistryHub; + } + } + + IRegistryHub& getRegistryHub() { + return *getTheRegistryHub(); + } + IMutableRegistryHub& getMutableRegistryHub() { + return *getTheRegistryHub(); + } + void cleanUp() { + delete getTheRegistryHub(); + getTheRegistryHub() = CATCH_NULL; + cleanUpContext(); + } + std::string translateActiveException() { + return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); + } + +} // end namespace Catch + +// #included from: catch_notimplemented_exception.hpp +#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_HPP_INCLUDED + +#include <sstream> + +namespace Catch { + + NotImplementedException::NotImplementedException( SourceLineInfo const& lineInfo ) + : m_lineInfo( lineInfo ) { + std::ostringstream oss; + oss << lineInfo << ": function "; + oss << "not implemented"; + m_what = oss.str(); + } + + const char* NotImplementedException::what() const CATCH_NOEXCEPT { + return m_what.c_str(); + } + +} // end namespace Catch + +// #included from: catch_context_impl.hpp +#define TWOBLUECUBES_CATCH_CONTEXT_IMPL_HPP_INCLUDED + +// #included from: catch_stream.hpp +#define TWOBLUECUBES_CATCH_STREAM_HPP_INCLUDED + +#include <stdexcept> +#include <cstdio> +#include <iostream> + +namespace Catch { + + template<typename WriterF, size_t bufferSize=256> + class StreamBufImpl : public StreamBufBase { + char data[bufferSize]; + WriterF m_writer; + + public: + StreamBufImpl() { + setp( data, data + sizeof(data) ); + } + + ~StreamBufImpl() CATCH_NOEXCEPT { + sync(); + } + + private: + int overflow( int c ) { + sync(); + + if( c != EOF ) { + if( pbase() == epptr() ) + m_writer( std::string( 1, static_cast<char>( c ) ) ); + else + sputc( static_cast<char>( c ) ); + } + return 0; + } + + int sync() { + if( pbase() != pptr() ) { + m_writer( std::string( pbase(), static_cast<std::string::size_type>( pptr() - pbase() ) ) ); + setp( pbase(), epptr() ); + } + return 0; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + FileStream::FileStream( std::string const& filename ) { + m_ofs.open( filename.c_str() ); + if( m_ofs.fail() ) { + std::ostringstream oss; + oss << "Unable to open file: '" << filename << '\''; + throw std::domain_error( oss.str() ); + } + } + + std::ostream& FileStream::stream() const { + return m_ofs; + } + + struct OutputDebugWriter { + + void operator()( std::string const&str ) { + writeToDebugConsole( str ); + } + }; + + DebugOutStream::DebugOutStream() + : m_streamBuf( new StreamBufImpl<OutputDebugWriter>() ), + m_os( m_streamBuf.get() ) + {} + + std::ostream& DebugOutStream::stream() const { + return m_os; + } + + // Store the streambuf from cout up-front because + // cout may get redirected when running tests + CoutStream::CoutStream() + : m_os( Catch::cout().rdbuf() ) + {} + + std::ostream& CoutStream::stream() const { + return m_os; + } + +#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions + std::ostream& cout() { + return std::cout; + } + std::ostream& cerr() { + return std::cerr; + } +#endif +} + +namespace Catch { + + class Context : public IMutableContext { + + Context() : m_config( CATCH_NULL ), m_runner( CATCH_NULL ), m_resultCapture( CATCH_NULL ) {} + Context( Context const& ); + void operator=( Context const& ); + + public: + virtual ~Context() { + deleteAllValues( m_generatorsByTestName ); + } + + public: // IContext + virtual IResultCapture* getResultCapture() { + return m_resultCapture; + } + virtual IRunner* getRunner() { + return m_runner; + } + virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) { + return getGeneratorsForCurrentTest() + .getGeneratorInfo( fileInfo, totalSize ) + .getCurrentIndex(); + } + virtual bool advanceGeneratorsForCurrentTest() { + IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); + return generators && generators->moveNext(); + } + + virtual Ptr<IConfig const> getConfig() const { + return m_config; + } + + public: // IMutableContext + virtual void setResultCapture( IResultCapture* resultCapture ) { + m_resultCapture = resultCapture; + } + virtual void setRunner( IRunner* runner ) { + m_runner = runner; + } + virtual void setConfig( Ptr<IConfig const> const& config ) { + m_config = config; + } + + friend IMutableContext& getCurrentMutableContext(); + + private: + IGeneratorsForTest* findGeneratorsForCurrentTest() { + std::string testName = getResultCapture()->getCurrentTestName(); + + std::map<std::string, IGeneratorsForTest*>::const_iterator it = + m_generatorsByTestName.find( testName ); + return it != m_generatorsByTestName.end() + ? it->second + : CATCH_NULL; + } + + IGeneratorsForTest& getGeneratorsForCurrentTest() { + IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); + if( !generators ) { + std::string testName = getResultCapture()->getCurrentTestName(); + generators = createGeneratorsForTest(); + m_generatorsByTestName.insert( std::make_pair( testName, generators ) ); + } + return *generators; + } + + private: + Ptr<IConfig const> m_config; + IRunner* m_runner; + IResultCapture* m_resultCapture; + std::map<std::string, IGeneratorsForTest*> m_generatorsByTestName; + }; + + namespace { + Context* currentContext = CATCH_NULL; + } + IMutableContext& getCurrentMutableContext() { + if( !currentContext ) + currentContext = new Context(); + return *currentContext; + } + IContext& getCurrentContext() { + return getCurrentMutableContext(); + } + + void cleanUpContext() { + delete currentContext; + currentContext = CATCH_NULL; + } +} + +// #included from: catch_console_colour_impl.hpp +#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_IMPL_HPP_INCLUDED + +// #included from: catch_errno_guard.hpp +#define TWOBLUECUBES_CATCH_ERRNO_GUARD_HPP_INCLUDED + +#include <cerrno> + +namespace Catch { + + class ErrnoGuard { + public: + ErrnoGuard():m_oldErrno(errno){} + ~ErrnoGuard() { errno = m_oldErrno; } + private: + int m_oldErrno; + }; + +} + +namespace Catch { + namespace { + + struct IColourImpl { + virtual ~IColourImpl() {} + virtual void use( Colour::Code _colourCode ) = 0; + }; + + struct NoColourImpl : IColourImpl { + void use( Colour::Code ) {} + + static IColourImpl* instance() { + static NoColourImpl s_instance; + return &s_instance; + } + }; + + } // anon namespace +} // namespace Catch + +#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) +# ifdef CATCH_PLATFORM_WINDOWS +# define CATCH_CONFIG_COLOUR_WINDOWS +# else +# define CATCH_CONFIG_COLOUR_ANSI +# endif +#endif + +#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// + +namespace Catch { +namespace { + + class Win32ColourImpl : public IColourImpl { + public: + Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) + { + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); + originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY ); + originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); + } + + virtual void use( Colour::Code _colourCode ) { + switch( _colourCode ) { + case Colour::None: return setTextAttribute( originalForegroundAttributes ); + case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::Red: return setTextAttribute( FOREGROUND_RED ); + case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); + case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); + case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); + case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); + case Colour::Grey: return setTextAttribute( 0 ); + + case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); + case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); + case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); + case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + + case Colour::Bright: throw std::logic_error( "not a colour" ); + } + } + + private: + void setTextAttribute( WORD _textAttribute ) { + SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes ); + } + HANDLE stdoutHandle; + WORD originalForegroundAttributes; + WORD originalBackgroundAttributes; + }; + + IColourImpl* platformColourInstance() { + static Win32ColourImpl s_instance; + + Ptr<IConfig const> config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = !isDebuggerActive() + ? UseColour::Yes + : UseColour::No; + return colourMode == UseColour::Yes + ? &s_instance + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// + +#include <unistd.h> + +namespace Catch { +namespace { + + // use POSIX/ ANSI console terminal codes + // Thanks to Adam Strzelecki for original contribution + // (http://github.com/nanoant) + // https://github.com/philsquared/Catch/pull/131 + class PosixColourImpl : public IColourImpl { + public: + virtual void use( Colour::Code _colourCode ) { + switch( _colourCode ) { + case Colour::None: + case Colour::White: return setColour( "[0m" ); + case Colour::Red: return setColour( "[0;31m" ); + case Colour::Green: return setColour( "[0;32m" ); + case Colour::Blue: return setColour( "[0;34m" ); + case Colour::Cyan: return setColour( "[0;36m" ); + case Colour::Yellow: return setColour( "[0;33m" ); + case Colour::Grey: return setColour( "[1;30m" ); + + case Colour::LightGrey: return setColour( "[0;37m" ); + case Colour::BrightRed: return setColour( "[1;31m" ); + case Colour::BrightGreen: return setColour( "[1;32m" ); + case Colour::BrightWhite: return setColour( "[1;37m" ); + + case Colour::Bright: throw std::logic_error( "not a colour" ); + } + } + static IColourImpl* instance() { + static PosixColourImpl s_instance; + return &s_instance; + } + + private: + void setColour( const char* _escapeCode ) { + Catch::cout() << '\033' << _escapeCode; + } + }; + + IColourImpl* platformColourInstance() { + ErrnoGuard guard; + Ptr<IConfig const> config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = (!isDebuggerActive() && isatty(STDOUT_FILENO) ) + ? UseColour::Yes + : UseColour::No; + return colourMode == UseColour::Yes + ? PosixColourImpl::instance() + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#else // not Windows or ANSI /////////////////////////////////////////////// + +namespace Catch { + + static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } + +} // end namespace Catch + +#endif // Windows/ ANSI/ None + +namespace Catch { + + Colour::Colour( Code _colourCode ) : m_moved( false ) { use( _colourCode ); } + Colour::Colour( Colour const& _other ) : m_moved( false ) { const_cast<Colour&>( _other ).m_moved = true; } + Colour::~Colour(){ if( !m_moved ) use( None ); } + + void Colour::use( Code _colourCode ) { + static IColourImpl* impl = platformColourInstance(); + impl->use( _colourCode ); + } + +} // end namespace Catch + +// #included from: catch_generators_impl.hpp +#define TWOBLUECUBES_CATCH_GENERATORS_IMPL_HPP_INCLUDED + +#include <vector> +#include <string> +#include <map> + +namespace Catch { + + struct GeneratorInfo : IGeneratorInfo { + + GeneratorInfo( std::size_t size ) + : m_size( size ), + m_currentIndex( 0 ) + {} + + bool moveNext() { + if( ++m_currentIndex == m_size ) { + m_currentIndex = 0; + return false; + } + return true; + } + + std::size_t getCurrentIndex() const { + return m_currentIndex; + } + + std::size_t m_size; + std::size_t m_currentIndex; + }; + + /////////////////////////////////////////////////////////////////////////// + + class GeneratorsForTest : public IGeneratorsForTest { + + public: + ~GeneratorsForTest() { + deleteAll( m_generatorsInOrder ); + } + + IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) { + std::map<std::string, IGeneratorInfo*>::const_iterator it = m_generatorsByName.find( fileInfo ); + if( it == m_generatorsByName.end() ) { + IGeneratorInfo* info = new GeneratorInfo( size ); + m_generatorsByName.insert( std::make_pair( fileInfo, info ) ); + m_generatorsInOrder.push_back( info ); + return *info; + } + return *it->second; + } + + bool moveNext() { + std::vector<IGeneratorInfo*>::const_iterator it = m_generatorsInOrder.begin(); + std::vector<IGeneratorInfo*>::const_iterator itEnd = m_generatorsInOrder.end(); + for(; it != itEnd; ++it ) { + if( (*it)->moveNext() ) + return true; + } + return false; + } + + private: + std::map<std::string, IGeneratorInfo*> m_generatorsByName; + std::vector<IGeneratorInfo*> m_generatorsInOrder; + }; + + IGeneratorsForTest* createGeneratorsForTest() + { + return new GeneratorsForTest(); + } + +} // end namespace Catch + +// #included from: catch_assertionresult.hpp +#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_HPP_INCLUDED + +namespace Catch { + + AssertionInfo::AssertionInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + std::string const& _capturedExpression, + ResultDisposition::Flags _resultDisposition ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + capturedExpression( _capturedExpression ), + resultDisposition( _resultDisposition ) + {} + + AssertionResult::AssertionResult() {} + + AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) + : m_info( info ), + m_resultData( data ) + {} + + AssertionResult::~AssertionResult() {} + + // Result was a success + bool AssertionResult::succeeded() const { + return Catch::isOk( m_resultData.resultType ); + } + + // Result was a success, or failure is suppressed + bool AssertionResult::isOk() const { + return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); + } + + ResultWas::OfType AssertionResult::getResultType() const { + return m_resultData.resultType; + } + + bool AssertionResult::hasExpression() const { + return !m_info.capturedExpression.empty(); + } + + bool AssertionResult::hasMessage() const { + return !m_resultData.message.empty(); + } + + std::string AssertionResult::getExpression() const { + if( isFalseTest( m_info.resultDisposition ) ) + return '!' + m_info.capturedExpression; + else + return m_info.capturedExpression; + } + std::string AssertionResult::getExpressionInMacro() const { + if( m_info.macroName.empty() ) + return m_info.capturedExpression; + else + return m_info.macroName + "( " + m_info.capturedExpression + " )"; + } + + bool AssertionResult::hasExpandedExpression() const { + return hasExpression() && getExpandedExpression() != getExpression(); + } + + std::string AssertionResult::getExpandedExpression() const { + return m_resultData.reconstructExpression(); + } + + std::string AssertionResult::getMessage() const { + return m_resultData.message; + } + SourceLineInfo AssertionResult::getSourceInfo() const { + return m_info.lineInfo; + } + + std::string AssertionResult::getTestMacroName() const { + return m_info.macroName; + } + + void AssertionResult::discardDecomposedExpression() const { + m_resultData.decomposedExpression = CATCH_NULL; + } + + void AssertionResult::expandDecomposedExpression() const { + m_resultData.reconstructExpression(); + } + +} // end namespace Catch + +// #included from: catch_test_case_info.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_HPP_INCLUDED + +#include <cctype> + +namespace Catch { + + inline TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { + if( startsWith( tag, '.' ) || + tag == "hide" || + tag == "!hide" ) + return TestCaseInfo::IsHidden; + else if( tag == "!throws" ) + return TestCaseInfo::Throws; + else if( tag == "!shouldfail" ) + return TestCaseInfo::ShouldFail; + else if( tag == "!mayfail" ) + return TestCaseInfo::MayFail; + else if( tag == "!nonportable" ) + return TestCaseInfo::NonPortable; + else + return TestCaseInfo::None; + } + inline bool isReservedTag( std::string const& tag ) { + return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( tag[0] ); + } + inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { + if( isReservedTag( tag ) ) { + std::ostringstream ss; + ss << Colour(Colour::Red) + << "Tag name [" << tag << "] not allowed.\n" + << "Tag names starting with non alpha-numeric characters are reserved\n" + << Colour(Colour::FileName) + << _lineInfo << '\n'; + throw std::runtime_error(ss.str()); + } + } + + TestCase makeTestCase( ITestCase* _testCase, + std::string const& _className, + std::string const& _name, + std::string const& _descOrTags, + SourceLineInfo const& _lineInfo ) + { + bool isHidden( startsWith( _name, "./" ) ); // Legacy support + + // Parse out tags + std::set<std::string> tags; + std::string desc, tag; + bool inTag = false; + for( std::size_t i = 0; i < _descOrTags.size(); ++i ) { + char c = _descOrTags[i]; + if( !inTag ) { + if( c == '[' ) + inTag = true; + else + desc += c; + } + else { + if( c == ']' ) { + TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); + if( prop == TestCaseInfo::IsHidden ) + isHidden = true; + else if( prop == TestCaseInfo::None ) + enforceNotReservedTag( tag, _lineInfo ); + + tags.insert( tag ); + tag.clear(); + inTag = false; + } + else + tag += c; + } + } + if( isHidden ) { + tags.insert( "hide" ); + tags.insert( "." ); + } + + TestCaseInfo info( _name, _className, desc, tags, _lineInfo ); + return TestCase( _testCase, info ); + } + + void setTags( TestCaseInfo& testCaseInfo, std::set<std::string> const& tags ) + { + testCaseInfo.tags = tags; + testCaseInfo.lcaseTags.clear(); + + std::ostringstream oss; + for( std::set<std::string>::const_iterator it = tags.begin(), itEnd = tags.end(); it != itEnd; ++it ) { + oss << '[' << *it << ']'; + std::string lcaseTag = toLower( *it ); + testCaseInfo.properties = static_cast<TestCaseInfo::SpecialProperties>( testCaseInfo.properties | parseSpecialTag( lcaseTag ) ); + testCaseInfo.lcaseTags.insert( lcaseTag ); + } + testCaseInfo.tagsAsString = oss.str(); + } + + TestCaseInfo::TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::set<std::string> const& _tags, + SourceLineInfo const& _lineInfo ) + : name( _name ), + className( _className ), + description( _description ), + lineInfo( _lineInfo ), + properties( None ) + { + setTags( *this, _tags ); + } + + TestCaseInfo::TestCaseInfo( TestCaseInfo const& other ) + : name( other.name ), + className( other.className ), + description( other.description ), + tags( other.tags ), + lcaseTags( other.lcaseTags ), + tagsAsString( other.tagsAsString ), + lineInfo( other.lineInfo ), + properties( other.properties ) + {} + + bool TestCaseInfo::isHidden() const { + return ( properties & IsHidden ) != 0; + } + bool TestCaseInfo::throws() const { + return ( properties & Throws ) != 0; + } + bool TestCaseInfo::okToFail() const { + return ( properties & (ShouldFail | MayFail ) ) != 0; + } + bool TestCaseInfo::expectedToFail() const { + return ( properties & (ShouldFail ) ) != 0; + } + + TestCase::TestCase( ITestCase* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {} + + TestCase::TestCase( TestCase const& other ) + : TestCaseInfo( other ), + test( other.test ) + {} + + TestCase TestCase::withName( std::string const& _newName ) const { + TestCase other( *this ); + other.name = _newName; + return other; + } + + void TestCase::swap( TestCase& other ) { + test.swap( other.test ); + name.swap( other.name ); + className.swap( other.className ); + description.swap( other.description ); + tags.swap( other.tags ); + lcaseTags.swap( other.lcaseTags ); + tagsAsString.swap( other.tagsAsString ); + std::swap( TestCaseInfo::properties, static_cast<TestCaseInfo&>( other ).properties ); + std::swap( lineInfo, other.lineInfo ); + } + + void TestCase::invoke() const { + test->invoke(); + } + + bool TestCase::operator == ( TestCase const& other ) const { + return test.get() == other.test.get() && + name == other.name && + className == other.className; + } + + bool TestCase::operator < ( TestCase const& other ) const { + return name < other.name; + } + TestCase& TestCase::operator = ( TestCase const& other ) { + TestCase temp( other ); + swap( temp ); + return *this; + } + + TestCaseInfo const& TestCase::getTestCaseInfo() const + { + return *this; + } + +} // end namespace Catch + +// #included from: catch_version.hpp +#define TWOBLUECUBES_CATCH_VERSION_HPP_INCLUDED + +namespace Catch { + + Version::Version + ( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ) + : majorVersion( _majorVersion ), + minorVersion( _minorVersion ), + patchNumber( _patchNumber ), + branchName( _branchName ), + buildNumber( _buildNumber ) + {} + + std::ostream& operator << ( std::ostream& os, Version const& version ) { + os << version.majorVersion << '.' + << version.minorVersion << '.' + << version.patchNumber; + // branchName is never null -> 0th char is \0 if it is empty + if (version.branchName[0]) { + os << '-' << version.branchName + << '.' << version.buildNumber; + } + return os; + } + + inline Version libraryVersion() { + static Version version( 1, 9, 4, "", 0 ); + return version; + } + +} + +// #included from: catch_message.hpp +#define TWOBLUECUBES_CATCH_MESSAGE_HPP_INCLUDED + +namespace Catch { + + MessageInfo::MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + type( _type ), + sequence( ++globalCount ) + {} + + // This may need protecting if threading support is added + unsigned int MessageInfo::globalCount = 0; + + //////////////////////////////////////////////////////////////////////////// + + ScopedMessage::ScopedMessage( MessageBuilder const& builder ) + : m_info( builder.m_info ) + { + m_info.message = builder.m_stream.str(); + getResultCapture().pushScopedMessage( m_info ); + } + ScopedMessage::ScopedMessage( ScopedMessage const& other ) + : m_info( other.m_info ) + {} + + ScopedMessage::~ScopedMessage() { + if ( !std::uncaught_exception() ){ + getResultCapture().popScopedMessage(m_info); + } + } + +} // end namespace Catch + +// #included from: catch_legacy_reporter_adapter.hpp +#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_HPP_INCLUDED + +// #included from: catch_legacy_reporter_adapter.h +#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_H_INCLUDED + +namespace Catch +{ + // Deprecated + struct IReporter : IShared { + virtual ~IReporter(); + + virtual bool shouldRedirectStdout() const = 0; + + virtual void StartTesting() = 0; + virtual void EndTesting( Totals const& totals ) = 0; + virtual void StartGroup( std::string const& groupName ) = 0; + virtual void EndGroup( std::string const& groupName, Totals const& totals ) = 0; + virtual void StartTestCase( TestCaseInfo const& testInfo ) = 0; + virtual void EndTestCase( TestCaseInfo const& testInfo, Totals const& totals, std::string const& stdOut, std::string const& stdErr ) = 0; + virtual void StartSection( std::string const& sectionName, std::string const& description ) = 0; + virtual void EndSection( std::string const& sectionName, Counts const& assertions ) = 0; + virtual void NoAssertionsInSection( std::string const& sectionName ) = 0; + virtual void NoAssertionsInTestCase( std::string const& testName ) = 0; + virtual void Aborted() = 0; + virtual void Result( AssertionResult const& result ) = 0; + }; + + class LegacyReporterAdapter : public SharedImpl<IStreamingReporter> + { + public: + LegacyReporterAdapter( Ptr<IReporter> const& legacyReporter ); + virtual ~LegacyReporterAdapter(); + + virtual ReporterPreferences getPreferences() const; + virtual void noMatchingTestCases( std::string const& ); + virtual void testRunStarting( TestRunInfo const& ); + virtual void testGroupStarting( GroupInfo const& groupInfo ); + virtual void testCaseStarting( TestCaseInfo const& testInfo ); + virtual void sectionStarting( SectionInfo const& sectionInfo ); + virtual void assertionStarting( AssertionInfo const& ); + virtual bool assertionEnded( AssertionStats const& assertionStats ); + virtual void sectionEnded( SectionStats const& sectionStats ); + virtual void testCaseEnded( TestCaseStats const& testCaseStats ); + virtual void testGroupEnded( TestGroupStats const& testGroupStats ); + virtual void testRunEnded( TestRunStats const& testRunStats ); + virtual void skipTest( TestCaseInfo const& ); + + private: + Ptr<IReporter> m_legacyReporter; + }; +} + +namespace Catch +{ + LegacyReporterAdapter::LegacyReporterAdapter( Ptr<IReporter> const& legacyReporter ) + : m_legacyReporter( legacyReporter ) + {} + LegacyReporterAdapter::~LegacyReporterAdapter() {} + + ReporterPreferences LegacyReporterAdapter::getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = m_legacyReporter->shouldRedirectStdout(); + return prefs; + } + + void LegacyReporterAdapter::noMatchingTestCases( std::string const& ) {} + void LegacyReporterAdapter::testRunStarting( TestRunInfo const& ) { + m_legacyReporter->StartTesting(); + } + void LegacyReporterAdapter::testGroupStarting( GroupInfo const& groupInfo ) { + m_legacyReporter->StartGroup( groupInfo.name ); + } + void LegacyReporterAdapter::testCaseStarting( TestCaseInfo const& testInfo ) { + m_legacyReporter->StartTestCase( testInfo ); + } + void LegacyReporterAdapter::sectionStarting( SectionInfo const& sectionInfo ) { + m_legacyReporter->StartSection( sectionInfo.name, sectionInfo.description ); + } + void LegacyReporterAdapter::assertionStarting( AssertionInfo const& ) { + // Not on legacy interface + } + + bool LegacyReporterAdapter::assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { + for( std::vector<MessageInfo>::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); + it != itEnd; + ++it ) { + if( it->type == ResultWas::Info ) { + ResultBuilder rb( it->macroName.c_str(), it->lineInfo, "", ResultDisposition::Normal ); + rb << it->message; + rb.setResultType( ResultWas::Info ); + AssertionResult result = rb.build(); + m_legacyReporter->Result( result ); + } + } + } + m_legacyReporter->Result( assertionStats.assertionResult ); + return true; + } + void LegacyReporterAdapter::sectionEnded( SectionStats const& sectionStats ) { + if( sectionStats.missingAssertions ) + m_legacyReporter->NoAssertionsInSection( sectionStats.sectionInfo.name ); + m_legacyReporter->EndSection( sectionStats.sectionInfo.name, sectionStats.assertions ); + } + void LegacyReporterAdapter::testCaseEnded( TestCaseStats const& testCaseStats ) { + m_legacyReporter->EndTestCase + ( testCaseStats.testInfo, + testCaseStats.totals, + testCaseStats.stdOut, + testCaseStats.stdErr ); + } + void LegacyReporterAdapter::testGroupEnded( TestGroupStats const& testGroupStats ) { + if( testGroupStats.aborting ) + m_legacyReporter->Aborted(); + m_legacyReporter->EndGroup( testGroupStats.groupInfo.name, testGroupStats.totals ); + } + void LegacyReporterAdapter::testRunEnded( TestRunStats const& testRunStats ) { + m_legacyReporter->EndTesting( testRunStats.totals ); + } + void LegacyReporterAdapter::skipTest( TestCaseInfo const& ) { + } +} + +// #included from: catch_timer.hpp + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++11-long-long" +#endif + +#ifdef CATCH_PLATFORM_WINDOWS + +#else + +#include <sys/time.h> + +#endif + +namespace Catch { + + namespace { +#ifdef CATCH_PLATFORM_WINDOWS + UInt64 getCurrentTicks() { + static UInt64 hz=0, hzo=0; + if (!hz) { + QueryPerformanceFrequency( reinterpret_cast<LARGE_INTEGER*>( &hz ) ); + QueryPerformanceCounter( reinterpret_cast<LARGE_INTEGER*>( &hzo ) ); + } + UInt64 t; + QueryPerformanceCounter( reinterpret_cast<LARGE_INTEGER*>( &t ) ); + return ((t-hzo)*1000000)/hz; + } +#else + UInt64 getCurrentTicks() { + timeval t; + gettimeofday(&t,CATCH_NULL); + return static_cast<UInt64>( t.tv_sec ) * 1000000ull + static_cast<UInt64>( t.tv_usec ); + } +#endif + } + + void Timer::start() { + m_ticks = getCurrentTicks(); + } + unsigned int Timer::getElapsedMicroseconds() const { + return static_cast<unsigned int>(getCurrentTicks() - m_ticks); + } + unsigned int Timer::getElapsedMilliseconds() const { + return static_cast<unsigned int>(getElapsedMicroseconds()/1000); + } + double Timer::getElapsedSeconds() const { + return getElapsedMicroseconds()/1000000.0; + } + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +// #included from: catch_common.hpp +#define TWOBLUECUBES_CATCH_COMMON_HPP_INCLUDED + +#include <cstring> +#include <cctype> + +namespace Catch { + + bool startsWith( std::string const& s, std::string const& prefix ) { + return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin()); + } + bool startsWith( std::string const& s, char prefix ) { + return !s.empty() && s[0] == prefix; + } + bool endsWith( std::string const& s, std::string const& suffix ) { + return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin()); + } + bool endsWith( std::string const& s, char suffix ) { + return !s.empty() && s[s.size()-1] == suffix; + } + bool contains( std::string const& s, std::string const& infix ) { + return s.find( infix ) != std::string::npos; + } + char toLowerCh(char c) { + return static_cast<char>( std::tolower( c ) ); + } + void toLowerInPlace( std::string& s ) { + std::transform( s.begin(), s.end(), s.begin(), toLowerCh ); + } + std::string toLower( std::string const& s ) { + std::string lc = s; + toLowerInPlace( lc ); + return lc; + } + std::string trim( std::string const& str ) { + static char const* whitespaceChars = "\n\r\t "; + std::string::size_type start = str.find_first_not_of( whitespaceChars ); + std::string::size_type end = str.find_last_not_of( whitespaceChars ); + + return start != std::string::npos ? str.substr( start, 1+end-start ) : std::string(); + } + + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { + bool replaced = false; + std::size_t i = str.find( replaceThis ); + while( i != std::string::npos ) { + replaced = true; + str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); + if( i < str.size()-withThis.size() ) + i = str.find( replaceThis, i+withThis.size() ); + else + i = std::string::npos; + } + return replaced; + } + + pluralise::pluralise( std::size_t count, std::string const& label ) + : m_count( count ), + m_label( label ) + {} + + std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { + os << pluraliser.m_count << ' ' << pluraliser.m_label; + if( pluraliser.m_count != 1 ) + os << 's'; + return os; + } + + SourceLineInfo::SourceLineInfo() : file(""), line( 0 ){} + SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line ) + : file( _file ), + line( _line ) + {} + bool SourceLineInfo::empty() const { + return file[0] == '\0'; + } + bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const { + return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0); + } + bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const { + return line < other.line || ( line == other.line && (std::strcmp(file, other.file) < 0)); + } + + void seedRng( IConfig const& config ) { + if( config.rngSeed() != 0 ) + std::srand( config.rngSeed() ); + } + unsigned int rngSeed() { + return getCurrentContext().getConfig()->rngSeed(); + } + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { +#ifndef __GNUG__ + os << info.file << '(' << info.line << ')'; +#else + os << info.file << ':' << info.line; +#endif + return os; + } + + void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ) { + std::ostringstream oss; + oss << locationInfo << ": Internal Catch error: '" << message << '\''; + if( alwaysTrue() ) + throw std::logic_error( oss.str() ); + } +} + +// #included from: catch_section.hpp +#define TWOBLUECUBES_CATCH_SECTION_HPP_INCLUDED + +namespace Catch { + + SectionInfo::SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& _description ) + : name( _name ), + description( _description ), + lineInfo( _lineInfo ) + {} + + Section::Section( SectionInfo const& info ) + : m_info( info ), + m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) + { + m_timer.start(); + } + + Section::~Section() { + if( m_sectionIncluded ) { + SectionEndInfo endInfo( m_info, m_assertions, m_timer.getElapsedSeconds() ); + if( std::uncaught_exception() ) + getResultCapture().sectionEndedEarly( endInfo ); + else + getResultCapture().sectionEnded( endInfo ); + } + } + + // This indicates whether the section should be executed or not + Section::operator bool() const { + return m_sectionIncluded; + } + +} // end namespace Catch + +// #included from: catch_debugger.hpp +#define TWOBLUECUBES_CATCH_DEBUGGER_HPP_INCLUDED + +#ifdef CATCH_PLATFORM_MAC + + #include <assert.h> + #include <stdbool.h> + #include <sys/types.h> + #include <unistd.h> + #include <sys/sysctl.h> + + namespace Catch{ + + // The following function is taken directly from the following technical note: + // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html + + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive(){ + + int mib[4]; + struct kinfo_proc info; + size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, CATCH_NULL, 0) != 0 ) { + Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; + return false; + } + + // We're being debugged if the P_TRACED flag is set. + + return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); + } + } // namespace Catch + +#elif defined(CATCH_PLATFORM_LINUX) + #include <fstream> + #include <string> + + namespace Catch{ + // The standard POSIX way of detecting a debugger is to attempt to + // ptrace() the process, but this needs to be done from a child and not + // this process itself to still allow attaching to this process later + // if wanted, so is rather heavy. Under Linux we have the PID of the + // "debugger" (which doesn't need to be gdb, of course, it could also + // be strace, for example) in /proc/$PID/status, so just get it from + // there instead. + bool isDebuggerActive(){ + // Libstdc++ has a bug, where std::ifstream sets errno to 0 + // This way our users can properly assert over errno values + ErrnoGuard guard; + std::ifstream in("/proc/self/status"); + for( std::string line; std::getline(in, line); ) { + static const int PREFIX_LEN = 11; + if( line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0 ) { + // We're traced if the PID is not 0 and no other PID starts + // with 0 digit, so it's enough to check for just a single + // character. + return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; + } + } + + return false; + } + } // namespace Catch +#elif defined(_MSC_VER) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#else + namespace Catch { + inline bool isDebuggerActive() { return false; } + } +#endif // Platform + +#ifdef CATCH_PLATFORM_WINDOWS + + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + ::OutputDebugStringA( text.c_str() ); + } + } +#else + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + // !TBD: Need a version for Mac/ XCode and other IDEs + Catch::cout() << text; + } + } +#endif // Platform + +// #included from: catch_tostring.hpp +#define TWOBLUECUBES_CATCH_TOSTRING_HPP_INCLUDED + +namespace Catch { + +namespace Detail { + + const std::string unprintableString = "{?}"; + + namespace { + const int hexThreshold = 255; + + struct Endianness { + enum Arch { Big, Little }; + + static Arch which() { + union _{ + int asInt; + char asChar[sizeof (int)]; + } u; + + u.asInt = 1; + return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; + } + }; + } + + std::string rawMemoryToString( const void *object, std::size_t size ) + { + // Reverse order for little endian architectures + int i = 0, end = static_cast<int>( size ), inc = 1; + if( Endianness::which() == Endianness::Little ) { + i = end-1; + end = inc = -1; + } + + unsigned char const *bytes = static_cast<unsigned char const *>(object); + std::ostringstream os; + os << "0x" << std::setfill('0') << std::hex; + for( ; i != end; i += inc ) + os << std::setw(2) << static_cast<unsigned>(bytes[i]); + return os.str(); + } +} + +std::string toString( std::string const& value ) { + std::string s = value; + if( getCurrentContext().getConfig()->showInvisibles() ) { + for(size_t i = 0; i < s.size(); ++i ) { + std::string subs; + switch( s[i] ) { + case '\n': subs = "\\n"; break; + case '\t': subs = "\\t"; break; + default: break; + } + if( !subs.empty() ) { + s = s.substr( 0, i ) + subs + s.substr( i+1 ); + ++i; + } + } + } + return '"' + s + '"'; +} +std::string toString( std::wstring const& value ) { + + std::string s; + s.reserve( value.size() ); + for(size_t i = 0; i < value.size(); ++i ) + s += value[i] <= 0xff ? static_cast<char>( value[i] ) : '?'; + return Catch::toString( s ); +} + +std::string toString( const char* const value ) { + return value ? Catch::toString( std::string( value ) ) : std::string( "{null string}" ); +} + +std::string toString( char* const value ) { + return Catch::toString( static_cast<const char*>( value ) ); +} + +std::string toString( const wchar_t* const value ) +{ + return value ? Catch::toString( std::wstring(value) ) : std::string( "{null string}" ); +} + +std::string toString( wchar_t* const value ) +{ + return Catch::toString( static_cast<const wchar_t*>( value ) ); +} + +std::string toString( int value ) { + std::ostringstream oss; + oss << value; + if( value > Detail::hexThreshold ) + oss << " (0x" << std::hex << value << ')'; + return oss.str(); +} + +std::string toString( unsigned long value ) { + std::ostringstream oss; + oss << value; + if( value > Detail::hexThreshold ) + oss << " (0x" << std::hex << value << ')'; + return oss.str(); +} + +std::string toString( unsigned int value ) { + return Catch::toString( static_cast<unsigned long>( value ) ); +} + +template<typename T> +std::string fpToString( T value, int precision ) { + std::ostringstream oss; + oss << std::setprecision( precision ) + << std::fixed + << value; + std::string d = oss.str(); + std::size_t i = d.find_last_not_of( '0' ); + if( i != std::string::npos && i != d.size()-1 ) { + if( d[i] == '.' ) + i++; + d = d.substr( 0, i+1 ); + } + return d; +} + +std::string toString( const double value ) { + return fpToString( value, 10 ); +} +std::string toString( const float value ) { + return fpToString( value, 5 ) + 'f'; +} + +std::string toString( bool value ) { + return value ? "true" : "false"; +} + +std::string toString( char value ) { + if ( value == '\r' ) + return "'\\r'"; + if ( value == '\f' ) + return "'\\f'"; + if ( value == '\n' ) + return "'\\n'"; + if ( value == '\t' ) + return "'\\t'"; + if ( '\0' <= value && value < ' ' ) + return toString( static_cast<unsigned int>( value ) ); + char chstr[] = "' '"; + chstr[1] = value; + return chstr; +} + +std::string toString( signed char value ) { + return toString( static_cast<char>( value ) ); +} + +std::string toString( unsigned char value ) { + return toString( static_cast<char>( value ) ); +} + +#ifdef CATCH_CONFIG_CPP11_LONG_LONG +std::string toString( long long value ) { + std::ostringstream oss; + oss << value; + if( value > Detail::hexThreshold ) + oss << " (0x" << std::hex << value << ')'; + return oss.str(); +} +std::string toString( unsigned long long value ) { + std::ostringstream oss; + oss << value; + if( value > Detail::hexThreshold ) + oss << " (0x" << std::hex << value << ')'; + return oss.str(); +} +#endif + +#ifdef CATCH_CONFIG_CPP11_NULLPTR +std::string toString( std::nullptr_t ) { + return "nullptr"; +} +#endif + +#ifdef __OBJC__ + std::string toString( NSString const * const& nsstring ) { + if( !nsstring ) + return "nil"; + return "@" + toString([nsstring UTF8String]); + } + std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ) { + if( !nsstring ) + return "nil"; + return "@" + toString([nsstring UTF8String]); + } + std::string toString( NSObject* const& nsObject ) { + return toString( [nsObject description] ); + } +#endif + +} // end namespace Catch + +// #included from: catch_result_builder.hpp +#define TWOBLUECUBES_CATCH_RESULT_BUILDER_HPP_INCLUDED + +namespace Catch { + + std::string capturedExpressionWithSecondArgument( std::string const& capturedExpression, std::string const& secondArg ) { + return secondArg.empty() || secondArg == "\"\"" + ? capturedExpression + : capturedExpression + ", " + secondArg; + } + ResultBuilder::ResultBuilder( char const* macroName, + SourceLineInfo const& lineInfo, + char const* capturedExpression, + ResultDisposition::Flags resultDisposition, + char const* secondArg ) + : m_assertionInfo( macroName, lineInfo, capturedExpressionWithSecondArgument( capturedExpression, secondArg ), resultDisposition ), + m_shouldDebugBreak( false ), + m_shouldThrow( false ), + m_guardException( false ) + {} + + ResultBuilder::~ResultBuilder() { +#if defined(CATCH_CONFIG_FAST_COMPILE) + if ( m_guardException ) { + m_stream.oss << "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"; + captureResult( ResultWas::ThrewException ); + getCurrentContext().getResultCapture()->exceptionEarlyReported(); + } +#endif + } + + ResultBuilder& ResultBuilder::setResultType( ResultWas::OfType result ) { + m_data.resultType = result; + return *this; + } + ResultBuilder& ResultBuilder::setResultType( bool result ) { + m_data.resultType = result ? ResultWas::Ok : ResultWas::ExpressionFailed; + return *this; + } + + void ResultBuilder::endExpression( DecomposedExpression const& expr ) { + AssertionResult result = build( expr ); + handleResult( result ); + } + + void ResultBuilder::useActiveException( ResultDisposition::Flags resultDisposition ) { + m_assertionInfo.resultDisposition = resultDisposition; + m_stream.oss << Catch::translateActiveException(); + captureResult( ResultWas::ThrewException ); + } + + void ResultBuilder::captureResult( ResultWas::OfType resultType ) { + setResultType( resultType ); + captureExpression(); + } + + void ResultBuilder::captureExpectedException( std::string const& expectedMessage ) { + if( expectedMessage.empty() ) + captureExpectedException( Matchers::Impl::MatchAllOf<std::string>() ); + else + captureExpectedException( Matchers::Equals( expectedMessage ) ); + } + + void ResultBuilder::captureExpectedException( Matchers::Impl::MatcherBase<std::string> const& matcher ) { + + assert( !isFalseTest( m_assertionInfo.resultDisposition ) ); + AssertionResultData data = m_data; + data.resultType = ResultWas::Ok; + data.reconstructedExpression = m_assertionInfo.capturedExpression; + + std::string actualMessage = Catch::translateActiveException(); + if( !matcher.match( actualMessage ) ) { + data.resultType = ResultWas::ExpressionFailed; + data.reconstructedExpression = actualMessage; + } + AssertionResult result( m_assertionInfo, data ); + handleResult( result ); + } + + void ResultBuilder::captureExpression() { + AssertionResult result = build(); + handleResult( result ); + } + + void ResultBuilder::handleResult( AssertionResult const& result ) + { + getResultCapture().assertionEnded( result ); + + if( !result.isOk() ) { + if( getCurrentContext().getConfig()->shouldDebugBreak() ) + m_shouldDebugBreak = true; + if( getCurrentContext().getRunner()->aborting() || (m_assertionInfo.resultDisposition & ResultDisposition::Normal) ) + m_shouldThrow = true; + } + } + + void ResultBuilder::react() { +#if defined(CATCH_CONFIG_FAST_COMPILE) + if (m_shouldDebugBreak) { + /////////////////////////////////////////////////////////////////// + // To inspect the state during test, you need to go one level up the callstack + // To go back to the test and change execution, jump over the throw statement + /////////////////////////////////////////////////////////////////// + CATCH_BREAK_INTO_DEBUGGER(); + } +#endif + if( m_shouldThrow ) + throw Catch::TestFailureException(); + } + + bool ResultBuilder::shouldDebugBreak() const { return m_shouldDebugBreak; } + bool ResultBuilder::allowThrows() const { return getCurrentContext().getConfig()->allowThrows(); } + + AssertionResult ResultBuilder::build() const + { + return build( *this ); + } + + // CAVEAT: The returned AssertionResult stores a pointer to the argument expr, + // a temporary DecomposedExpression, which in turn holds references to + // operands, possibly temporary as well. + // It should immediately be passed to handleResult; if the expression + // needs to be reported, its string expansion must be composed before + // the temporaries are destroyed. + AssertionResult ResultBuilder::build( DecomposedExpression const& expr ) const + { + assert( m_data.resultType != ResultWas::Unknown ); + AssertionResultData data = m_data; + + // Flip bool results if FalseTest flag is set + if( isFalseTest( m_assertionInfo.resultDisposition ) ) { + data.negate( expr.isBinaryExpression() ); + } + + data.message = m_stream.oss.str(); + data.decomposedExpression = &expr; // for lazy reconstruction + return AssertionResult( m_assertionInfo, data ); + } + + void ResultBuilder::reconstructExpression( std::string& dest ) const { + dest = m_assertionInfo.capturedExpression; + } + + void ResultBuilder::setExceptionGuard() { + m_guardException = true; + } + void ResultBuilder::unsetExceptionGuard() { + m_guardException = false; + } + +} // end namespace Catch + +// #included from: catch_tag_alias_registry.hpp +#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED + +namespace Catch { + + TagAliasRegistry::~TagAliasRegistry() {} + + Option<TagAlias> TagAliasRegistry::find( std::string const& alias ) const { + std::map<std::string, TagAlias>::const_iterator it = m_registry.find( alias ); + if( it != m_registry.end() ) + return it->second; + else + return Option<TagAlias>(); + } + + std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { + std::string expandedTestSpec = unexpandedTestSpec; + for( std::map<std::string, TagAlias>::const_iterator it = m_registry.begin(), itEnd = m_registry.end(); + it != itEnd; + ++it ) { + std::size_t pos = expandedTestSpec.find( it->first ); + if( pos != std::string::npos ) { + expandedTestSpec = expandedTestSpec.substr( 0, pos ) + + it->second.tag + + expandedTestSpec.substr( pos + it->first.size() ); + } + } + return expandedTestSpec; + } + + void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) { + + if( !startsWith( alias, "[@" ) || !endsWith( alias, ']' ) ) { + std::ostringstream oss; + oss << Colour( Colour::Red ) + << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" + << Colour( Colour::FileName ) + << lineInfo << '\n'; + throw std::domain_error( oss.str().c_str() ); + } + if( !m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second ) { + std::ostringstream oss; + oss << Colour( Colour::Red ) + << "error: tag alias, \"" << alias << "\" already registered.\n" + << "\tFirst seen at " + << Colour( Colour::Red ) << find(alias)->lineInfo << '\n' + << Colour( Colour::Red ) << "\tRedefined at " + << Colour( Colour::FileName) << lineInfo << '\n'; + throw std::domain_error( oss.str().c_str() ); + } + } + + ITagAliasRegistry::~ITagAliasRegistry() {} + + ITagAliasRegistry const& ITagAliasRegistry::get() { + return getRegistryHub().getTagAliasRegistry(); + } + + RegistrarForTagAliases::RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { + getMutableRegistryHub().registerTagAlias( alias, tag, lineInfo ); + } + +} // end namespace Catch + +// #included from: catch_matchers_string.hpp + +namespace Catch { +namespace Matchers { + + namespace StdString { + + CasedString::CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_str( adjustString( str ) ) + {} + std::string CasedString::adjustString( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No + ? toLower( str ) + : str; + } + std::string CasedString::caseSensitivitySuffix() const { + return m_caseSensitivity == CaseSensitive::No + ? " (case insensitive)" + : std::string(); + } + + StringMatcherBase::StringMatcherBase( std::string const& operation, CasedString const& comparator ) + : m_comparator( comparator ), + m_operation( operation ) { + } + + std::string StringMatcherBase::describe() const { + std::string description; + description.reserve(5 + m_operation.size() + m_comparator.m_str.size() + + m_comparator.caseSensitivitySuffix().size()); + description += m_operation; + description += ": \""; + description += m_comparator.m_str; + description += "\""; + description += m_comparator.caseSensitivitySuffix(); + return description; + } + + EqualsMatcher::EqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals", comparator ) {} + + bool EqualsMatcher::match( std::string const& source ) const { + return m_comparator.adjustString( source ) == m_comparator.m_str; + } + + ContainsMatcher::ContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains", comparator ) {} + + bool ContainsMatcher::match( std::string const& source ) const { + return contains( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with", comparator ) {} + + bool StartsWithMatcher::match( std::string const& source ) const { + return startsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with", comparator ) {} + + bool EndsWithMatcher::match( std::string const& source ) const { + return endsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + } // namespace StdString + + StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::EqualsMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::ContainsMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::EndsWithMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::StartsWithMatcher( StdString::CasedString( str, caseSensitivity) ); + } + +} // namespace Matchers +} // namespace Catch +// #included from: ../reporters/catch_reporter_multi.hpp +#define TWOBLUECUBES_CATCH_REPORTER_MULTI_HPP_INCLUDED + +namespace Catch { + +class MultipleReporters : public SharedImpl<IStreamingReporter> { + typedef std::vector<Ptr<IStreamingReporter> > Reporters; + Reporters m_reporters; + +public: + void add( Ptr<IStreamingReporter> const& reporter ) { + m_reporters.push_back( reporter ); + } + +public: // IStreamingReporter + + virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { + return m_reporters[0]->getPreferences(); + } + + virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->noMatchingTestCases( spec ); + } + + virtual void testRunStarting( TestRunInfo const& testRunInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testRunStarting( testRunInfo ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testGroupStarting( groupInfo ); + } + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testCaseStarting( testInfo ); + } + + virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->sectionStarting( sectionInfo ); + } + + virtual void assertionStarting( AssertionInfo const& assertionInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->assertionStarting( assertionInfo ); + } + + // The return value indicates if the messages buffer should be cleared: + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { + bool clearBuffer = false; + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + clearBuffer |= (*it)->assertionEnded( assertionStats ); + return clearBuffer; + } + + virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->sectionEnded( sectionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testCaseEnded( testCaseStats ); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testGroupEnded( testGroupStats ); + } + + virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testRunEnded( testRunStats ); + } + + virtual void skipTest( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->skipTest( testInfo ); + } + + virtual MultipleReporters* tryAsMulti() CATCH_OVERRIDE { + return this; + } + +}; + +Ptr<IStreamingReporter> addReporter( Ptr<IStreamingReporter> const& existingReporter, Ptr<IStreamingReporter> const& additionalReporter ) { + Ptr<IStreamingReporter> resultingReporter; + + if( existingReporter ) { + MultipleReporters* multi = existingReporter->tryAsMulti(); + if( !multi ) { + multi = new MultipleReporters; + resultingReporter = Ptr<IStreamingReporter>( multi ); + if( existingReporter ) + multi->add( existingReporter ); + } + else + resultingReporter = existingReporter; + multi->add( additionalReporter ); + } + else + resultingReporter = additionalReporter; + + return resultingReporter; +} + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_xml.hpp +#define TWOBLUECUBES_CATCH_REPORTER_XML_HPP_INCLUDED + +// #included from: catch_reporter_bases.hpp +#define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED + +#include <cstring> +#include <cfloat> +#include <cstdio> +#include <assert.h> + +namespace Catch { + + namespace { + // Because formatting using c++ streams is stateful, drop down to C is required + // Alternatively we could use stringstream, but its performance is... not good. + std::string getFormattedDuration( double duration ) { + // Max exponent + 1 is required to represent the whole part + // + 1 for decimal point + // + 3 for the 3 decimal places + // + 1 for null terminator + const size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1; + char buffer[maxDoubleSize]; + + // Save previous errno, to prevent sprintf from overwriting it + ErrnoGuard guard; +#ifdef _MSC_VER + sprintf_s(buffer, "%.3f", duration); +#else + sprintf(buffer, "%.3f", duration); +#endif + return std::string(buffer); + } + } + + struct StreamingReporterBase : SharedImpl<IStreamingReporter> { + + StreamingReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = false; + } + + virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { + return m_reporterPrefs; + } + + virtual ~StreamingReporterBase() CATCH_OVERRIDE; + + virtual void noMatchingTestCases( std::string const& ) CATCH_OVERRIDE {} + + virtual void testRunStarting( TestRunInfo const& _testRunInfo ) CATCH_OVERRIDE { + currentTestRunInfo = _testRunInfo; + } + virtual void testGroupStarting( GroupInfo const& _groupInfo ) CATCH_OVERRIDE { + currentGroupInfo = _groupInfo; + } + + virtual void testCaseStarting( TestCaseInfo const& _testInfo ) CATCH_OVERRIDE { + currentTestCaseInfo = _testInfo; + } + virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE { + m_sectionStack.push_back( _sectionInfo ); + } + + virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) CATCH_OVERRIDE { + m_sectionStack.pop_back(); + } + virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) CATCH_OVERRIDE { + currentTestCaseInfo.reset(); + } + virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) CATCH_OVERRIDE { + currentGroupInfo.reset(); + } + virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) CATCH_OVERRIDE { + currentTestCaseInfo.reset(); + currentGroupInfo.reset(); + currentTestRunInfo.reset(); + } + + virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE { + // Don't do anything with this by default. + // It can optionally be overridden in the derived class. + } + + Ptr<IConfig const> m_config; + std::ostream& stream; + + LazyStat<TestRunInfo> currentTestRunInfo; + LazyStat<GroupInfo> currentGroupInfo; + LazyStat<TestCaseInfo> currentTestCaseInfo; + + std::vector<SectionInfo> m_sectionStack; + ReporterPreferences m_reporterPrefs; + }; + + struct CumulativeReporterBase : SharedImpl<IStreamingReporter> { + template<typename T, typename ChildNodeT> + struct Node : SharedImpl<> { + explicit Node( T const& _value ) : value( _value ) {} + virtual ~Node() {} + + typedef std::vector<Ptr<ChildNodeT> > ChildNodes; + T value; + ChildNodes children; + }; + struct SectionNode : SharedImpl<> { + explicit SectionNode( SectionStats const& _stats ) : stats( _stats ) {} + virtual ~SectionNode(); + + bool operator == ( SectionNode const& other ) const { + return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; + } + bool operator == ( Ptr<SectionNode> const& other ) const { + return operator==( *other ); + } + + SectionStats stats; + typedef std::vector<Ptr<SectionNode> > ChildSections; + typedef std::vector<AssertionStats> Assertions; + ChildSections childSections; + Assertions assertions; + std::string stdOut; + std::string stdErr; + }; + + struct BySectionInfo { + BySectionInfo( SectionInfo const& other ) : m_other( other ) {} + BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} + bool operator() ( Ptr<SectionNode> const& node ) const { + return node->stats.sectionInfo.lineInfo == m_other.lineInfo; + } + private: + void operator=( BySectionInfo const& ); + SectionInfo const& m_other; + }; + + typedef Node<TestCaseStats, SectionNode> TestCaseNode; + typedef Node<TestGroupStats, TestCaseNode> TestGroupNode; + typedef Node<TestRunStats, TestGroupNode> TestRunNode; + + CumulativeReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = false; + } + ~CumulativeReporterBase(); + + virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { + return m_reporterPrefs; + } + + virtual void testRunStarting( TestRunInfo const& ) CATCH_OVERRIDE {} + virtual void testGroupStarting( GroupInfo const& ) CATCH_OVERRIDE {} + + virtual void testCaseStarting( TestCaseInfo const& ) CATCH_OVERRIDE {} + + virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { + SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); + Ptr<SectionNode> node; + if( m_sectionStack.empty() ) { + if( !m_rootSection ) + m_rootSection = new SectionNode( incompleteStats ); + node = m_rootSection; + } + else { + SectionNode& parentNode = *m_sectionStack.back(); + SectionNode::ChildSections::const_iterator it = + std::find_if( parentNode.childSections.begin(), + parentNode.childSections.end(), + BySectionInfo( sectionInfo ) ); + if( it == parentNode.childSections.end() ) { + node = new SectionNode( incompleteStats ); + parentNode.childSections.push_back( node ); + } + else + node = *it; + } + m_sectionStack.push_back( node ); + m_deepestSection = node; + } + + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {} + + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { + assert( !m_sectionStack.empty() ); + SectionNode& sectionNode = *m_sectionStack.back(); + sectionNode.assertions.push_back( assertionStats ); + // AssertionResult holds a pointer to a temporary DecomposedExpression, + // which getExpandedExpression() calls to build the expression string. + // Our section stack copy of the assertionResult will likely outlive the + // temporary, so it must be expanded or discarded now to avoid calling + // a destroyed object later. + prepareExpandedExpression( sectionNode.assertions.back().assertionResult ); + return true; + } + virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { + assert( !m_sectionStack.empty() ); + SectionNode& node = *m_sectionStack.back(); + node.stats = sectionStats; + m_sectionStack.pop_back(); + } + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { + Ptr<TestCaseNode> node = new TestCaseNode( testCaseStats ); + assert( m_sectionStack.size() == 0 ); + node->children.push_back( m_rootSection ); + m_testCases.push_back( node ); + m_rootSection.reset(); + + assert( m_deepestSection ); + m_deepestSection->stdOut = testCaseStats.stdOut; + m_deepestSection->stdErr = testCaseStats.stdErr; + } + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { + Ptr<TestGroupNode> node = new TestGroupNode( testGroupStats ); + node->children.swap( m_testCases ); + m_testGroups.push_back( node ); + } + virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { + Ptr<TestRunNode> node = new TestRunNode( testRunStats ); + node->children.swap( m_testGroups ); + m_testRuns.push_back( node ); + testRunEndedCumulative(); + } + virtual void testRunEndedCumulative() = 0; + + virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE {} + + virtual void prepareExpandedExpression( AssertionResult& result ) const { + if( result.isOk() ) + result.discardDecomposedExpression(); + else + result.expandDecomposedExpression(); + } + + Ptr<IConfig const> m_config; + std::ostream& stream; + std::vector<AssertionStats> m_assertions; + std::vector<std::vector<Ptr<SectionNode> > > m_sections; + std::vector<Ptr<TestCaseNode> > m_testCases; + std::vector<Ptr<TestGroupNode> > m_testGroups; + + std::vector<Ptr<TestRunNode> > m_testRuns; + + Ptr<SectionNode> m_rootSection; + Ptr<SectionNode> m_deepestSection; + std::vector<Ptr<SectionNode> > m_sectionStack; + ReporterPreferences m_reporterPrefs; + + }; + + template<char C> + char const* getLineOfChars() { + static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; + if( !*line ) { + std::memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); + line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; + } + return line; + } + + struct TestEventListenerBase : StreamingReporterBase { + TestEventListenerBase( ReporterConfig const& _config ) + : StreamingReporterBase( _config ) + {} + + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {} + virtual bool assertionEnded( AssertionStats const& ) CATCH_OVERRIDE { + return false; + } + }; + +} // end namespace Catch + +// #included from: ../internal/catch_reporter_registrars.hpp +#define TWOBLUECUBES_CATCH_REPORTER_REGISTRARS_HPP_INCLUDED + +namespace Catch { + + template<typename T> + class LegacyReporterRegistrar { + + class ReporterFactory : public IReporterFactory { + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new LegacyReporterAdapter( new T( config ) ); + } + + virtual std::string getDescription() const { + return T::getDescription(); + } + }; + + public: + + LegacyReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); + } + }; + + template<typename T> + class ReporterRegistrar { + + class ReporterFactory : public SharedImpl<IReporterFactory> { + + // *** Please Note ***: + // - If you end up here looking at a compiler error because it's trying to register + // your custom reporter class be aware that the native reporter interface has changed + // to IStreamingReporter. The "legacy" interface, IReporter, is still supported via + // an adapter. Just use REGISTER_LEGACY_REPORTER to take advantage of the adapter. + // However please consider updating to the new interface as the old one is now + // deprecated and will probably be removed quite soon! + // Please contact me via github if you have any questions at all about this. + // In fact, ideally, please contact me anyway to let me know you've hit this - as I have + // no idea who is actually using custom reporters at all (possibly no-one!). + // The new interface is designed to minimise exposure to interface changes in the future. + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new T( config ); + } + + virtual std::string getDescription() const { + return T::getDescription(); + } + }; + + public: + + ReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); + } + }; + + template<typename T> + class ListenerRegistrar { + + class ListenerFactory : public SharedImpl<IReporterFactory> { + + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new T( config ); + } + virtual std::string getDescription() const { + return std::string(); + } + }; + + public: + + ListenerRegistrar() { + getMutableRegistryHub().registerListener( new ListenerFactory() ); + } + }; +} + +#define INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) \ + namespace{ Catch::LegacyReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); } + +#define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \ + namespace{ Catch::ReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); } + +// Deprecated - use the form without INTERNAL_ +#define INTERNAL_CATCH_REGISTER_LISTENER( listenerType ) \ + namespace{ Catch::ListenerRegistrar<listenerType> catch_internal_RegistrarFor##listenerType; } + +#define CATCH_REGISTER_LISTENER( listenerType ) \ + namespace{ Catch::ListenerRegistrar<listenerType> catch_internal_RegistrarFor##listenerType; } + +// #included from: ../internal/catch_xmlwriter.hpp +#define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED + +#include <sstream> +#include <string> +#include <vector> +#include <iomanip> + +namespace Catch { + + class XmlEncode { + public: + enum ForWhat { ForTextNodes, ForAttributes }; + + XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ) + : m_str( str ), + m_forWhat( forWhat ) + {} + + void encodeTo( std::ostream& os ) const { + + // Apostrophe escaping not necessary if we always use " to write attributes + // (see: http://www.w3.org/TR/xml/#syntax) + + for( std::size_t i = 0; i < m_str.size(); ++ i ) { + char c = m_str[i]; + switch( c ) { + case '<': os << "<"; break; + case '&': os << "&"; break; + + case '>': + // See: http://www.w3.org/TR/xml/#syntax + if( i > 2 && m_str[i-1] == ']' && m_str[i-2] == ']' ) + os << ">"; + else + os << c; + break; + + case '\"': + if( m_forWhat == ForAttributes ) + os << """; + else + os << c; + break; + + default: + // Escape control chars - based on contribution by @espenalb in PR #465 and + // by @mrpi PR #588 + if ( ( c >= 0 && c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' ) { + // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + os << "\\x" << std::uppercase << std::hex << std::setfill('0') << std::setw(2) + << static_cast<int>( c ); + } + else + os << c; + } + } + } + + friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { + xmlEncode.encodeTo( os ); + return os; + } + + private: + std::string m_str; + ForWhat m_forWhat; + }; + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer ) + : m_writer( writer ) + {} + + ScopedElement( ScopedElement const& other ) + : m_writer( other.m_writer ){ + other.m_writer = CATCH_NULL; + } + + ~ScopedElement() { + if( m_writer ) + m_writer->endElement(); + } + + ScopedElement& writeText( std::string const& text, bool indent = true ) { + m_writer->writeText( text, indent ); + return *this; + } + + template<typename T> + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer; + }; + + XmlWriter() + : m_tagIsOpen( false ), + m_needsNewline( false ), + m_os( Catch::cout() ) + { + writeDeclaration(); + } + + XmlWriter( std::ostream& os ) + : m_tagIsOpen( false ), + m_needsNewline( false ), + m_os( os ) + { + writeDeclaration(); + } + + ~XmlWriter() { + while( !m_tags.empty() ) + endElement(); + } + + XmlWriter& startElement( std::string const& name ) { + ensureTagClosed(); + newlineIfNecessary(); + m_os << m_indent << '<' << name; + m_tags.push_back( name ); + m_indent += " "; + m_tagIsOpen = true; + return *this; + } + + ScopedElement scopedElement( std::string const& name ) { + ScopedElement scoped( this ); + startElement( name ); + return scoped; + } + + XmlWriter& endElement() { + newlineIfNecessary(); + m_indent = m_indent.substr( 0, m_indent.size()-2 ); + if( m_tagIsOpen ) { + m_os << "/>"; + m_tagIsOpen = false; + } + else { + m_os << m_indent << "</" << m_tags.back() << ">"; + } + m_os << std::endl; + m_tags.pop_back(); + return *this; + } + + XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) { + if( !name.empty() && !attribute.empty() ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; + } + + XmlWriter& writeAttribute( std::string const& name, bool attribute ) { + m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; + return *this; + } + + template<typename T> + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + std::ostringstream oss; + oss << attribute; + return writeAttribute( name, oss.str() ); + } + + XmlWriter& writeText( std::string const& text, bool indent = true ) { + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if( tagWasOpen && indent ) + m_os << m_indent; + m_os << XmlEncode( text ); + m_needsNewline = true; + } + return *this; + } + + XmlWriter& writeComment( std::string const& text ) { + ensureTagClosed(); + m_os << m_indent << "<!--" << text << "-->"; + m_needsNewline = true; + return *this; + } + + void writeStylesheetRef( std::string const& url ) { + m_os << "<?xml-stylesheet type=\"text/xsl\" href=\"" << url << "\"?>\n"; + } + + XmlWriter& writeBlankLine() { + ensureTagClosed(); + m_os << '\n'; + return *this; + } + + void ensureTagClosed() { + if( m_tagIsOpen ) { + m_os << ">" << std::endl; + m_tagIsOpen = false; + } + } + + private: + XmlWriter( XmlWriter const& ); + void operator=( XmlWriter const& ); + + void writeDeclaration() { + m_os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; + } + + void newlineIfNecessary() { + if( m_needsNewline ) { + m_os << std::endl; + m_needsNewline = false; + } + } + + bool m_tagIsOpen; + bool m_needsNewline; + std::vector<std::string> m_tags; + std::string m_indent; + std::ostream& m_os; + }; + +} +// #included from: catch_reenable_warnings.h + +#define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(pop) +# else +# pragma clang diagnostic pop +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic pop +#endif + + +namespace Catch { + class XmlReporter : public StreamingReporterBase { + public: + XmlReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_xml(_config.stream()), + m_sectionDepth( 0 ) + { + m_reporterPrefs.shouldRedirectStdOut = true; + } + + virtual ~XmlReporter() CATCH_OVERRIDE; + + static std::string getDescription() { + return "Reports test results as an XML document"; + } + + virtual std::string getStylesheetRef() const { + return std::string(); + } + + void writeSourceInfo( SourceLineInfo const& sourceInfo ) { + m_xml + .writeAttribute( "filename", sourceInfo.file ) + .writeAttribute( "line", sourceInfo.line ); + } + + public: // StreamingReporterBase + + virtual void noMatchingTestCases( std::string const& s ) CATCH_OVERRIDE { + StreamingReporterBase::noMatchingTestCases( s ); + } + + virtual void testRunStarting( TestRunInfo const& testInfo ) CATCH_OVERRIDE { + StreamingReporterBase::testRunStarting( testInfo ); + std::string stylesheetRef = getStylesheetRef(); + if( !stylesheetRef.empty() ) + m_xml.writeStylesheetRef( stylesheetRef ); + m_xml.startElement( "Catch" ); + if( !m_config->name().empty() ) + m_xml.writeAttribute( "name", m_config->name() ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { + StreamingReporterBase::testGroupStarting( groupInfo ); + m_xml.startElement( "Group" ) + .writeAttribute( "name", groupInfo.name ); + } + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { + StreamingReporterBase::testCaseStarting(testInfo); + m_xml.startElement( "TestCase" ) + .writeAttribute( "name", trim( testInfo.name ) ) + .writeAttribute( "description", testInfo.description ) + .writeAttribute( "tags", testInfo.tagsAsString ); + + writeSourceInfo( testInfo.lineInfo ); + + if ( m_config->showDurations() == ShowDurations::Always ) + m_testCaseTimer.start(); + m_xml.ensureTagClosed(); + } + + virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { + StreamingReporterBase::sectionStarting( sectionInfo ); + if( m_sectionDepth++ > 0 ) { + m_xml.startElement( "Section" ) + .writeAttribute( "name", trim( sectionInfo.name ) ) + .writeAttribute( "description", sectionInfo.description ); + writeSourceInfo( sectionInfo.lineInfo ); + m_xml.ensureTagClosed(); + } + } + + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { } + + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { + + AssertionResult const& result = assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + if( includeResults ) { + // Print any info messages in <Info> tags. + for( std::vector<MessageInfo>::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); + it != itEnd; + ++it ) { + if( it->type == ResultWas::Info ) { + m_xml.scopedElement( "Info" ) + .writeText( it->message ); + } else if ( it->type == ResultWas::Warning ) { + m_xml.scopedElement( "Warning" ) + .writeText( it->message ); + } + } + } + + // Drop out if result was successful but we're not printing them. + if( !includeResults && result.getResultType() != ResultWas::Warning ) + return true; + + // Print the expression if there is one. + if( result.hasExpression() ) { + m_xml.startElement( "Expression" ) + .writeAttribute( "success", result.succeeded() ) + .writeAttribute( "type", result.getTestMacroName() ); + + writeSourceInfo( result.getSourceInfo() ); + + m_xml.scopedElement( "Original" ) + .writeText( result.getExpression() ); + m_xml.scopedElement( "Expanded" ) + .writeText( result.getExpandedExpression() ); + } + + // And... Print a result applicable to each result type. + switch( result.getResultType() ) { + case ResultWas::ThrewException: + m_xml.startElement( "Exception" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::FatalErrorCondition: + m_xml.startElement( "FatalErrorCondition" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::Info: + m_xml.scopedElement( "Info" ) + .writeText( result.getMessage() ); + break; + case ResultWas::Warning: + // Warning will already have been written + break; + case ResultWas::ExplicitFailure: + m_xml.startElement( "Failure" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + default: + break; + } + + if( result.hasExpression() ) + m_xml.endElement(); + + return true; + } + + virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { + StreamingReporterBase::sectionEnded( sectionStats ); + if( --m_sectionDepth > 0 ) { + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); + e.writeAttribute( "successes", sectionStats.assertions.passed ); + e.writeAttribute( "failures", sectionStats.assertions.failed ); + e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); + + m_xml.endElement(); + } + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { + StreamingReporterBase::testCaseEnded( testCaseStats ); + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); + e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); + + if( !testCaseStats.stdOut.empty() ) + m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), false ); + if( !testCaseStats.stdErr.empty() ) + m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), false ); + + m_xml.endElement(); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { + StreamingReporterBase::testGroupEnded( testGroupStats ); + // TODO: Check testGroupStats.aborting and act accordingly. + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) + .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { + StreamingReporterBase::testRunEnded( testRunStats ); + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testRunStats.totals.assertions.passed ) + .writeAttribute( "failures", testRunStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + private: + Timer m_testCaseTimer; + XmlWriter m_xml; + int m_sectionDepth; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "xml", XmlReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_junit.hpp +#define TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED + +#include <assert.h> + +namespace Catch { + + namespace { + std::string getCurrentTimestamp() { + // Beware, this is not reentrant because of backward compatibility issues + // Also, UTC only, again because of backward compatibility (%z is C++11) + time_t rawtime; + std::time(&rawtime); + const size_t timeStampSize = sizeof("2017-01-16T17:06:45Z"); + +#ifdef _MSC_VER + std::tm timeInfo = {}; + gmtime_s(&timeInfo, &rawtime); +#else + std::tm* timeInfo; + timeInfo = std::gmtime(&rawtime); +#endif + + char timeStamp[timeStampSize]; + const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; + +#ifdef _MSC_VER + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); +#else + std::strftime(timeStamp, timeStampSize, fmt, timeInfo); +#endif + return std::string(timeStamp); + } + + } + + class JunitReporter : public CumulativeReporterBase { + public: + JunitReporter( ReporterConfig const& _config ) + : CumulativeReporterBase( _config ), + xml( _config.stream() ), + m_okToFail( false ) + { + m_reporterPrefs.shouldRedirectStdOut = true; + } + + virtual ~JunitReporter() CATCH_OVERRIDE; + + static std::string getDescription() { + return "Reports test results in an XML format that looks like Ant's junitreport target"; + } + + virtual void noMatchingTestCases( std::string const& /*spec*/ ) CATCH_OVERRIDE {} + + virtual void testRunStarting( TestRunInfo const& runInfo ) CATCH_OVERRIDE { + CumulativeReporterBase::testRunStarting( runInfo ); + xml.startElement( "testsuites" ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { + suiteTimer.start(); + stdOutForSuite.str(""); + stdErrForSuite.str(""); + unexpectedExceptions = 0; + CumulativeReporterBase::testGroupStarting( groupInfo ); + } + + virtual void testCaseStarting( TestCaseInfo const& testCaseInfo ) CATCH_OVERRIDE { + m_okToFail = testCaseInfo.okToFail(); + } + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { + if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail ) + unexpectedExceptions++; + return CumulativeReporterBase::assertionEnded( assertionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { + stdOutForSuite << testCaseStats.stdOut; + stdErrForSuite << testCaseStats.stdErr; + CumulativeReporterBase::testCaseEnded( testCaseStats ); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { + double suiteTime = suiteTimer.getElapsedSeconds(); + CumulativeReporterBase::testGroupEnded( testGroupStats ); + writeGroup( *m_testGroups.back(), suiteTime ); + } + + virtual void testRunEndedCumulative() CATCH_OVERRIDE { + xml.endElement(); + } + + void writeGroup( TestGroupNode const& groupNode, double suiteTime ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); + TestGroupStats const& stats = groupNode.value; + xml.writeAttribute( "name", stats.groupInfo.name ); + xml.writeAttribute( "errors", unexpectedExceptions ); + xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); + xml.writeAttribute( "tests", stats.totals.assertions.total() ); + xml.writeAttribute( "hostname", "tbd" ); // !TBD + if( m_config->showDurations() == ShowDurations::Never ) + xml.writeAttribute( "time", "" ); + else + xml.writeAttribute( "time", suiteTime ); + xml.writeAttribute( "timestamp", getCurrentTimestamp() ); + + // Write test cases + for( TestGroupNode::ChildNodes::const_iterator + it = groupNode.children.begin(), itEnd = groupNode.children.end(); + it != itEnd; + ++it ) + writeTestCase( **it ); + + xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false ); + xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false ); + } + + void writeTestCase( TestCaseNode const& testCaseNode ) { + TestCaseStats const& stats = testCaseNode.value; + + // All test cases have exactly one section - which represents the + // test case itself. That section may have 0-n nested sections + assert( testCaseNode.children.size() == 1 ); + SectionNode const& rootSection = *testCaseNode.children.front(); + + std::string className = stats.testInfo.className; + + if( className.empty() ) { + if( rootSection.childSections.empty() ) + className = "global"; + } + writeSection( className, "", rootSection ); + } + + void writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode ) { + std::string name = trim( sectionNode.stats.sectionInfo.name ); + if( !rootName.empty() ) + name = rootName + '/' + name; + + if( !sectionNode.assertions.empty() || + !sectionNode.stdOut.empty() || + !sectionNode.stdErr.empty() ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); + if( className.empty() ) { + xml.writeAttribute( "classname", name ); + xml.writeAttribute( "name", "root" ); + } + else { + xml.writeAttribute( "classname", className ); + xml.writeAttribute( "name", name ); + } + xml.writeAttribute( "time", Catch::toString( sectionNode.stats.durationInSeconds ) ); + + writeAssertions( sectionNode ); + + if( !sectionNode.stdOut.empty() ) + xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); + if( !sectionNode.stdErr.empty() ) + xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); + } + for( SectionNode::ChildSections::const_iterator + it = sectionNode.childSections.begin(), + itEnd = sectionNode.childSections.end(); + it != itEnd; + ++it ) + if( className.empty() ) + writeSection( name, "", **it ); + else + writeSection( className, name, **it ); + } + + void writeAssertions( SectionNode const& sectionNode ) { + for( SectionNode::Assertions::const_iterator + it = sectionNode.assertions.begin(), itEnd = sectionNode.assertions.end(); + it != itEnd; + ++it ) + writeAssertion( *it ); + } + void writeAssertion( AssertionStats const& stats ) { + AssertionResult const& result = stats.assertionResult; + if( !result.isOk() ) { + std::string elementName; + switch( result.getResultType() ) { + case ResultWas::ThrewException: + case ResultWas::FatalErrorCondition: + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + elementName = "failure"; + break; + case ResultWas::ExpressionFailed: + elementName = "failure"; + break; + case ResultWas::DidntThrowException: + elementName = "failure"; + break; + + // We should never see these here: + case ResultWas::Info: + case ResultWas::Warning: + case ResultWas::Ok: + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + elementName = "internalError"; + break; + } + + XmlWriter::ScopedElement e = xml.scopedElement( elementName ); + + xml.writeAttribute( "message", result.getExpandedExpression() ); + xml.writeAttribute( "type", result.getTestMacroName() ); + + std::ostringstream oss; + if( !result.getMessage().empty() ) + oss << result.getMessage() << '\n'; + for( std::vector<MessageInfo>::const_iterator + it = stats.infoMessages.begin(), + itEnd = stats.infoMessages.end(); + it != itEnd; + ++it ) + if( it->type == ResultWas::Info ) + oss << it->message << '\n'; + + oss << "at " << result.getSourceInfo(); + xml.writeText( oss.str(), false ); + } + } + + XmlWriter xml; + Timer suiteTimer; + std::ostringstream stdOutForSuite; + std::ostringstream stdErrForSuite; + unsigned int unexpectedExceptions; + bool m_okToFail; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_console.hpp +#define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED + +#include <cfloat> +#include <cstdio> + +namespace Catch { + + struct ConsoleReporter : StreamingReporterBase { + ConsoleReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_headerPrinted( false ) + {} + + virtual ~ConsoleReporter() CATCH_OVERRIDE; + static std::string getDescription() { + return "Reports test results as plain lines of text"; + } + + virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE { + stream << "No test cases matched '" << spec << '\'' << std::endl; + } + + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { + } + + virtual bool assertionEnded( AssertionStats const& _assertionStats ) CATCH_OVERRIDE { + AssertionResult const& result = _assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + // Drop out if result was successful but we're not printing them. + if( !includeResults && result.getResultType() != ResultWas::Warning ) + return false; + + lazyPrint(); + + AssertionPrinter printer( stream, _assertionStats, includeResults ); + printer.print(); + stream << std::endl; + return true; + } + + virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE { + m_headerPrinted = false; + StreamingReporterBase::sectionStarting( _sectionInfo ); + } + virtual void sectionEnded( SectionStats const& _sectionStats ) CATCH_OVERRIDE { + if( _sectionStats.missingAssertions ) { + lazyPrint(); + Colour colour( Colour::ResultError ); + if( m_sectionStack.size() > 1 ) + stream << "\nNo assertions in section"; + else + stream << "\nNo assertions in test case"; + stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; + } + if( m_config->showDurations() == ShowDurations::Always ) { + stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + } + if( m_headerPrinted ) { + m_headerPrinted = false; + } + StreamingReporterBase::sectionEnded( _sectionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) CATCH_OVERRIDE { + StreamingReporterBase::testCaseEnded( _testCaseStats ); + m_headerPrinted = false; + } + virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) CATCH_OVERRIDE { + if( currentGroupInfo.used ) { + printSummaryDivider(); + stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; + printTotals( _testGroupStats.totals ); + stream << '\n' << std::endl; + } + StreamingReporterBase::testGroupEnded( _testGroupStats ); + } + virtual void testRunEnded( TestRunStats const& _testRunStats ) CATCH_OVERRIDE { + printTotalsDivider( _testRunStats.totals ); + printTotals( _testRunStats.totals ); + stream << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + private: + + class AssertionPrinter { + void operator= ( AssertionPrinter const& ); + public: + AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) + : stream( _stream ), + stats( _stats ), + result( _stats.assertionResult ), + colour( Colour::None ), + message( result.getMessage() ), + messages( _stats.infoMessages ), + printInfoMessages( _printInfoMessages ) + { + switch( result.getResultType() ) { + case ResultWas::Ok: + colour = Colour::Success; + passOrFail = "PASSED"; + //if( result.hasMessage() ) + if( _stats.infoMessages.size() == 1 ) + messageLabel = "with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "with messages"; + break; + case ResultWas::ExpressionFailed: + if( result.isOk() ) { + colour = Colour::Success; + passOrFail = "FAILED - but was ok"; + } + else { + colour = Colour::Error; + passOrFail = "FAILED"; + } + if( _stats.infoMessages.size() == 1 ) + messageLabel = "with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "with messages"; + break; + case ResultWas::ThrewException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to unexpected exception with "; + if (_stats.infoMessages.size() == 1) + messageLabel += "message"; + if (_stats.infoMessages.size() > 1) + messageLabel += "messages"; + break; + case ResultWas::FatalErrorCondition: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to a fatal error condition"; + break; + case ResultWas::DidntThrowException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "because no exception was thrown where one was expected"; + break; + case ResultWas::Info: + messageLabel = "info"; + break; + case ResultWas::Warning: + messageLabel = "warning"; + break; + case ResultWas::ExplicitFailure: + passOrFail = "FAILED"; + colour = Colour::Error; + if( _stats.infoMessages.size() == 1 ) + messageLabel = "explicitly with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "explicitly with messages"; + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + passOrFail = "** internal error **"; + colour = Colour::Error; + break; + } + } + + void print() const { + printSourceInfo(); + if( stats.totals.assertions.total() > 0 ) { + if( result.isOk() ) + stream << '\n'; + printResultType(); + printOriginalExpression(); + printReconstructedExpression(); + } + else { + stream << '\n'; + } + printMessage(); + } + + private: + void printResultType() const { + if( !passOrFail.empty() ) { + Colour colourGuard( colour ); + stream << passOrFail << ":\n"; + } + } + void printOriginalExpression() const { + if( result.hasExpression() ) { + Colour colourGuard( Colour::OriginalExpression ); + stream << " "; + stream << result.getExpressionInMacro(); + stream << '\n'; + } + } + void printReconstructedExpression() const { + if( result.hasExpandedExpression() ) { + stream << "with expansion:\n"; + Colour colourGuard( Colour::ReconstructedExpression ); + stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << '\n'; + } + } + void printMessage() const { + if( !messageLabel.empty() ) + stream << messageLabel << ':' << '\n'; + for( std::vector<MessageInfo>::const_iterator it = messages.begin(), itEnd = messages.end(); + it != itEnd; + ++it ) { + // If this assertion is a warning ignore any INFO messages + if( printInfoMessages || it->type != ResultWas::Info ) + stream << Text( it->message, TextAttributes().setIndent(2) ) << '\n'; + } + } + void printSourceInfo() const { + Colour colourGuard( Colour::FileName ); + stream << result.getSourceInfo() << ": "; + } + + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + Colour::Code colour; + std::string passOrFail; + std::string messageLabel; + std::string message; + std::vector<MessageInfo> messages; + bool printInfoMessages; + }; + + void lazyPrint() { + + if( !currentTestRunInfo.used ) + lazyPrintRunInfo(); + if( !currentGroupInfo.used ) + lazyPrintGroupInfo(); + + if( !m_headerPrinted ) { + printTestCaseAndSectionHeader(); + m_headerPrinted = true; + } + } + void lazyPrintRunInfo() { + stream << '\n' << getLineOfChars<'~'>() << '\n'; + Colour colour( Colour::SecondaryText ); + stream << currentTestRunInfo->name + << " is a Catch v" << libraryVersion() << " host application.\n" + << "Run with -? for options\n\n"; + + if( m_config->rngSeed() != 0 ) + stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; + + currentTestRunInfo.used = true; + } + void lazyPrintGroupInfo() { + if( !currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1 ) { + printClosedHeader( "Group: " + currentGroupInfo->name ); + currentGroupInfo.used = true; + } + } + void printTestCaseAndSectionHeader() { + assert( !m_sectionStack.empty() ); + printOpenHeader( currentTestCaseInfo->name ); + + if( m_sectionStack.size() > 1 ) { + Colour colourGuard( Colour::Headers ); + + std::vector<SectionInfo>::const_iterator + it = m_sectionStack.begin()+1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for( ; it != itEnd; ++it ) + printHeaderString( it->name, 2 ); + } + + SourceLineInfo lineInfo = m_sectionStack.back().lineInfo; + + if( !lineInfo.empty() ){ + stream << getLineOfChars<'-'>() << '\n'; + Colour colourGuard( Colour::FileName ); + stream << lineInfo << '\n'; + } + stream << getLineOfChars<'.'>() << '\n' << std::endl; + } + + void printClosedHeader( std::string const& _name ) { + printOpenHeader( _name ); + stream << getLineOfChars<'.'>() << '\n'; + } + void printOpenHeader( std::string const& _name ) { + stream << getLineOfChars<'-'>() << '\n'; + { + Colour colourGuard( Colour::Headers ); + printHeaderString( _name ); + } + } + + // if string has a : in first line will set indent to follow it on + // subsequent lines + void printHeaderString( std::string const& _string, std::size_t indent = 0 ) { + std::size_t i = _string.find( ": " ); + if( i != std::string::npos ) + i+=2; + else + i = 0; + stream << Text( _string, TextAttributes() + .setIndent( indent+i) + .setInitialIndent( indent ) ) << '\n'; + } + + struct SummaryColumn { + + SummaryColumn( std::string const& _label, Colour::Code _colour ) + : label( _label ), + colour( _colour ) + {} + SummaryColumn addRow( std::size_t count ) { + std::ostringstream oss; + oss << count; + std::string row = oss.str(); + for( std::vector<std::string>::iterator it = rows.begin(); it != rows.end(); ++it ) { + while( it->size() < row.size() ) + *it = ' ' + *it; + while( it->size() > row.size() ) + row = ' ' + row; + } + rows.push_back( row ); + return *this; + } + + std::string label; + Colour::Code colour; + std::vector<std::string> rows; + + }; + + void printTotals( Totals const& totals ) { + if( totals.testCases.total() == 0 ) { + stream << Colour( Colour::Warning ) << "No tests ran\n"; + } + else if( totals.assertions.total() > 0 && totals.testCases.allPassed() ) { + stream << Colour( Colour::ResultSuccess ) << "All tests passed"; + stream << " (" + << pluralise( totals.assertions.passed, "assertion" ) << " in " + << pluralise( totals.testCases.passed, "test case" ) << ')' + << '\n'; + } + else { + + std::vector<SummaryColumn> columns; + columns.push_back( SummaryColumn( "", Colour::None ) + .addRow( totals.testCases.total() ) + .addRow( totals.assertions.total() ) ); + columns.push_back( SummaryColumn( "passed", Colour::Success ) + .addRow( totals.testCases.passed ) + .addRow( totals.assertions.passed ) ); + columns.push_back( SummaryColumn( "failed", Colour::ResultError ) + .addRow( totals.testCases.failed ) + .addRow( totals.assertions.failed ) ); + columns.push_back( SummaryColumn( "failed as expected", Colour::ResultExpectedFailure ) + .addRow( totals.testCases.failedButOk ) + .addRow( totals.assertions.failedButOk ) ); + + printSummaryRow( "test cases", columns, 0 ); + printSummaryRow( "assertions", columns, 1 ); + } + } + void printSummaryRow( std::string const& label, std::vector<SummaryColumn> const& cols, std::size_t row ) { + for( std::vector<SummaryColumn>::const_iterator it = cols.begin(); it != cols.end(); ++it ) { + std::string value = it->rows[row]; + if( it->label.empty() ) { + stream << label << ": "; + if( value != "0" ) + stream << value; + else + stream << Colour( Colour::Warning ) << "- none -"; + } + else if( value != "0" ) { + stream << Colour( Colour::LightGrey ) << " | "; + stream << Colour( it->colour ) + << value << ' ' << it->label; + } + } + stream << '\n'; + } + + static std::size_t makeRatio( std::size_t number, std::size_t total ) { + std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number/ total : 0; + return ( ratio == 0 && number > 0 ) ? 1 : ratio; + } + static std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) { + if( i > j && i > k ) + return i; + else if( j > k ) + return j; + else + return k; + } + + void printTotalsDivider( Totals const& totals ) { + if( totals.testCases.total() > 0 ) { + std::size_t failedRatio = makeRatio( totals.testCases.failed, totals.testCases.total() ); + std::size_t failedButOkRatio = makeRatio( totals.testCases.failedButOk, totals.testCases.total() ); + std::size_t passedRatio = makeRatio( totals.testCases.passed, totals.testCases.total() ); + while( failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH-1 ) + findMax( failedRatio, failedButOkRatio, passedRatio )++; + while( failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH-1 ) + findMax( failedRatio, failedButOkRatio, passedRatio )--; + + stream << Colour( Colour::Error ) << std::string( failedRatio, '=' ); + stream << Colour( Colour::ResultExpectedFailure ) << std::string( failedButOkRatio, '=' ); + if( totals.testCases.allPassed() ) + stream << Colour( Colour::ResultSuccess ) << std::string( passedRatio, '=' ); + else + stream << Colour( Colour::Success ) << std::string( passedRatio, '=' ); + } + else { + stream << Colour( Colour::Warning ) << std::string( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' ); + } + stream << '\n'; + } + void printSummaryDivider() { + stream << getLineOfChars<'-'>() << '\n'; + } + + private: + bool m_headerPrinted; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "console", ConsoleReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_compact.hpp +#define TWOBLUECUBES_CATCH_REPORTER_COMPACT_HPP_INCLUDED + +namespace Catch { + + struct CompactReporter : StreamingReporterBase { + + CompactReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ) + {} + + virtual ~CompactReporter(); + + static std::string getDescription() { + return "Reports test results on a single line, suitable for IDEs"; + } + + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = false; + return prefs; + } + + virtual void noMatchingTestCases( std::string const& spec ) { + stream << "No test cases matched '" << spec << '\'' << std::endl; + } + + virtual void assertionStarting( AssertionInfo const& ) {} + + virtual bool assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; + } + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); + + stream << std::endl; + return true; + } + + virtual void sectionEnded(SectionStats const& _sectionStats) CATCH_OVERRIDE { + if (m_config->showDurations() == ShowDurations::Always) { + stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + } + } + + virtual void testRunEnded( TestRunStats const& _testRunStats ) { + printTotals( _testRunStats.totals ); + stream << '\n' << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + private: + class AssertionPrinter { + void operator= ( AssertionPrinter const& ); + public: + AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) + : stream( _stream ) + , stats( _stats ) + , result( _stats.assertionResult ) + , messages( _stats.infoMessages ) + , itMessage( _stats.infoMessages.begin() ) + , printInfoMessages( _printInfoMessages ) + {} + + void print() { + printSourceInfo(); + + itMessage = messages.begin(); + + switch( result.getResultType() ) { + case ResultWas::Ok: + printResultType( Colour::ResultSuccess, passedString() ); + printOriginalExpression(); + printReconstructedExpression(); + if ( ! result.hasExpression() ) + printRemainingMessages( Colour::None ); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if( result.isOk() ) + printResultType( Colour::ResultSuccess, failedString() + std::string( " - but was ok" ) ); + else + printResultType( Colour::Error, failedString() ); + printOriginalExpression(); + printReconstructedExpression(); + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType( Colour::Error, failedString() ); + printIssue( "unexpected exception with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType( Colour::Error, failedString() ); + printIssue( "fatal error condition with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType( Colour::Error, failedString() ); + printIssue( "expected exception, got none" ); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType( Colour::None, "info" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType( Colour::None, "warning" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType( Colour::Error, failedString() ); + printIssue( "explicitly" ); + printRemainingMessages( Colour::None ); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType( Colour::Error, "** internal error **" ); + break; + } + } + + private: + // Colour::LightGrey + + static Colour::Code dimColour() { return Colour::FileName; } + +#ifdef CATCH_PLATFORM_MAC + static const char* failedString() { return "FAILED"; } + static const char* passedString() { return "PASSED"; } +#else + static const char* failedString() { return "failed"; } + static const char* passedString() { return "passed"; } +#endif + + void printSourceInfo() const { + Colour colourGuard( Colour::FileName ); + stream << result.getSourceInfo() << ':'; + } + + void printResultType( Colour::Code colour, std::string const& passOrFail ) const { + if( !passOrFail.empty() ) { + { + Colour colourGuard( colour ); + stream << ' ' << passOrFail; + } + stream << ':'; + } + } + + void printIssue( std::string const& issue ) const { + stream << ' ' << issue; + } + + void printExpressionWas() { + if( result.hasExpression() ) { + stream << ';'; + { + Colour colour( dimColour() ); + stream << " expression was:"; + } + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if( result.hasExpression() ) { + stream << ' ' << result.getExpression(); + } + } + + void printReconstructedExpression() const { + if( result.hasExpandedExpression() ) { + { + Colour colour( dimColour() ); + stream << " for: "; + } + stream << result.getExpandedExpression(); + } + } + + void printMessage() { + if ( itMessage != messages.end() ) { + stream << " '" << itMessage->message << '\''; + ++itMessage; + } + } + + void printRemainingMessages( Colour::Code colour = dimColour() ) { + if ( itMessage == messages.end() ) + return; + + // using messages.end() directly yields compilation error: + std::vector<MessageInfo>::const_iterator itEnd = messages.end(); + const std::size_t N = static_cast<std::size_t>( std::distance( itMessage, itEnd ) ); + + { + Colour colourGuard( colour ); + stream << " with " << pluralise( N, "message" ) << ':'; + } + + for(; itMessage != itEnd; ) { + // If this assertion is a warning ignore any INFO messages + if( printInfoMessages || itMessage->type != ResultWas::Info ) { + stream << " '" << itMessage->message << '\''; + if ( ++itMessage != itEnd ) { + Colour colourGuard( dimColour() ); + stream << " and"; + } + } + } + } + + private: + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + std::vector<MessageInfo> messages; + std::vector<MessageInfo>::const_iterator itMessage; + bool printInfoMessages; + }; + + // Colour, message variants: + // - white: No tests ran. + // - red: Failed [both/all] N test cases, failed [both/all] M assertions. + // - white: Passed [both/all] N test cases (no assertions). + // - red: Failed N tests cases, failed M assertions. + // - green: Passed [both/all] N tests cases with M assertions. + + std::string bothOrAll( std::size_t count ) const { + return count == 1 ? std::string() : count == 2 ? "both " : "all " ; + } + + void printTotals( const Totals& totals ) const { + if( totals.testCases.total() == 0 ) { + stream << "No tests ran."; + } + else if( totals.testCases.failed == totals.testCases.total() ) { + Colour colour( Colour::ResultError ); + const std::string qualify_assertions_failed = + totals.assertions.failed == totals.assertions.total() ? + bothOrAll( totals.assertions.failed ) : std::string(); + stream << + "Failed " << bothOrAll( totals.testCases.failed ) + << pluralise( totals.testCases.failed, "test case" ) << ", " + "failed " << qualify_assertions_failed << + pluralise( totals.assertions.failed, "assertion" ) << '.'; + } + else if( totals.assertions.total() == 0 ) { + stream << + "Passed " << bothOrAll( totals.testCases.total() ) + << pluralise( totals.testCases.total(), "test case" ) + << " (no assertions)."; + } + else if( totals.assertions.failed ) { + Colour colour( Colour::ResultError ); + stream << + "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", " + "failed " << pluralise( totals.assertions.failed, "assertion" ) << '.'; + } + else { + Colour colour( Colour::ResultSuccess ); + stream << + "Passed " << bothOrAll( totals.testCases.passed ) + << pluralise( totals.testCases.passed, "test case" ) << + " with " << pluralise( totals.assertions.passed, "assertion" ) << '.'; + } + } + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "compact", CompactReporter ) + +} // end namespace Catch + +namespace Catch { + // These are all here to avoid warnings about not having any out of line + // virtual methods + NonCopyable::~NonCopyable() {} + IShared::~IShared() {} + IStream::~IStream() CATCH_NOEXCEPT {} + FileStream::~FileStream() CATCH_NOEXCEPT {} + CoutStream::~CoutStream() CATCH_NOEXCEPT {} + DebugOutStream::~DebugOutStream() CATCH_NOEXCEPT {} + StreamBufBase::~StreamBufBase() CATCH_NOEXCEPT {} + IContext::~IContext() {} + IResultCapture::~IResultCapture() {} + ITestCase::~ITestCase() {} + ITestCaseRegistry::~ITestCaseRegistry() {} + IRegistryHub::~IRegistryHub() {} + IMutableRegistryHub::~IMutableRegistryHub() {} + IExceptionTranslator::~IExceptionTranslator() {} + IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() {} + IReporter::~IReporter() {} + IReporterFactory::~IReporterFactory() {} + IReporterRegistry::~IReporterRegistry() {} + IStreamingReporter::~IStreamingReporter() {} + AssertionStats::~AssertionStats() {} + SectionStats::~SectionStats() {} + TestCaseStats::~TestCaseStats() {} + TestGroupStats::~TestGroupStats() {} + TestRunStats::~TestRunStats() {} + CumulativeReporterBase::SectionNode::~SectionNode() {} + CumulativeReporterBase::~CumulativeReporterBase() {} + + StreamingReporterBase::~StreamingReporterBase() {} + ConsoleReporter::~ConsoleReporter() {} + CompactReporter::~CompactReporter() {} + IRunner::~IRunner() {} + IMutableContext::~IMutableContext() {} + IConfig::~IConfig() {} + XmlReporter::~XmlReporter() {} + JunitReporter::~JunitReporter() {} + TestRegistry::~TestRegistry() {} + FreeFunctionTestCase::~FreeFunctionTestCase() {} + IGeneratorInfo::~IGeneratorInfo() {} + IGeneratorsForTest::~IGeneratorsForTest() {} + WildcardPattern::~WildcardPattern() {} + TestSpec::Pattern::~Pattern() {} + TestSpec::NamePattern::~NamePattern() {} + TestSpec::TagPattern::~TagPattern() {} + TestSpec::ExcludedPattern::~ExcludedPattern() {} + Matchers::Impl::MatcherUntypedBase::~MatcherUntypedBase() {} + + void Config::dummy() {} + + namespace TestCaseTracking { + ITracker::~ITracker() {} + TrackerBase::~TrackerBase() {} + SectionTracker::~SectionTracker() {} + IndexTracker::~IndexTracker() {} + } +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif + +#ifdef CATCH_CONFIG_MAIN +// #included from: internal/catch_default_main.hpp +#define TWOBLUECUBES_CATCH_DEFAULT_MAIN_HPP_INCLUDED + +#ifndef __OBJC__ + +#if defined(WIN32) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) +// Standard C/C++ Win32 Unicode wmain entry point +extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) { +#else +// Standard C/C++ main entry point +int main (int argc, char * argv[]) { +#endif + + int result = Catch::Session().run( argc, argv ); + return ( result < 0xff ? result : 0xff ); +} + +#else // __OBJC__ + +// Objective-C entry point +int main (int argc, char * const argv[]) { +#if !CATCH_ARC_ENABLED + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; +#endif + + Catch::registerTestMethods(); + int result = Catch::Session().run( argc, (char* const*)argv ); + +#if !CATCH_ARC_ENABLED + [pool drain]; +#endif + + return ( result < 0xff ? result : 0xff ); +} + +#endif // __OBJC__ + +#endif + +#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED +# undef CLARA_CONFIG_MAIN +#endif + +////// + +// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ +#ifdef CATCH_CONFIG_PREFIX_ALL + +#if defined(CATCH_CONFIG_FAST_COMPILE) +#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, expr ) +#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr ) +#else +#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, expr ) +#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr ) +#endif + +#define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", expr ) +#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) +#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) +#define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, expr ) + +#define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, expr ) +#define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, expr ) + +#define CATCH_CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", expr ) +#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, expr ) + +#define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) + +#if defined(CATCH_CONFIG_FAST_COMPILE) +#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT_NO_TRY( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#else +#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#endif + +#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) +#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) +#define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) +#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CATCH_CAPTURE", #msg " := " << Catch::toString(msg) ) +#define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CATCH_CAPTURE", #msg " := " << Catch::toString(msg) ) + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) + #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) + #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#else + #define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) + #define CATCH_TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) + #define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) + #define CATCH_REGISTER_TEST_CASE( function, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( function, name, description ) + #define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) + #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, msg ) + #define CATCH_FAIL_CHECK( msg ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, msg ) + #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, msg ) +#endif +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) + +#define CATCH_REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) +#define CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) + +#define CATCH_GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) + +// "BDD-style" convenience wrappers +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#else +#define CATCH_SCENARIO( name, tags ) CATCH_TEST_CASE( "Scenario: " name, tags ) +#define CATCH_SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) +#endif +#define CATCH_GIVEN( desc ) CATCH_SECTION( std::string( "Given: ") + desc, "" ) +#define CATCH_WHEN( desc ) CATCH_SECTION( std::string( " When: ") + desc, "" ) +#define CATCH_AND_WHEN( desc ) CATCH_SECTION( std::string( " And: ") + desc, "" ) +#define CATCH_THEN( desc ) CATCH_SECTION( std::string( " Then: ") + desc, "" ) +#define CATCH_AND_THEN( desc ) CATCH_SECTION( std::string( " And: ") + desc, "" ) + +// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required +#else + +#if defined(CATCH_CONFIG_FAST_COMPILE) +#define REQUIRE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "REQUIRE", Catch::ResultDisposition::Normal, expr ) +#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr ) + +#else +#define REQUIRE( expr ) INTERNAL_CATCH_TEST( "REQUIRE", Catch::ResultDisposition::Normal, expr ) +#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr ) +#endif + +#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", expr ) +#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) +#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) +#define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, expr ) + +#define CHECK( expr ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, expr ) +#define CHECKED_IF( expr ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, expr ) + +#define CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", expr ) +#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, expr ) + +#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) + +#if defined(CATCH_CONFIG_FAST_COMPILE) +#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT_NO_TRY( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#else +#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#endif + +#define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) +#define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) +#define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) +#define CAPTURE( msg ) INTERNAL_CATCH_INFO( "CAPTURE", #msg " := " << Catch::toString(msg) ) +#define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CAPTURE", #msg " := " << Catch::toString(msg) ) + +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) +#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) +#define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) +#define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) +#define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#else +#define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) + #define TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) + #define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) + #define REGISTER_TEST_CASE( method, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( method, name, description ) + #define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) + #define FAIL( msg ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, msg ) + #define FAIL_CHECK( msg ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, msg ) + #define SUCCEED( msg ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, msg ) +#endif +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) + +#define REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) +#define REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) + +#define GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) + +#endif + +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) + +// "BDD-style" convenience wrappers +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#else +#define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags ) +#define SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) +#endif +#define GIVEN( desc ) SECTION( std::string(" Given: ") + desc, "" ) +#define WHEN( desc ) SECTION( std::string(" When: ") + desc, "" ) +#define AND_WHEN( desc ) SECTION( std::string("And when: ") + desc, "" ) +#define THEN( desc ) SECTION( std::string(" Then: ") + desc, "" ) +#define AND_THEN( desc ) SECTION( std::string(" And: ") + desc, "" ) + +using Catch::Detail::Approx; + +#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + diff --git a/src/jaegertracing/thrift/compiler/cpp/tests/netcore/t_netcore_generator_functional_tests.cc b/src/jaegertracing/thrift/compiler/cpp/tests/netcore/t_netcore_generator_functional_tests.cc new file mode 100644 index 000000000..0b8c8378e --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/tests/netcore/t_netcore_generator_functional_tests.cc @@ -0,0 +1,339 @@ +// Licensed to the Apache Software Foundation(ASF) under one +// or more contributor license agreements.See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership.The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#include "../catch/catch.hpp" +#include <thrift/parse/t_program.h> +#include <thrift/generate/t_netcore_generator.h> +#include "t_netcore_generator_functional_tests_helpers.h" + +TEST_CASE( "t_netcore_generator should generate valid enum", "[functional]" ) +{ + string path = "CassandraTest.thrift"; + string name = "netcore"; + map<string, string> parsed_options = { { "wcf", "wcf" } }; + string option_string = ""; + + t_program* program = new t_program(path, name); + t_netcore_generator* gen = new t_netcore_generator(program, parsed_options, option_string); + + std::pair<string, t_enum*> pair = TestDataGenerator::get_test_enum_data(program); + string expected_result = pair.first; + t_enum* test_enum = pair.second; + + string file_path = test_enum->get_name() + ".cs"; + ofstream out; + out.open(file_path.c_str()); + + REQUIRE_NOTHROW(gen->generate_enum(out, test_enum)); + + out.close(); + + std::ifstream ifs(file_path); + string actual_result((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>())); + std::remove(file_path.c_str()); + + REQUIRE(expected_result == actual_result); + + delete test_enum; + delete gen; + delete program; +} + +TEST_CASE("t_netcore_generator should generate valid void", "[functional]") +{ + string path = "CassandraTest.thrift"; + string name = "netcore"; + map<string, string> parsed_options = { { "wcf", "wcf" } }; + string option_string = ""; + + t_program* program = new t_program(path, name); + t_netcore_generator* gen = new t_netcore_generator(program, parsed_options, option_string); + + std::pair<string, t_const*> pair = TestDataGenerator::get_test_void_const_data(gen); + string expected_result = pair.first; + t_const* const_ = pair.second; + vector<t_const*> consts_; + consts_.push_back(const_); + + string file_path = const_->get_name() + ".cs"; + ofstream out; + out.open(file_path.c_str()); + + REQUIRE_THROWS(gen->generate_consts(out, consts_)); + + out.close(); + + std::ifstream ifs(file_path); + string actual_result((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>())); + std::remove(file_path.c_str()); + + delete const_; + delete gen; + delete program; +} + +TEST_CASE("t_netcore_generator should generate valid string with escaping keyword", "[functional]") +{ + string path = "CassandraTest.thrift"; + string name = "netcore"; + map<string, string> parsed_options = { { "wcf", "wcf" } }; + string option_string = ""; + + t_program* program = new t_program(path, name); + t_netcore_generator* gen = new t_netcore_generator(program, parsed_options, option_string); + gen->init_generator(); + + std::pair<string, t_const*> pair = TestDataGenerator::get_test_string_const_data(gen); + string expected_result = pair.first; + t_const* const_ = pair.second; + vector<t_const*> consts_; + consts_.push_back(const_); + + string file_path = const_->get_name() + ".cs"; + ofstream out; + out.open(file_path.c_str()); + + REQUIRE_NOTHROW(gen->generate_consts(out, consts_)); + + out.close(); + + std::ifstream ifs(file_path); + string actual_result((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>())); + std::remove(file_path.c_str()); + + REQUIRE(expected_result == actual_result); + + delete const_; + delete gen; + delete program; +} + +TEST_CASE("t_netcore_generator should generate valid bool with escaping keyword", "[functional]") +{ + string path = "CassandraTest.thrift"; + string name = "netcore"; + map<string, string> parsed_options = { { "wcf", "wcf" } }; + string option_string = ""; + + t_program* program = new t_program(path, name); + t_netcore_generator* gen = new t_netcore_generator(program, parsed_options, option_string); + gen->init_generator(); + + std::pair<string, t_const*> pair = TestDataGenerator::get_test_bool_const_data(gen); + string expected_result = pair.first; + t_const* const_ = pair.second; + vector<t_const*> consts_; + consts_.push_back(const_); + + string file_path = const_->get_name() + ".cs"; + ofstream out; + out.open(file_path.c_str()); + + REQUIRE_NOTHROW(gen->generate_consts(out, consts_)); + + out.close(); + + std::ifstream ifs(file_path); + string actual_result((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>())); + std::remove(file_path.c_str()); + + REQUIRE(expected_result == actual_result); + + delete const_; + delete gen; + delete program; +} + +TEST_CASE("t_netcore_generator should generate valid sbyte (i8) with escaping keyword", "[functional]") +{ + string path = "CassandraTest.thrift"; + string name = "netcore"; + map<string, string> parsed_options = { { "wcf", "wcf" } }; + string option_string = ""; + + t_program* program = new t_program(path, name); + t_netcore_generator* gen = new t_netcore_generator(program, parsed_options, option_string); + gen->init_generator(); + + std::pair<string, t_const*> pair = TestDataGenerator::get_test_i8_const_data(gen); + string expected_result = pair.first; + t_const* const_ = pair.second; + vector<t_const*> consts_; + consts_.push_back(const_); + + string file_path = const_->get_name() + ".cs"; + ofstream out; + out.open(file_path.c_str()); + + REQUIRE_NOTHROW(gen->generate_consts(out, consts_)); + + out.close(); + + std::ifstream ifs(file_path); + string actual_result((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>())); + std::remove(file_path.c_str()); + + REQUIRE(expected_result == actual_result); + + delete const_; + delete gen; + delete program; +} + +TEST_CASE("t_netcore_generator should generate valid short (i16) with escaping keyword", "[functional]") +{ + string path = "CassandraTest.thrift"; + string name = "netcore"; + map<string, string> parsed_options = { { "wcf", "wcf" } }; + string option_string = ""; + + t_program* program = new t_program(path, name); + t_netcore_generator* gen = new t_netcore_generator(program, parsed_options, option_string); + gen->init_generator(); + + std::pair<string, t_const*> pair = TestDataGenerator::get_test_i16_const_data(gen); + string expected_result = pair.first; + t_const* const_ = pair.second; + vector<t_const*> consts_; + consts_.push_back(const_); + + string file_path = const_->get_name() + ".cs"; + ofstream out; + out.open(file_path.c_str()); + + REQUIRE_NOTHROW(gen->generate_consts(out, consts_)); + + out.close(); + + std::ifstream ifs(file_path); + string actual_result((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>())); + std::remove(file_path.c_str()); + + REQUIRE(expected_result == actual_result); + + delete const_; + delete gen; + delete program; +} + +TEST_CASE("t_netcore_generator should generate valid integer (i32) with escaping keyword", "[functional]") +{ + string path = "CassandraTest.thrift"; + string name = "netcore"; + map<string, string> parsed_options = { { "wcf", "wcf" } }; + string option_string = ""; + + t_program* program = new t_program(path, name); + t_netcore_generator* gen = new t_netcore_generator(program, parsed_options, option_string); + gen->init_generator(); + + std::pair<string, t_const*> pair = TestDataGenerator::get_test_i32_const_data(gen); + string expected_result = pair.first; + t_const* const_ = pair.second; + vector<t_const*> consts_; + consts_.push_back(const_); + + string file_path = const_->get_name() + ".cs"; + ofstream out; + out.open(file_path.c_str()); + + REQUIRE_NOTHROW(gen->generate_consts(out, consts_)); + + out.close(); + + std::ifstream ifs(file_path); + string actual_result((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>())); + std::remove(file_path.c_str()); + + REQUIRE(expected_result == actual_result); + + delete const_; + delete gen; + delete program; +} + +TEST_CASE("t_netcore_generator should generate valid long (i64) with escaping keyword", "[functional]") +{ + string path = "CassandraTest.thrift"; + string name = "netcore"; + map<string, string> parsed_options = { { "wcf", "wcf" } }; + string option_string = ""; + + t_program* program = new t_program(path, name); + t_netcore_generator* gen = new t_netcore_generator(program, parsed_options, option_string); + gen->init_generator(); + + std::pair<string, t_const*> pair = TestDataGenerator::get_test_i64_const_data(gen); + string expected_result = pair.first; + t_const* const_ = pair.second; + vector<t_const*> consts_; + consts_.push_back(const_); + + string file_path = const_->get_name() + ".cs"; + ofstream out; + out.open(file_path.c_str()); + + REQUIRE_NOTHROW(gen->generate_consts(out, consts_)); + + out.close(); + + std::ifstream ifs(file_path); + string actual_result((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>())); + std::remove(file_path.c_str()); + + REQUIRE(expected_result == actual_result); + + delete const_; + delete gen; + delete program; +} + +TEST_CASE("t_netcore_generator should generate valid double with escaping keyword", "[functional]") +{ + string path = "CassandraTest.thrift"; + string name = "netcore"; + map<string, string> parsed_options = { { "wcf", "wcf" } }; + string option_string = ""; + + t_program* program = new t_program(path, name); + t_netcore_generator* gen = new t_netcore_generator(program, parsed_options, option_string); + gen->init_generator(); + + std::pair<string, t_const*> pair = TestDataGenerator::get_test_double_const_data(gen); + string expected_result = pair.first; + t_const* const_ = pair.second; + vector<t_const*> consts_; + consts_.push_back(const_); + + string file_path = const_->get_name() + ".cs"; + ofstream out; + out.open(file_path.c_str()); + + REQUIRE_NOTHROW(gen->generate_consts(out, consts_)); + + out.close(); + + std::ifstream ifs(file_path); + string actual_result((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>())); + std::remove(file_path.c_str()); + + REQUIRE(expected_result == actual_result); + + delete const_; + delete gen; + delete program; +} diff --git a/src/jaegertracing/thrift/compiler/cpp/tests/netcore/t_netcore_generator_functional_tests_helpers.cc b/src/jaegertracing/thrift/compiler/cpp/tests/netcore/t_netcore_generator_functional_tests_helpers.cc new file mode 100644 index 000000000..92c170bb9 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/tests/netcore/t_netcore_generator_functional_tests_helpers.cc @@ -0,0 +1,237 @@ +// Licensed to the Apache Software Foundation(ASF) under one +// or more contributor license agreements.See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership.The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#include <thrift/parse/t_program.h> +#include "thrift/common.h" +#include <thrift/generate/t_netcore_generator.h> +#include "t_netcore_generator_functional_tests_helpers.h" + +const string TestDataGenerator::DEFAULT_FILE_HEADER = "/**" "\n" + " * Autogenerated by Thrift Compiler ()" "\n" + " *" "\n" + " * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING" "\n" + " * @generated" "\n" + " */"; + +std::pair<string, t_enum*> TestDataGenerator::get_test_enum_data(t_program* program) +{ + string expected_result = DEFAULT_FILE_HEADER + + "\n" + "\n" + "/// <summary>\n" + "/// TestDoc\n" + "/// </summary>\n" + "public enum TestName\n" + "{\n" + " None = 0,\n" + " First = 1,\n" + " Second = 2,\n" + "}\n"; + + t_enum* enum_ = new t_enum(program); + enum_->set_name("TestName"); + enum_->set_doc("TestDoc"); + enum_->append(new t_enum_value("None", 0)); + enum_->append(new t_enum_value("First", 1)); + enum_->append(new t_enum_value("Second", 2)); + + return std::pair<string, t_enum*>(expected_result, enum_); +} + +std::pair<string, t_const*> TestDataGenerator::get_test_void_const_data(t_netcore_generator* gen) +{ + string expected_result = DEFAULT_FILE_HEADER; + + t_type* type_ = new t_base_type("void", t_base_type::TYPE_VOID); + type_->set_doc("TestDoc"); + + t_const_value* const_value_ = new t_const_value(); + const_value_->set_string("VoidValue"); + + t_const* const_ = new t_const(type_, "void", const_value_); + const_->set_doc("TestDoc"); + + return std::pair<string, t_const*>(expected_result, const_); +} + +std::pair<string, t_const*> TestDataGenerator::get_test_string_const_data(t_netcore_generator* gen) +{ + string expected_result = DEFAULT_FILE_HEADER + "\n" +gen->netcore_type_usings() + + "\n" + "public static class netcoreConstants\n" + "{\n" + " /// <summary>\n" + " /// TestDoc\n" + " /// </summary>\n" + " public const string @string = \"StringValue\";\n" + "}\n"; + + t_type* type_ = new t_base_type("string", t_base_type::TYPE_STRING); + type_->set_doc("TestDoc"); + + t_const_value* const_value_ = new t_const_value(); + const_value_->set_string("StringValue"); + + t_const* const_ = new t_const(type_, "string", const_value_); + const_->set_doc("TestDoc"); + + return std::pair<string, t_const*>(expected_result, const_); +} + +std::pair<string, t_const*> TestDataGenerator::get_test_bool_const_data(t_netcore_generator* gen) +{ + string expected_result = DEFAULT_FILE_HEADER + "\n" +gen->netcore_type_usings() + + "\n" + "public static class netcoreConstants\n" + "{\n" + " /// <summary>\n" + " /// TestDoc\n" + " /// </summary>\n" + " public const bool @bool = true;\n" + "}\n"; + + t_type* type_ = new t_base_type("bool", t_base_type::TYPE_BOOL); + type_->set_doc("TestDoc"); + + t_const_value* const_value_ = new t_const_value(); + const_value_->set_integer(1); + + t_const* const_ = new t_const(type_, "bool", const_value_); + const_->set_doc("TestDoc"); + + return std::pair<string, t_const*>(expected_result, const_); +} + +std::pair<string, t_const*> TestDataGenerator::get_test_i8_const_data(t_netcore_generator* gen) +{ + string expected_result = DEFAULT_FILE_HEADER + "\n" +gen->netcore_type_usings() + + "\n" + "public static class netcoreConstants\n" + "{\n" + " /// <summary>\n" + " /// TestDoc\n" + " /// </summary>\n" + " public const sbyte @sbyte = 127;\n" + "}\n"; + + t_type* type_ = new t_base_type("I8", t_base_type::TYPE_I8); + type_->set_doc("TestDoc"); + + t_const_value* const_value_ = new t_const_value(); + const_value_->set_integer(127); + + t_const* const_ = new t_const(type_, "sbyte", const_value_); + const_->set_doc("TestDoc"); + + return std::pair<string, t_const*>(expected_result, const_); +} + +std::pair<string, t_const*> TestDataGenerator::get_test_i16_const_data(t_netcore_generator* gen) +{ + string expected_result = DEFAULT_FILE_HEADER + "\n" +gen->netcore_type_usings() + + "\n" + "public static class netcoreConstants\n" + "{\n" + " /// <summary>\n" + " /// TestDoc\n" + " /// </summary>\n" + " public const short @short = 32767;\n" + "}\n"; + + t_type* type_ = new t_base_type("i16", t_base_type::TYPE_I16); + type_->set_doc("TestDoc"); + + t_const_value* const_value_ = new t_const_value(); + const_value_->set_integer(32767); + + t_const* const_ = new t_const(type_, "short", const_value_); + const_->set_doc("TestDoc"); + + return std::pair<string, t_const*>(expected_result, const_); +} + +std::pair<string, t_const*> TestDataGenerator::get_test_i32_const_data(t_netcore_generator* gen) +{ + string expected_result = DEFAULT_FILE_HEADER + "\n" +gen->netcore_type_usings() + + "\n" + "public static class netcoreConstants\n" + "{\n" + " /// <summary>\n" + " /// TestDoc\n" + " /// </summary>\n" + " public const int @int = 2147483647;\n" + "}\n"; + + t_type* type_ = new t_base_type("i32", t_base_type::TYPE_I32); + type_->set_doc("TestDoc"); + + t_const_value* const_value_ = new t_const_value(); + const_value_->set_integer(2147483647); + + t_const* const_ = new t_const(type_, "int", const_value_); + const_->set_doc("TestDoc"); + + return std::pair<string, t_const*>(expected_result, const_); +} + +std::pair<string, t_const*> TestDataGenerator::get_test_i64_const_data(t_netcore_generator* gen) +{ + string expected_result = DEFAULT_FILE_HEADER + "\n" +gen->netcore_type_usings() + + "\n" + "public static class netcoreConstants\n" + "{\n" + " /// <summary>\n" + " /// TestDoc\n" + " /// </summary>\n" + " public const long @long = 9223372036854775807;\n" + "}\n"; + + t_type* type_ = new t_base_type("i64", t_base_type::TYPE_I64); + type_->set_doc("TestDoc"); + + t_const_value* const_value_ = new t_const_value(); + const_value_->set_integer(9223372036854775807); + + t_const* const_ = new t_const(type_, "long", const_value_); + const_->set_doc("TestDoc"); + + return std::pair<string, t_const*>(expected_result, const_); +} + +std::pair<string, t_const*> TestDataGenerator::get_test_double_const_data(t_netcore_generator* gen) +{ + string expected_result = DEFAULT_FILE_HEADER + "\n" +gen->netcore_type_usings() + + "\n" + "public static class netcoreConstants\n" + "{\n" + " /// <summary>\n" + " /// TestDoc\n" + " /// </summary>\n" + " public const double @double = 9.22337e+18;\n" + "}\n"; + + t_type* type_ = new t_base_type("double", t_base_type::TYPE_DOUBLE); + type_->set_doc("TestDoc"); + + t_const_value* const_value_ = new t_const_value(); + const_value_->set_double(9223372036854775807.1); + + t_const* const_ = new t_const(type_, "double", const_value_); + const_->set_doc("TestDoc"); + + return std::pair<string, t_const*>(expected_result, const_); +} diff --git a/src/jaegertracing/thrift/compiler/cpp/tests/netcore/t_netcore_generator_functional_tests_helpers.h b/src/jaegertracing/thrift/compiler/cpp/tests/netcore/t_netcore_generator_functional_tests_helpers.h new file mode 100644 index 000000000..c6eaac22c --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/tests/netcore/t_netcore_generator_functional_tests_helpers.h @@ -0,0 +1,34 @@ +// Licensed to the Apache Software Foundation(ASF) under one +// or more contributor license agreements.See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership.The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#include <thrift/parse/t_program.h> + +class TestDataGenerator +{ +public: + static const string DEFAULT_FILE_HEADER; + + static std::pair<string, t_enum*> get_test_enum_data(t_program* program); + static std::pair<string, t_const*> get_test_void_const_data(t_netcore_generator* gen); + static std::pair<string, t_const*> get_test_string_const_data(t_netcore_generator* gen); + static std::pair<string, t_const*> get_test_bool_const_data(t_netcore_generator* gen); + static std::pair<string, t_const*> get_test_i8_const_data(t_netcore_generator* gen); + static std::pair<string, t_const*> get_test_i16_const_data(t_netcore_generator* gen); + static std::pair<string, t_const*> get_test_i32_const_data(t_netcore_generator* gen); + static std::pair<string, t_const*> get_test_i64_const_data(t_netcore_generator* gen); + static std::pair<string, t_const*> get_test_double_const_data(t_netcore_generator* gen); +}; diff --git a/src/jaegertracing/thrift/compiler/cpp/tests/netcore/t_netcore_generator_helpers_tests.cc b/src/jaegertracing/thrift/compiler/cpp/tests/netcore/t_netcore_generator_helpers_tests.cc new file mode 100644 index 000000000..0bcbeed19 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/tests/netcore/t_netcore_generator_helpers_tests.cc @@ -0,0 +1,209 @@ +// Licensed to the Apache Software Foundation(ASF) under one +// or more contributor license agreements.See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership.The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#include "../catch/catch.hpp" +#include <thrift/parse/t_program.h> +#include <thrift/generate/t_netcore_generator.h> + +using std::vector; + +TEST_CASE("t_netcore_generator::netcore_type_usings() without option wcf should return valid namespaces", "[helpers]") +{ + string path = "CassandraTest.thrift"; + string name = "netcore"; + map<string, string> parsed_options = { { "union", "union" } }; + string option_string = ""; + + string expected_namespaces = "using System;\n" + "using System.Collections;\n" + "using System.Collections.Generic;\n" + "using System.Text;\n" + "using System.IO;\n" + "using System.Threading;\n" + "using System.Threading.Tasks;\n" + "using Thrift;\n" + "using Thrift.Collections;\n" + endl; + + t_program* program = new t_program(path, name); + t_netcore_generator* gen = new t_netcore_generator(program, parsed_options, option_string); + + REQUIRE_FALSE(gen->is_wcf_enabled()); + REQUIRE(gen->netcore_type_usings() == expected_namespaces); + + delete gen; + delete program; +} + +TEST_CASE("t_netcore_generator::netcore_type_usings() with option wcf should return valid namespaces", "[helpers]") +{ + string path = "CassandraTest.thrift"; + string name = "netcore"; + map<string, string> parsed_options = { { "wcf", "wcf" } }; + string option_string = ""; + + string expected_namespaces_wcf = "using System;\n" + "using System.Collections;\n" + "using System.Collections.Generic;\n" + "using System.Text;\n" + "using System.IO;\n" + "using System.Threading;\n" + "using System.Threading.Tasks;\n" + "using Thrift;\n" + "using Thrift.Collections;\n" + "using System.ServiceModel;\n" + "using System.Runtime.Serialization;\n" + endl; + + t_program* program = new t_program(path, name); + t_netcore_generator* gen = new t_netcore_generator(program, parsed_options, option_string); + + REQUIRE(gen->is_wcf_enabled()); + REQUIRE(gen->netcore_type_usings() == expected_namespaces_wcf); + + delete gen; + delete program; +} + +TEST_CASE("t_netcore_generator should contains latest C# keywords to normalize with @", "[helpers]") +{ + string path = "CassandraTest.thrift"; + string name = "netcore"; + map<string, string> parsed_options = { { "wcf", "wcf" } }; + string option_string = ""; + vector<string> current_keywords = { + { "abstract" }, + { "as" }, + { "base" }, + { "bool" }, + { "break" }, + { "byte" }, + { "case" }, + { "catch" }, + { "char" }, + { "checked" }, + { "class" }, + { "const" }, + { "continue" }, + { "decimal" }, + { "default" }, + { "delegate" }, + { "do" }, + { "double" }, + { "else" }, + { "enum" }, + { "event" }, + { "explicit" }, + { "extern" }, + { "false" }, + { "finally" }, + { "fixed" }, + { "float" }, + { "for" }, + { "foreach" }, + { "goto" }, + { "if" }, + { "implicit" }, + { "in" }, + { "int" }, + { "interface" }, + { "internal" }, + { "is" }, + { "lock" }, + { "long" }, + { "namespace" }, + { "new" }, + { "null" }, + { "object" }, + { "operator" }, + { "out" }, + { "override" }, + { "params" }, + { "private" }, + { "protected" }, + { "public" }, + { "readonly" }, + { "ref" }, + { "return" }, + { "sbyte" }, + { "sealed" }, + { "short" }, + { "sizeof" }, + { "stackalloc" }, + { "static" }, + { "string" }, + { "struct" }, + { "switch" }, + { "this" }, + { "throw" }, + { "true" }, + { "try" }, + { "typeof" }, + { "uint" }, + { "ulong" }, + { "unchecked" }, + { "unsafe" }, + { "ushort" }, + { "using" }, + { "void" }, + { "volatile" }, + { "while" }, + // Contextual Keywords + { "add" }, + { "alias" }, + { "ascending" }, + { "async" }, + { "await" }, + { "descending" }, + { "dynamic" }, + { "from" }, + { "get" }, + { "global" }, + { "group" }, + { "into" }, + { "join" }, + { "let" }, + { "orderby" }, + { "partial" }, + { "remove" }, + { "select" }, + { "set" }, + { "value" }, + { "var" }, + { "when" }, + { "where" }, + { "yield" } + }; + + string missed_keywords = ""; + + t_program* program = new t_program(path, name); + t_netcore_generator* gen = new t_netcore_generator(program, parsed_options, option_string); + gen->init_generator(); + map<string, int> generators_keywords = gen->get_keywords_list(); + + for (vector<string>::iterator it = current_keywords.begin(); it != current_keywords.end(); ++it) + { + if (generators_keywords.find(*it) == generators_keywords.end()) + { + missed_keywords = missed_keywords + *it + ","; + } + } + + REQUIRE(missed_keywords == ""); + + delete gen; + delete program; +} diff --git a/src/jaegertracing/thrift/compiler/cpp/tests/netcore/t_netcore_generator_initialization_tests.cc b/src/jaegertracing/thrift/compiler/cpp/tests/netcore/t_netcore_generator_initialization_tests.cc new file mode 100644 index 000000000..ec17733bd --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/tests/netcore/t_netcore_generator_initialization_tests.cc @@ -0,0 +1,74 @@ +// Licensed to the Apache Software Foundation(ASF) under one +// or more contributor license agreements.See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership.The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#include "../catch/catch.hpp" +#include <thrift/parse/t_program.h> +#include <thrift/generate/t_netcore_generator.h> + +TEST_CASE( "t_netcore_generator should throw error with unknown options", "[initialization]" ) +{ + string path = "CassandraTest.thrift"; + string name = "netcore"; + map<string, string> parsed_options = { { "keys", "keys" } }; + string option_string = ""; + + t_program* program = new t_program(path, name); + t_netcore_generator* gen = nullptr; + + REQUIRE_THROWS(gen = new t_netcore_generator(program, parsed_options, option_string)); + + delete gen; + delete program; +} + +TEST_CASE("t_netcore_generator should create valid instance with valid options", "[initialization]") +{ + string path = "CassandraTest.thrift"; + string name = "netcore"; + map<string, string> parsed_options = { { "wcf", "wcf" }, { "nullable", "nullable"} }; + string option_string = ""; + + t_program* program = new t_program(path, name); + t_netcore_generator* gen = nullptr; + + REQUIRE_NOTHROW(gen = new t_netcore_generator(program, parsed_options, option_string)); + REQUIRE(gen != nullptr); + REQUIRE(gen->is_wcf_enabled()); + REQUIRE(gen->is_nullable_enabled()); + REQUIRE_FALSE(gen->is_hashcode_enabled()); + REQUIRE_FALSE(gen->is_serialize_enabled()); + REQUIRE_FALSE(gen->is_union_enabled()); + + delete gen; + delete program; +} + +TEST_CASE("t_netcore_generator should pass init succesfully", "[initialization]") +{ + string path = "CassandraTest.thrift"; + string name = "netcore"; + map<string, string> parsed_options = { { "wcf", "wcf" },{ "nullable", "nullable" } }; + string option_string = ""; + + t_program* program = new t_program(path, name); + t_netcore_generator* gen = new t_netcore_generator(program, parsed_options, option_string); + + REQUIRE_NOTHROW(gen->init_generator()); + + delete gen; + delete program; +} diff --git a/src/jaegertracing/thrift/compiler/cpp/tests/tests_main.cc b/src/jaegertracing/thrift/compiler/cpp/tests/tests_main.cc new file mode 100644 index 000000000..21d09b9f0 --- /dev/null +++ b/src/jaegertracing/thrift/compiler/cpp/tests/tests_main.cc @@ -0,0 +1,19 @@ +// Licensed to the Apache Software Foundation(ASF) under one +// or more contributor license agreements.See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership.The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#define CATCH_CONFIG_MAIN +#include "catch/catch.hpp" |