diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 90b1169f8..8bcc88622 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,6 +48,14 @@ jobs: llvm-10-dev clang-10 libclang-10-dev default-jdk libssl1.0-dev libgraphviz-dev libmagic-dev libgit2-dev ctags doxygen libgtest-dev npm libldap2-dev + - name: Install dotnet SDK + if: ${{ matrix.os == 'ubuntu-18.04' }} + run: | + wget https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb + sudo dpkg -i packages-microsoft-prod.deb + sudo apt-get update + sudo apt-get install -y apt-transport-https && sudo apt-get update && sudo apt-get install -y dotnet-sdk-6.0 + - name: Remove default Postgresql Ubuntu 18 if: ${{ matrix.os == 'ubuntu-18.04' && matrix.db == 'postgresql' }} run: sudo apt-get remove libpq5 @@ -84,7 +92,7 @@ jobs: --enable-tutorial=no --enable-tests=no --with-libevent --with-zlib --without-nodejs --without-lua --without-ruby --without-csharp --without-erlang --without-perl --without-php --without-php_extension --without-dart --without-haskell --without-go - --without-rs --without-haxe --without-dotnetcore --without-d --without-qt4 --without-qt5 + --without-rs --without-haxe --without-d --without-qt4 --without-qt5 --without-java --without-swift make install -j $(nproc) @@ -133,6 +141,14 @@ jobs: libclang-10-dev odb libodb-dev thrift-compiler libthrift-dev default-jdk libssl-dev libgraphviz-dev libmagic-dev libgit2-dev ctags doxygen libgtest-dev npm libldap2-dev + - name: Install dotnet SDK + if: ${{ matrix.os == 'ubuntu-20.04' }} + run: | + wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb + sudo dpkg -i packages-microsoft-prod.deb + sudo apt-get update + sudo apt-get install -y apt-transport-https && sudo apt-get update && sudo apt-get install -y dotnet-sdk-6.0 + - name: Install Postgresql Ubuntu 20 if: ${{ matrix.os == 'ubuntu-20.04' && matrix.db == 'postgresql' }} run: sudo apt-get install libodb-pgsql-dev postgresql-server-dev-12 diff --git a/.gitignore b/.gitignore index 5a099cdbd..f64f24e53 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,9 @@ nbproject/ *.swp build/ build_*/ + +# C# +plugins/**/bin +plugins/**/obj +*.out +*.bak diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile index edb3f64f7..db6583ec1 100644 --- a/docker/dev/Dockerfile +++ b/docker/dev/Dockerfile @@ -8,6 +8,7 @@ ARG DEBIAN_FRONTEND=noninteractive # CodeCompass development dependencies. RUN set -x && apt-get update -qq \ && apt-get -y install --no-install-recommends \ + wget \ cmake make \ default-jdk \ ctags \ @@ -31,6 +32,14 @@ RUN set -x && apt-get update -qq \ ln -s /usr/bin/gcc-9 /usr/bin/gcc && \ ln -s /usr/bin/g++-9 /usr/bin/g++ +# Install .NET 6. +RUN wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb && \ + dpkg -i packages-microsoft-prod.deb && \ + apt-get update; \ + apt-get install -y apt-transport-https && \ + apt-get update && \ + apt-get install -y dotnet-sdk-6.0 + # Build GTest. RUN cd /usr/src/googletest && \ mkdir build && \ diff --git a/docker/runtime/Dockerfile b/docker/runtime/Dockerfile index 272d1985e..f952b6bdd 100644 --- a/docker/runtime/Dockerfile +++ b/docker/runtime/Dockerfile @@ -77,6 +77,13 @@ RUN set -x && apt-get update -qq && \ rm -rf /var/lib/apt/lists/ && \ set +x +# Install .NET 6. +RUN wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb && \ + dpkg -i packages-microsoft-prod.deb && \ + apt-get update; \ + apt-get install -y apt-transport-https && \ + apt-get update && \ + apt-get install -y dotnet-sdk-6.0 # Copy CodeCompass installed directory. (Change permission of the CodeCompass package.) COPY --from=builder /CodeCompass-install /codecompass diff --git a/docker/web/Dockerfile b/docker/web/Dockerfile index 7e6f0ce66..bdf5fc6e7 100644 --- a/docker/web/Dockerfile +++ b/docker/web/Dockerfile @@ -17,6 +17,7 @@ ARG DEBIAN_FRONTEND=noninteractive RUN set -x && apt-get update -qq \ && apt-get install -qqy --no-install-recommends \ + wget \ llvm-10 \ libboost-filesystem-dev libboost-log-dev libboost-program-options-dev \ libsqlite3-dev \ @@ -36,6 +37,14 @@ RUN set -x && apt-get update -qq \ && rm -rf /var/lib/apt/lists/ \ && set +x +# Install .NET 6. +RUN wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb && \ + dpkg -i packages-microsoft-prod.deb && \ + apt-get update; \ + apt-get install -y apt-transport-https && \ + apt-get update && \ + apt-get install -y dotnet-sdk-6.0 + ARG CC_GID=960 ARG CC_UID=960 diff --git a/lib/csharp/thrift_netstd/Benchmarks/Thrift.Benchmarks/CompactProtocolBenchmarks.cs b/lib/csharp/thrift_netstd/Benchmarks/Thrift.Benchmarks/CompactProtocolBenchmarks.cs new file mode 100644 index 000000000..ae4d54537 --- /dev/null +++ b/lib/csharp/thrift_netstd/Benchmarks/Thrift.Benchmarks/CompactProtocolBenchmarks.cs @@ -0,0 +1,79 @@ +// 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. + +using System.IO; +using System.Threading.Tasks; + +using BenchmarkDotNet.Attributes; + +using Thrift.Protocol; +using Thrift.Transport.Client; + +namespace Thrift.Benchmarks +{ + [MemoryDiagnoser] + public class CompactProtocolBenchmarks + { + private MemoryStream? _Stream; + private TProtocol? _Protocol; + + [Params(10000)] + public int NumberOfOperationsPerIteration { get; set; } + + [GlobalSetup] + public void GlobalSetup() + { + _Stream = new MemoryStream(); + var transport = new TStreamTransport(_Stream, _Stream, null); + _Protocol = new TCompactProtocol(transport); + } + + [GlobalCleanup] + public void GlobalCleanup() + { + _Protocol?.Dispose(); + } + + [Benchmark] + public async Task WriteString() + { + if ((_Protocol is null) || (_Stream is null)) + throw new System.Exception("unexpected internal state"); + + for (int i = 0; i < NumberOfOperationsPerIteration; i++) + { + await _Protocol.WriteStringAsync("Thrift String Benchmark"); + _Stream.Seek(0, SeekOrigin.Begin); + } + } + + [Benchmark] + public async Task ReadString() + { + if ((_Protocol is null) || (_Stream is null)) + throw new System.Exception("unexpected internal state"); + + await _Protocol.WriteStringAsync("Thrift String Benchmark"); + + for (int i = 0; i < NumberOfOperationsPerIteration; i++) + { + _Stream.Seek(0, SeekOrigin.Begin); + await _Protocol.ReadStringAsync(); + } + } + } +} diff --git a/lib/csharp/thrift_netstd/Benchmarks/Thrift.Benchmarks/Program.cs b/lib/csharp/thrift_netstd/Benchmarks/Thrift.Benchmarks/Program.cs new file mode 100644 index 000000000..923d73ef5 --- /dev/null +++ b/lib/csharp/thrift_netstd/Benchmarks/Thrift.Benchmarks/Program.cs @@ -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. + +using BenchmarkDotNet.Running; + +namespace Thrift.Benchmarks +{ + internal static class Program + { + public static void Main(string[] args) + { + BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); + } + } +} diff --git a/lib/csharp/thrift_netstd/Benchmarks/Thrift.Benchmarks/Thrift.Benchmarks.csproj b/lib/csharp/thrift_netstd/Benchmarks/Thrift.Benchmarks/Thrift.Benchmarks.csproj new file mode 100644 index 000000000..0e29b3b78 --- /dev/null +++ b/lib/csharp/thrift_netstd/Benchmarks/Thrift.Benchmarks/Thrift.Benchmarks.csproj @@ -0,0 +1,41 @@ + + + + + Exe + net6.0 + false + true + enable + + + + + + + + + + + + + + + diff --git a/lib/csharp/thrift_netstd/Directory.Build.props b/lib/csharp/thrift_netstd/Directory.Build.props new file mode 100644 index 000000000..3bd9541a4 --- /dev/null +++ b/lib/csharp/thrift_netstd/Directory.Build.props @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lib/csharp/thrift_netstd/Makefile b/lib/csharp/thrift_netstd/Makefile new file mode 100644 index 000000000..c73c61a9e --- /dev/null +++ b/lib/csharp/thrift_netstd/Makefile @@ -0,0 +1,858 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# lib/netstd/Makefile. Generated from Makefile.in by configure. + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + + + +# +# 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. +# + +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/thrift +pkgincludedir = $(includedir)/thrift +pkglibdir = $(libdir)/thrift +pkglibexecdir = $(libexecdir)/thrift +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = x86_64-pc-linux-gnu +host_triplet = x86_64-pc-linux-gnu +subdir = lib/netstd +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/aclocal/ac_prog_bison.m4 \ + $(top_srcdir)/aclocal/ax_boost_base.m4 \ + $(top_srcdir)/aclocal/ax_check_openssl.m4 \ + $(top_srcdir)/aclocal/ax_compare_version.m4 \ + $(top_srcdir)/aclocal/ax_cxx_compile_stdcxx.m4 \ + $(top_srcdir)/aclocal/ax_cxx_compile_stdcxx_11.m4 \ + $(top_srcdir)/aclocal/ax_dmd.m4 \ + $(top_srcdir)/aclocal/ax_javac_and_java.m4 \ + $(top_srcdir)/aclocal/ax_lib_event.m4 \ + $(top_srcdir)/aclocal/ax_lib_zlib.m4 \ + $(top_srcdir)/aclocal/ax_lua.m4 \ + $(top_srcdir)/aclocal/ax_prog_dotnetcore_version.m4 \ + $(top_srcdir)/aclocal/ax_prog_haxe_version.m4 \ + $(top_srcdir)/aclocal/ax_prog_perl_modules.m4 \ + $(top_srcdir)/aclocal/ax_signed_right_shift.m4 \ + $(top_srcdir)/aclocal/ax_thrift_internal.m4 \ + $(top_srcdir)/aclocal/libtool.m4 \ + $(top_srcdir)/aclocal/ltoptions.m4 \ + $(top_srcdir)/aclocal/ltsugar.m4 \ + $(top_srcdir)/aclocal/ltversion.m4 \ + $(top_srcdir)/aclocal/lt~obsolete.m4 \ + $(top_srcdir)/aclocal/tar.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h \ + $(top_builddir)/lib/cpp/src/thrift/config.h \ + $(top_builddir)/lib/c_glib/src/thrift/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_$(V)) +am__v_P_ = $(am__v_P_$(AM_DEFAULT_VERBOSITY)) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ + ctags-recursive dvi-recursive html-recursive info-recursive \ + install-data-recursive install-dvi-recursive \ + install-exec-recursive install-html-recursive \ + install-info-recursive install-pdf-recursive \ + install-ps-recursive install-recursive installcheck-recursive \ + installdirs-recursive pdf-recursive ps-recursive \ + tags-recursive uninstall-recursive +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + distdir distdir-am +am__extra_recursive_targets = style-recursive +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DIST_SUBDIRS = $(SUBDIRS) +am__DIST_COMMON = $(srcdir)/Makefile.in +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +ACLOCAL = ${SHELL} /home/borisz/Desktop/thrift_teszt/thrift/missing aclocal-1.16 +ALLOCA = +AMTAR = $${TAR-tar} +AM_DEFAULT_VERBOSITY = 1 +ANT = +ANT_FLAGS = +AR = ar +AUTOCONF = ${SHELL} /home/borisz/Desktop/thrift_teszt/thrift/missing autoconf +AUTOHEADER = ${SHELL} /home/borisz/Desktop/thrift_teszt/thrift/missing autoheader +AUTOMAKE = ${SHELL} /home/borisz/Desktop/thrift_teszt/thrift/missing automake-1.16 +AWK = mawk +BISON = bison +BOOST_CHRONO_LDADD = /usr/lib/x86_64-linux-gnu/libboost_chrono.a +BOOST_CPPFLAGS = -I/usr/include +BOOST_FILESYSTEM_LDADD = /usr/lib/x86_64-linux-gnu/libboost_filesystem.a +BOOST_LDFLAGS = -L/usr/lib/x86_64-linux-gnu +BOOST_LIB_DIR = /usr/lib/x86_64-linux-gnu +BOOST_SYSTEM_LDADD = /usr/lib/x86_64-linux-gnu/libboost_system.a +BOOST_TEST_LDADD = /usr/lib/x86_64-linux-gnu/libboost_unit_test_framework.a +BOOST_THREAD_LDADD = /usr/lib/x86_64-linux-gnu/libboost_thread.a +BUNDLER = +CARGO = +CC = gcc +CCDEPMODE = depmode=gcc3 +CFLAGS = -g -O2 +CLASSPATH = +CPP = gcc -E +CPPFLAGS = +CPPSTYLE_CMD = find . -type f \( -iname "*.h" -or -iname "*.cpp" -or -iname "*.cc" -or -iname "*.tcc" \) -printf "Reformatting: %h/%f\n" -exec clang-format -i {} \; +CXX = g++ -std=c++11 +CXXCPP = g++ -E -std=c++11 +CXXDEPMODE = depmode=gcc3 +CXXFLAGS = -g -O2 +CYGPATH_W = echo +DART = +DARTPUB = +DEFS = -DHAVE_CONFIG_H +DEPDIR = .deps +DLLTOOL = false +DMD = +DMD_LIBEVENT_FLAGS = +DMD_OF_DIRSEP = +DMD_OPENSSL_FLAGS = +DOTNETCORE = /usr/bin/dotnet +DOTNETCORE_VERSION = 5.0.404 +DSYMUTIL = +DUMPBIN = +D_EVENT_LIB_NAME = libthriftd-event.a +D_IMPORT_PREFIX = ${prefix}/include/d2 +D_LIB_NAME = libthriftd.a +D_SSL_LIB_NAME = libthriftd-ssl.a +ECHO_C = +ECHO_N = -n +ECHO_T = +EGREP = /usr/bin/grep -E +ENABLE_COVERAGE = 2 +ERL = +ERLANG_INSTALL_LIB_DIR = +ERLANG_INSTALL_LIB_DIR_thrift = +ERLANG_LIB_DIR = +ERLC = +ERLCFLAGS = +EXEEXT = +FGREP = /usr/bin/grep -F +GCOV_CFLAGS = +GCOV_CXXFLAGS = +GCOV_LDFLAGS = +GLIB_CFLAGS = +GLIB_LIBS = +GO = +GOBJECT_CFLAGS = +GOBJECT_LIBS = +GRADLE_OPTS = +GREP = /usr/bin/grep +GSETTINGS = +HAVE_CXX11 = 1 +HAXE = +HAXE_VERSION = +INSTALL = /usr/bin/install -c +INSTALLDIRS = vendor +INSTALL_DATA = ${INSTALL} -m 644 +INSTALL_PROGRAM = ${INSTALL} +INSTALL_SCRIPT = ${INSTALL} +INSTALL_STRIP_PROGRAM = $(install_sh) -c -s +JAVA_PREFIX = /usr/local/lib +LD = /usr/bin/ld -m elf_x86_64 +LDFLAGS = +LEX = flex +LEXLIB = -lfl +LEX_OUTPUT_ROOT = lex.yy +LIBEVENT_CPPFLAGS = +LIBEVENT_LDFLAGS = +LIBEVENT_LIBS = -levent +LIBOBJS = +LIBS = -lrt -lpthread +LIBTOOL = $(SHELL) $(top_builddir)/libtool +LIPO = +LN_S = ln -s +LTLIBOBJS = +LT_SYS_LIBRARY_PATH = +LUA = : +LUA_EXEC_PREFIX = +LUA_INCLUDE = +LUA_LIB = +LUA_PLATFORM = +LUA_PREFIX = +LUA_SHORT_VERSION = +LUA_VERSION = +MAKEINFO = ${SHELL} /home/borisz/Desktop/thrift_teszt/thrift/missing makeinfo +MANIFEST_TOOL = : +MAYBE_CPP = cpp +MAYBE_C_GLIB = +MAYBE_D = +MAYBE_DART = +MAYBE_ERLANG = +MAYBE_GO = +MAYBE_JAVA = +MAYBE_LUA = +MAYBE_NETSTD = netstd +MAYBE_NODEJS = nodejs +MAYBE_NODETS = nodets +MAYBE_PERL = +MAYBE_PHP = +MAYBE_PY3 = py3 +MAYBE_PYTHON = py +MAYBE_RS = +MAYBE_RUBY = +MAYBE_SWIFT = +MKDIR_P = /usr/bin/mkdir -p +NM = /usr/bin/nm -B +NMEDIT = +NODEJS = /usr/bin/nodejs +NODETS = /usr/bin/node +NPM = /usr/bin/npm +OBJDUMP = objdump +OBJEXT = o +OPENSSL_INCLUDES = +OPENSSL_LDFLAGS = +OPENSSL_LIBS = -lssl -lcrypto +OTOOL = +OTOOL64 = +PACKAGE = thrift +PACKAGE_BUGREPORT = +PACKAGE_NAME = thrift +PACKAGE_STRING = thrift 0.17.0 +PACKAGE_TARNAME = thrift +PACKAGE_URL = +PACKAGE_VERSION = 0.17.0 +PATH_SEPARATOR = : +PERL = /usr/bin/perl +PERL_PREFIX = /usr/local +PHP = +PHP_CONFIG = +PHP_CONFIG_PREFIX = /etc/php.d +PHP_PREFIX = /usr/lib/php +PKG_CONFIG = /usr/bin/pkg-config +PKG_CONFIG_LIBDIR = +PKG_CONFIG_PATH = +PYTHON = /usr/bin/python2 +PYTHON3 = /usr/bin/python3 +PYTHON_EXEC_PREFIX = ${exec_prefix} +PYTHON_PLATFORM = linux2 +PYTHON_PREFIX = ${prefix} +PYTHON_VERSION = 2.7 +PY_PREFIX = /usr +QT5_CFLAGS = +QT5_LIBS = +QT5_MOC = +RANLIB = ranlib +REBAR = +RUBY = +RUBY_PREFIX = +RUSTC = +SED = /usr/bin/sed +SET_MAKE = +SHELL = /bin/bash +STRIP = strip +SWIFT = +THRIFT = /home/borisz/Desktop/thrift_teszt/thrift/compiler/cpp/thrift +TRIAL = +VERSION = 0.17.0 +YACC = bison -y +YFLAGS = +ZLIB_CPPFLAGS = +ZLIB_LDFLAGS = +ZLIB_LIBS = -lz +abs_builddir = /home/borisz/Desktop/thrift_teszt/thrift/lib/netstd +abs_srcdir = /home/borisz/Desktop/thrift_teszt/thrift/lib/netstd +abs_top_builddir = /home/borisz/Desktop/thrift_teszt/thrift +abs_top_srcdir = /home/borisz/Desktop/thrift_teszt/thrift +ac_ct_AR = ar +ac_ct_CC = gcc +ac_ct_CXX = g++ +ac_ct_DUMPBIN = +am__include = include +am__leading_dot = . +am__quote = +am__tar = tar --hard-dereference --format=ustar -chf - "$$tardir" +am__untar = tar -xf - +bindir = ${exec_prefix}/bin +build = x86_64-pc-linux-gnu +build_alias = +build_cpu = x86_64 +build_os = linux-gnu +build_vendor = pc +builddir = . +datadir = ${datarootdir} +datarootdir = ${prefix}/share +docdir = ${datarootdir}/doc/${PACKAGE_TARNAME} +dvidir = ${docdir} +exec_prefix = ${prefix} +golang_version = +have_prog_bison = yes +host = x86_64-pc-linux-gnu +host_alias = +host_cpu = x86_64 +host_os = linux-gnu +host_vendor = pc +htmldir = ${docdir} +includedir = ${prefix}/include +infodir = ${datarootdir}/info +install_sh = ${SHELL} /home/borisz/Desktop/thrift_teszt/thrift/install-sh +libdir = ${exec_prefix}/lib +libexecdir = ${exec_prefix}/libexec +localedir = ${datarootdir}/locale +localstatedir = ${prefix}/var +luadir = +luaexecdir = +mandir = ${datarootdir}/man +mkdir_p = $(MKDIR_P) +oldincludedir = /usr/include +pdfdir = ${docdir} +pkgluadir = +pkgluaexecdir = +pkgpyexecdir = ${pyexecdir}/thrift +pkgpythondir = ${pythondir}/thrift +prefix = /usr/local +program_transform_name = s,x,x, +psdir = ${docdir} +pyexecdir = ${exec_prefix}/lib/python2.7/dist-packages +pythondir = ${prefix}/lib/python2.7/dist-packages +runstatedir = ${localstatedir}/run +rustc_version = +sbindir = ${exec_prefix}/sbin +sharedstatedir = ${prefix}/com +srcdir = . +subdirs = +sysconfdir = ${prefix}/etc +target_alias = +top_build_prefix = ../../ +top_builddir = ../.. +top_srcdir = ../.. +SUBDIRS = . +EXTRA_DIST = \ + README.md \ + Directory.Build.props \ + Benchmarks/Thrift.Benchmarks \ + Tests/Thrift.IntegrationTests/Protocols \ + Tests/Thrift.IntegrationTests/Thrift.IntegrationTests.csproj \ + Tests/Thrift.PublicInterfaces.Compile.Tests \ + Tests/Thrift.PublicInterfaces.Compile.Tests/CassandraTest.thrift \ + Tests/Thrift.PublicInterfaces.Compile.Tests/optional_required_default.thrift \ + Tests/Thrift.PublicInterfaces.Compile.Tests/Properties/AssemblyInfo.cs \ + Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj \ + Tests/Thrift.Tests/Collections \ + Tests/Thrift.Tests/DataModel \ + Tests/Thrift.Tests/Protocols \ + Tests/Thrift.Tests/Transports \ + Tests/Thrift.Tests/Thrift.Tests.csproj \ + Thrift/.editorconfig \ + Thrift/Collections \ + Thrift/Processor \ + Thrift/Properties \ + Thrift/Protocol \ + Thrift/Server \ + Thrift/GlobalSuppressions.cs \ + Thrift/TApplicationException.cs \ + Thrift/TBaseClient.cs \ + Thrift/TConfiguration.cs \ + Thrift/TException.cs \ + Thrift/Thrift.csproj \ + Thrift/Transport \ + Thrift/*.snk \ + Thrift.sln \ + build.cmd \ + build.sh \ + runtests.cmd \ + runtests.sh + +all: all-recursive + +.SUFFIXES: +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign lib/netstd/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign lib/netstd/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +# This directory's subdirectories are mostly independent; you can cd +# into them and run 'make' without going through this Makefile. +# To change the values of 'make' variables: instead of editing Makefiles, +# (1) if the variable is set in 'config.status', edit 'config.status' +# (which will cause the Makefiles to be regenerated when you run 'make'); +# (2) otherwise, pass the desired values on the 'make' command line. +$(am__recursive_targets): + @fail=; \ + if $(am__make_keepgoing); then \ + failcom='fail=yes'; \ + else \ + failcom='exit 1'; \ + fi; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" +style-local: + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-recursive +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-recursive + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-recursive + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-local +check: check-recursive +all-am: Makefile all-local +installdirs: installdirs-recursive +installdirs-am: +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-generic clean-libtool clean-local mostlyclean-am + +distclean: distclean-recursive + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +style: style-recursive + +style-am: style-local + +uninstall-am: + +.MAKE: $(am__recursive_targets) check-am install-am install-strip + +.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am all-local \ + check check-am check-local clean clean-generic clean-libtool \ + clean-local cscopelist-am ctags ctags-am distclean \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs installdirs-am \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + style-am style-local tags tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +all-local: + $(DOTNETCORE) build -c Release + +check-local: + $(DOTNETCORE) test Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj + $(DOTNETCORE) test Tests/Thrift.Tests/Thrift.Tests.csproj + $(DOTNETCORE) test Tests/Thrift.IntegrationTests/Thrift.IntegrationTests.csproj + +clean-local: + $(RM) -r Thrift/bin + $(RM) -r Thrift/obj + $(RM) -r Benchmarks/Thrift.Benchmarks/bin + $(RM) -r Benchmarks/Thrift.Benchmarks/obj + $(RM) -r Tests/Thrift.Tests/bin + $(RM) -r Tests/Thrift.Tests/obj + $(RM) -r Tests/Thrift.IntegrationTests/bin + $(RM) -r Tests/Thrift.IntegrationTests/obj + $(RM) -r Tests/Thrift.PublicInterfaces.Compile.Tests/bin + $(RM) -r Tests/Thrift.PublicInterfaces.Compile.Tests/obj + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/lib/csharp/thrift_netstd/Makefile.am b/lib/csharp/thrift_netstd/Makefile.am new file mode 100644 index 000000000..47b380309 --- /dev/null +++ b/lib/csharp/thrift_netstd/Makefile.am @@ -0,0 +1,77 @@ +# +# 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. +# + +SUBDIRS = . + +all-local: + $(DOTNETCORE) build -c Release + +check-local: + $(DOTNETCORE) test Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj + $(DOTNETCORE) test Tests/Thrift.Tests/Thrift.Tests.csproj + $(DOTNETCORE) test Tests/Thrift.IntegrationTests/Thrift.IntegrationTests.csproj + +clean-local: + $(RM) -r Thrift/bin + $(RM) -r Thrift/obj + $(RM) -r Benchmarks/Thrift.Benchmarks/bin + $(RM) -r Benchmarks/Thrift.Benchmarks/obj + $(RM) -r Tests/Thrift.Tests/bin + $(RM) -r Tests/Thrift.Tests/obj + $(RM) -r Tests/Thrift.IntegrationTests/bin + $(RM) -r Tests/Thrift.IntegrationTests/obj + $(RM) -r Tests/Thrift.PublicInterfaces.Compile.Tests/bin + $(RM) -r Tests/Thrift.PublicInterfaces.Compile.Tests/obj + +EXTRA_DIST = \ + README.md \ + Directory.Build.props \ + Benchmarks/Thrift.Benchmarks \ + Tests/Thrift.IntegrationTests/Protocols \ + Tests/Thrift.IntegrationTests/Thrift.IntegrationTests.csproj \ + Tests/Thrift.PublicInterfaces.Compile.Tests \ + Tests/Thrift.PublicInterfaces.Compile.Tests/CassandraTest.thrift \ + Tests/Thrift.PublicInterfaces.Compile.Tests/optional_required_default.thrift \ + Tests/Thrift.PublicInterfaces.Compile.Tests/Properties/AssemblyInfo.cs \ + Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj \ + Tests/Thrift.Tests/Collections \ + Tests/Thrift.Tests/DataModel \ + Tests/Thrift.Tests/Protocols \ + Tests/Thrift.Tests/Transports \ + Tests/Thrift.Tests/Thrift.Tests.csproj \ + Thrift/.editorconfig \ + Thrift/Collections \ + Thrift/Processor \ + Thrift/Properties \ + Thrift/Protocol \ + Thrift/Server \ + Thrift/GlobalSuppressions.cs \ + Thrift/TApplicationException.cs \ + Thrift/TBaseClient.cs \ + Thrift/TConfiguration.cs \ + Thrift/TException.cs \ + Thrift/Thrift.csproj \ + Thrift/Transport \ + Thrift/*.snk \ + Thrift.sln \ + build.cmd \ + build.sh \ + runtests.cmd \ + runtests.sh + diff --git a/lib/csharp/thrift_netstd/Makefile.in b/lib/csharp/thrift_netstd/Makefile.in new file mode 100644 index 000000000..93358786b --- /dev/null +++ b/lib/csharp/thrift_netstd/Makefile.in @@ -0,0 +1,858 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +# +# 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. +# +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = lib/netstd +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/aclocal/ac_prog_bison.m4 \ + $(top_srcdir)/aclocal/ax_boost_base.m4 \ + $(top_srcdir)/aclocal/ax_check_openssl.m4 \ + $(top_srcdir)/aclocal/ax_compare_version.m4 \ + $(top_srcdir)/aclocal/ax_cxx_compile_stdcxx.m4 \ + $(top_srcdir)/aclocal/ax_cxx_compile_stdcxx_11.m4 \ + $(top_srcdir)/aclocal/ax_dmd.m4 \ + $(top_srcdir)/aclocal/ax_javac_and_java.m4 \ + $(top_srcdir)/aclocal/ax_lib_event.m4 \ + $(top_srcdir)/aclocal/ax_lib_zlib.m4 \ + $(top_srcdir)/aclocal/ax_lua.m4 \ + $(top_srcdir)/aclocal/ax_prog_dotnetcore_version.m4 \ + $(top_srcdir)/aclocal/ax_prog_haxe_version.m4 \ + $(top_srcdir)/aclocal/ax_prog_perl_modules.m4 \ + $(top_srcdir)/aclocal/ax_signed_right_shift.m4 \ + $(top_srcdir)/aclocal/ax_thrift_internal.m4 \ + $(top_srcdir)/aclocal/libtool.m4 \ + $(top_srcdir)/aclocal/ltoptions.m4 \ + $(top_srcdir)/aclocal/ltsugar.m4 \ + $(top_srcdir)/aclocal/ltversion.m4 \ + $(top_srcdir)/aclocal/lt~obsolete.m4 \ + $(top_srcdir)/aclocal/tar.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h \ + $(top_builddir)/lib/cpp/src/thrift/config.h \ + $(top_builddir)/lib/c_glib/src/thrift/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ + ctags-recursive dvi-recursive html-recursive info-recursive \ + install-data-recursive install-dvi-recursive \ + install-exec-recursive install-html-recursive \ + install-info-recursive install-pdf-recursive \ + install-ps-recursive install-recursive installcheck-recursive \ + installdirs-recursive pdf-recursive ps-recursive \ + tags-recursive uninstall-recursive +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + distdir distdir-am +am__extra_recursive_targets = style-recursive +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DIST_SUBDIRS = $(SUBDIRS) +am__DIST_COMMON = $(srcdir)/Makefile.in +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +ACLOCAL = @ACLOCAL@ +ALLOCA = @ALLOCA@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +ANT = @ANT@ +ANT_FLAGS = @ANT_FLAGS@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BISON = @BISON@ +BOOST_CHRONO_LDADD = @BOOST_CHRONO_LDADD@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_FILESYSTEM_LDADD = @BOOST_FILESYSTEM_LDADD@ +BOOST_LDFLAGS = @BOOST_LDFLAGS@ +BOOST_LIB_DIR = @BOOST_LIB_DIR@ +BOOST_SYSTEM_LDADD = @BOOST_SYSTEM_LDADD@ +BOOST_TEST_LDADD = @BOOST_TEST_LDADD@ +BOOST_THREAD_LDADD = @BOOST_THREAD_LDADD@ +BUNDLER = @BUNDLER@ +CARGO = @CARGO@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CLASSPATH = @CLASSPATH@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CPPSTYLE_CMD = @CPPSTYLE_CMD@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DART = @DART@ +DARTPUB = @DARTPUB@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DMD = @DMD@ +DMD_LIBEVENT_FLAGS = @DMD_LIBEVENT_FLAGS@ +DMD_OF_DIRSEP = @DMD_OF_DIRSEP@ +DMD_OPENSSL_FLAGS = @DMD_OPENSSL_FLAGS@ +DOTNETCORE = @DOTNETCORE@ +DOTNETCORE_VERSION = @DOTNETCORE_VERSION@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +D_EVENT_LIB_NAME = @D_EVENT_LIB_NAME@ +D_IMPORT_PREFIX = @D_IMPORT_PREFIX@ +D_LIB_NAME = @D_LIB_NAME@ +D_SSL_LIB_NAME = @D_SSL_LIB_NAME@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ENABLE_COVERAGE = @ENABLE_COVERAGE@ +ERL = @ERL@ +ERLANG_INSTALL_LIB_DIR = @ERLANG_INSTALL_LIB_DIR@ +ERLANG_INSTALL_LIB_DIR_thrift = @ERLANG_INSTALL_LIB_DIR_thrift@ +ERLANG_LIB_DIR = @ERLANG_LIB_DIR@ +ERLC = @ERLC@ +ERLCFLAGS = @ERLCFLAGS@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GCOV_CFLAGS = @GCOV_CFLAGS@ +GCOV_CXXFLAGS = @GCOV_CXXFLAGS@ +GCOV_LDFLAGS = @GCOV_LDFLAGS@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_LIBS = @GLIB_LIBS@ +GO = @GO@ +GOBJECT_CFLAGS = @GOBJECT_CFLAGS@ +GOBJECT_LIBS = @GOBJECT_LIBS@ +GRADLE_OPTS = @GRADLE_OPTS@ +GREP = @GREP@ +GSETTINGS = @GSETTINGS@ +HAVE_CXX11 = @HAVE_CXX11@ +HAXE = @HAXE@ +HAXE_VERSION = @HAXE_VERSION@ +INSTALL = @INSTALL@ +INSTALLDIRS = @INSTALLDIRS@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +JAVA_PREFIX = @JAVA_PREFIX@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LEX = @LEX@ +LEXLIB = @LEXLIB@ +LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@ +LIBEVENT_CPPFLAGS = @LIBEVENT_CPPFLAGS@ +LIBEVENT_LDFLAGS = @LIBEVENT_LDFLAGS@ +LIBEVENT_LIBS = @LIBEVENT_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +LUA = @LUA@ +LUA_EXEC_PREFIX = @LUA_EXEC_PREFIX@ +LUA_INCLUDE = @LUA_INCLUDE@ +LUA_LIB = @LUA_LIB@ +LUA_PLATFORM = @LUA_PLATFORM@ +LUA_PREFIX = @LUA_PREFIX@ +LUA_SHORT_VERSION = @LUA_SHORT_VERSION@ +LUA_VERSION = @LUA_VERSION@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MAYBE_CPP = @MAYBE_CPP@ +MAYBE_C_GLIB = @MAYBE_C_GLIB@ +MAYBE_D = @MAYBE_D@ +MAYBE_DART = @MAYBE_DART@ +MAYBE_ERLANG = @MAYBE_ERLANG@ +MAYBE_GO = @MAYBE_GO@ +MAYBE_JAVA = @MAYBE_JAVA@ +MAYBE_LUA = @MAYBE_LUA@ +MAYBE_NETSTD = @MAYBE_NETSTD@ +MAYBE_NODEJS = @MAYBE_NODEJS@ +MAYBE_NODETS = @MAYBE_NODETS@ +MAYBE_PERL = @MAYBE_PERL@ +MAYBE_PHP = @MAYBE_PHP@ +MAYBE_PY3 = @MAYBE_PY3@ +MAYBE_PYTHON = @MAYBE_PYTHON@ +MAYBE_RS = @MAYBE_RS@ +MAYBE_RUBY = @MAYBE_RUBY@ +MAYBE_SWIFT = @MAYBE_SWIFT@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NODEJS = @NODEJS@ +NODETS = @NODETS@ +NPM = @NPM@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENSSL_INCLUDES = @OPENSSL_INCLUDES@ +OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@ +OPENSSL_LIBS = @OPENSSL_LIBS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PERL = @PERL@ +PERL_PREFIX = @PERL_PREFIX@ +PHP = @PHP@ +PHP_CONFIG = @PHP_CONFIG@ +PHP_CONFIG_PREFIX = @PHP_CONFIG_PREFIX@ +PHP_PREFIX = @PHP_PREFIX@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +PYTHON = @PYTHON@ +PYTHON3 = @PYTHON3@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +PY_PREFIX = @PY_PREFIX@ +QT5_CFLAGS = @QT5_CFLAGS@ +QT5_LIBS = @QT5_LIBS@ +QT5_MOC = @QT5_MOC@ +RANLIB = @RANLIB@ +REBAR = @REBAR@ +RUBY = @RUBY@ +RUBY_PREFIX = @RUBY_PREFIX@ +RUSTC = @RUSTC@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +SWIFT = @SWIFT@ +THRIFT = @THRIFT@ +TRIAL = @TRIAL@ +VERSION = @VERSION@ +YACC = @YACC@ +YFLAGS = @YFLAGS@ +ZLIB_CPPFLAGS = @ZLIB_CPPFLAGS@ +ZLIB_LDFLAGS = @ZLIB_LDFLAGS@ +ZLIB_LIBS = @ZLIB_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +golang_version = @golang_version@ +have_prog_bison = @have_prog_bison@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +luadir = @luadir@ +luaexecdir = @luaexecdir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgluadir = @pkgluadir@ +pkgluaexecdir = @pkgluaexecdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +rustc_version = @rustc_version@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +subdirs = @subdirs@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +SUBDIRS = . +EXTRA_DIST = \ + README.md \ + Directory.Build.props \ + Benchmarks/Thrift.Benchmarks \ + Tests/Thrift.IntegrationTests/Protocols \ + Tests/Thrift.IntegrationTests/Thrift.IntegrationTests.csproj \ + Tests/Thrift.PublicInterfaces.Compile.Tests \ + Tests/Thrift.PublicInterfaces.Compile.Tests/CassandraTest.thrift \ + Tests/Thrift.PublicInterfaces.Compile.Tests/optional_required_default.thrift \ + Tests/Thrift.PublicInterfaces.Compile.Tests/Properties/AssemblyInfo.cs \ + Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj \ + Tests/Thrift.Tests/Collections \ + Tests/Thrift.Tests/DataModel \ + Tests/Thrift.Tests/Protocols \ + Tests/Thrift.Tests/Transports \ + Tests/Thrift.Tests/Thrift.Tests.csproj \ + Thrift/.editorconfig \ + Thrift/Collections \ + Thrift/Processor \ + Thrift/Properties \ + Thrift/Protocol \ + Thrift/Server \ + Thrift/GlobalSuppressions.cs \ + Thrift/TApplicationException.cs \ + Thrift/TBaseClient.cs \ + Thrift/TConfiguration.cs \ + Thrift/TException.cs \ + Thrift/Thrift.csproj \ + Thrift/Transport \ + Thrift/*.snk \ + Thrift.sln \ + build.cmd \ + build.sh \ + runtests.cmd \ + runtests.sh + +all: all-recursive + +.SUFFIXES: +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign lib/netstd/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign lib/netstd/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +# This directory's subdirectories are mostly independent; you can cd +# into them and run 'make' without going through this Makefile. +# To change the values of 'make' variables: instead of editing Makefiles, +# (1) if the variable is set in 'config.status', edit 'config.status' +# (which will cause the Makefiles to be regenerated when you run 'make'); +# (2) otherwise, pass the desired values on the 'make' command line. +$(am__recursive_targets): + @fail=; \ + if $(am__make_keepgoing); then \ + failcom='fail=yes'; \ + else \ + failcom='exit 1'; \ + fi; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" +style-local: + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-recursive +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-recursive + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-recursive + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-local +check: check-recursive +all-am: Makefile all-local +installdirs: installdirs-recursive +installdirs-am: +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-generic clean-libtool clean-local mostlyclean-am + +distclean: distclean-recursive + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +style: style-recursive + +style-am: style-local + +uninstall-am: + +.MAKE: $(am__recursive_targets) check-am install-am install-strip + +.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am all-local \ + check check-am check-local clean clean-generic clean-libtool \ + clean-local cscopelist-am ctags ctags-am distclean \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs installdirs-am \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + style-am style-local tags tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +all-local: + $(DOTNETCORE) build -c Release + +check-local: + $(DOTNETCORE) test Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj + $(DOTNETCORE) test Tests/Thrift.Tests/Thrift.Tests.csproj + $(DOTNETCORE) test Tests/Thrift.IntegrationTests/Thrift.IntegrationTests.csproj + +clean-local: + $(RM) -r Thrift/bin + $(RM) -r Thrift/obj + $(RM) -r Benchmarks/Thrift.Benchmarks/bin + $(RM) -r Benchmarks/Thrift.Benchmarks/obj + $(RM) -r Tests/Thrift.Tests/bin + $(RM) -r Tests/Thrift.Tests/obj + $(RM) -r Tests/Thrift.IntegrationTests/bin + $(RM) -r Tests/Thrift.IntegrationTests/obj + $(RM) -r Tests/Thrift.PublicInterfaces.Compile.Tests/bin + $(RM) -r Tests/Thrift.PublicInterfaces.Compile.Tests/obj + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/lib/csharp/thrift_netstd/README.md b/lib/csharp/thrift_netstd/README.md new file mode 100644 index 000000000..d554e3854 --- /dev/null +++ b/lib/csharp/thrift_netstd/README.md @@ -0,0 +1,54 @@ +# Apache Thrift netstd + +Thrift client library for Microsoft .NET Standard + +# Build the library + +## How to build on Windows +- Get Thrift IDL compiler executable, add to some folder and add path to this folder into PATH variable +- Open the Thrift.sln project with Visual Studio and build. +or +- Build with scripts + +## How to build on Unix/Linux +- Ensure you have .NET Core SDK 3.1 (LTS) installed, or use the [Ubuntu docker image](../../build/docker/README.md) +- Follow common automake build practice: `./ bootstrap && ./ configure && make` + +## Known issues +- In trace logging mode you can see some not important internal exceptions + +# Migration to netstd + +## ... from netcore + +If you are migrating your code from netcore library, you will have to: + +- Switch to `thrift -gen netstd` +- the following compiler flags are no longer needed or supported: `hashcode` is now standard, while `nullable` is no longer supported. +- the `Thrift.Transport` and `Thrift.Protocol` namespaces now use the singular form +- add `using Thrift.Processor;` in the server code where appropriate +- rename all `T*ClientTransport` to `T*Transport` +- rename all `TBaseServer` occurrences in your code to `TServer` +- the `SingletonTProcessorFactory` is now called `TSingletonProcessorFactory` +- and the `AsyncBaseServer` is now the `TSimpleAsyncServer` + +You may wonder why we changed so many names. The naming scheme has been revised for two reasons: First, we want to get back the established, well-known naming consistency across the Thrift libraries which the netcore library did not fully respect. Second, by achieving that first objective, we get the additional benefit of making migration at least a bit easier for C# projects. + +## ... from csharp + +Because of the different environment requirements, migration from C# takes slightly more efforts. While the code changes related to Thrift itself are moderate, you may need to upgrade certain dependencies, components or even modules to more recent versions. + +1. Client and server applications must use at least framework 4.6.1, any version below will not work. +1. Switch to `thrift -gen netstd`. The following compiler flags are no longer needed or supported: `hashcode` and `async` are now standard, while `nullable` is no longer supported. +1. [Familiarize yourself with the `async/await` model](https://msdn.microsoft.com/en-us/magazine/jj991977.aspx), if you have not already done so. As netstd does not support `ISync` anymore, async is mandatory. The synchronous model is simply no longer available (that's also the reason why we don't need the `async` flag anymore). +1. Consider proper use of `cancellationToken` parameters. They are optional but may be quite helpful. +1. As you probably already guessed, there are a few names that have been changed: +- add `using Thrift.Processor;` in the server code where appropriate +- the `TServerSocket` is now called `TServerSocketTransport` +- change `IProtocolFactory` into `ITProtocolFactory` +- if you are looking for `TSimpleServer`, try `TSimpleAsyncServer` instead +- similarly, the `TThreadPoolServer` is now a `TThreadPoolAsyncServer` +- the server's `Serve()` method does now `ServeAsync()` +- In case you are using Thrift server event handlers: the `SetEventHandler` method now starts with an uppercase letter +- and you will also have to revise the method names of all `TServerEventHandler` descendants you have in your code + diff --git a/lib/csharp/thrift_netstd/Tests/Thrift.IntegrationTests/Protocols/ProtocolsOperationsTests.cs b/lib/csharp/thrift_netstd/Tests/Thrift.IntegrationTests/Protocols/ProtocolsOperationsTests.cs new file mode 100644 index 000000000..dd9646ad9 --- /dev/null +++ b/lib/csharp/thrift_netstd/Tests/Thrift.IntegrationTests/Protocols/ProtocolsOperationsTests.cs @@ -0,0 +1,506 @@ +// 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. + +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using KellermanSoftware.CompareNetObjects; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Thrift.Protocol; +using Thrift.Protocol.Entities; +using Thrift.Transport.Client; + +#pragma warning disable IDE0063 // using + +namespace Thrift.IntegrationTests.Protocols +{ + [TestClass] + public class ProtocolsOperationsTests + { + private readonly CompareLogic _compareLogic = new(); + private static readonly TConfiguration Configuration = new(); + + [DataTestMethod] + [DataRow(typeof(TBinaryProtocol), TMessageType.Call)] + [DataRow(typeof(TBinaryProtocol), TMessageType.Exception)] + [DataRow(typeof(TBinaryProtocol), TMessageType.Oneway)] + [DataRow(typeof(TBinaryProtocol), TMessageType.Reply)] + [DataRow(typeof(TCompactProtocol), TMessageType.Call)] + [DataRow(typeof(TCompactProtocol), TMessageType.Exception)] + [DataRow(typeof(TCompactProtocol), TMessageType.Oneway)] + [DataRow(typeof(TCompactProtocol), TMessageType.Reply)] + [DataRow(typeof(TJsonProtocol), TMessageType.Call)] + [DataRow(typeof(TJsonProtocol), TMessageType.Exception)] + [DataRow(typeof(TJsonProtocol), TMessageType.Oneway)] + [DataRow(typeof(TJsonProtocol), TMessageType.Reply)] + public async Task WriteReadMessage_Test(Type protocolType, TMessageType messageType) + { + var expected = new TMessage(nameof(TMessage), messageType, 1); + + try + { + var tuple = GetProtocolInstance(protocolType); + using (var stream = tuple.Item1) + { + var protocol = tuple.Item2; + + await protocol.WriteMessageBeginAsync(expected, default); + await protocol.WriteMessageEndAsync(default); + + stream.Seek(0, SeekOrigin.Begin); + + var actualMessage = await protocol.ReadMessageBeginAsync(default); + await protocol.ReadMessageEndAsync(default); + + var result = _compareLogic.Compare(expected, actualMessage); + Assert.IsTrue(result.AreEqual, result.DifferencesString); + } + } + catch (Exception e) + { + throw new Exception($"Exception during testing of protocol: {protocolType.FullName}", e); + } + } + + [DataTestMethod] + [DataRow(typeof(TBinaryProtocol))] + [DataRow(typeof(TCompactProtocol))] + [DataRow(typeof(TJsonProtocol))] + [ExpectedException(typeof(Exception))] + public async Task WriteReadStruct_Test(Type protocolType) + { + var expected = new TStruct(nameof(TStruct)); + + try + { + var tuple = GetProtocolInstance(protocolType); + using (var stream = tuple.Item1) + { + var protocol = tuple.Item2; + + await protocol.WriteStructBeginAsync(expected, default); + await protocol.WriteStructEndAsync(default); + + stream?.Seek(0, SeekOrigin.Begin); + + var actual = await protocol.ReadStructBeginAsync(default); + await protocol.ReadStructEndAsync(default); + + var result = _compareLogic.Compare(expected, actual); + Assert.IsTrue(result.AreEqual, result.DifferencesString); + } + + } + catch (Exception e) + { + throw new Exception($"Exception during testing of protocol: {protocolType.FullName}", e); + } + } + + [DataTestMethod] + [DataRow(typeof(TBinaryProtocol))] + [DataRow(typeof(TCompactProtocol))] + [DataRow(typeof(TJsonProtocol))] + [ExpectedException(typeof(Exception))] + public async Task WriteReadField_Test(Type protocolType) + { + var expected = new TField(nameof(TField), TType.String, 1); + + try + { + var tuple = GetProtocolInstance(protocolType); + using (var stream = tuple.Item1) + { + var protocol = tuple.Item2; + + await protocol.WriteFieldBeginAsync(expected, default); + await protocol.WriteFieldEndAsync(default); + + stream?.Seek(0, SeekOrigin.Begin); + + var actual = await protocol.ReadFieldBeginAsync(default); + await protocol.ReadFieldEndAsync(default); + + var result = _compareLogic.Compare(expected, actual); + Assert.IsTrue(result.AreEqual, result.DifferencesString); + } + } + catch (Exception e) + { + throw new Exception($"Exception during testing of protocol: {protocolType.FullName}", e); + } + } + + [DataTestMethod] + [DataRow(typeof(TBinaryProtocol))] + [DataRow(typeof(TCompactProtocol))] + [DataRow(typeof(TJsonProtocol))] + public async Task WriteReadMap_Test(Type protocolType) + { + var expected = new TMap(TType.String, TType.String, 1); + + try + { + var tuple = GetProtocolInstance(protocolType); + using (var stream = tuple.Item1) + { + var protocol = tuple.Item2; + + await protocol.WriteMapBeginAsync(expected, default); + await protocol.WriteMapEndAsync(default); + + stream?.Seek(0, SeekOrigin.Begin); + + var actual = await protocol.ReadMapBeginAsync(default); + await protocol.ReadMapEndAsync(default); + + var result = _compareLogic.Compare(expected, actual); + Assert.IsTrue(result.AreEqual, result.DifferencesString); + } + } + catch (Exception e) + { + throw new Exception($"Exception during testing of protocol: {protocolType.FullName}", e); + } + + } + + [DataTestMethod] + [DataRow(typeof(TBinaryProtocol))] + [DataRow(typeof(TCompactProtocol))] + [DataRow(typeof(TJsonProtocol))] + public async Task WriteReadList_Test(Type protocolType) + { + var expected = new TList(TType.String, 1); + + try + { + var tuple = GetProtocolInstance(protocolType); + using (var stream = tuple.Item1) + { + var protocol = tuple.Item2; + + await protocol.WriteListBeginAsync(expected, default); + await protocol.WriteListEndAsync(default); + + stream?.Seek(0, SeekOrigin.Begin); + + var actual = await protocol.ReadListBeginAsync(default); + await protocol.ReadListEndAsync(default); + + var result = _compareLogic.Compare(expected, actual); + Assert.IsTrue(result.AreEqual, result.DifferencesString); + } + } + catch (Exception e) + { + throw new Exception($"Exception during testing of protocol: {protocolType.FullName}", e); + } + } + + [DataTestMethod] + [DataRow(typeof(TBinaryProtocol))] + [DataRow(typeof(TCompactProtocol))] + [DataRow(typeof(TJsonProtocol))] + public async Task WriteReadSet_Test(Type protocolType) + { + var expected = new TSet(TType.String, 1); + + try + { + var tuple = GetProtocolInstance(protocolType); + using (var stream = tuple.Item1) + { + var protocol = tuple.Item2; + + await protocol.WriteSetBeginAsync(expected, default); + await protocol.WriteSetEndAsync(default); + + stream?.Seek(0, SeekOrigin.Begin); + + var actual = await protocol.ReadSetBeginAsync(default); + await protocol.ReadSetEndAsync(default); + + var result = _compareLogic.Compare(expected, actual); + Assert.IsTrue(result.AreEqual, result.DifferencesString); + } + } + catch (Exception e) + { + throw new Exception($"Exception during testing of protocol: {protocolType.FullName}", e); + } + } + + [DataTestMethod] + [DataRow(typeof(TBinaryProtocol))] + [DataRow(typeof(TCompactProtocol))] + [DataRow(typeof(TJsonProtocol))] + public async Task WriteReadBool_Test(Type protocolType) + { + var expected = true; + + try + { + var tuple = GetProtocolInstance(protocolType); + using (var stream = tuple.Item1) + { + var protocol = tuple.Item2; + + await protocol.WriteBoolAsync(expected, default); + + stream?.Seek(0, SeekOrigin.Begin); + + var actual = await protocol.ReadBoolAsync(default); + + var result = _compareLogic.Compare(expected, actual); + Assert.IsTrue(result.AreEqual, result.DifferencesString); + } + } + catch (Exception e) + { + throw new Exception($"Exception during testing of protocol: {protocolType.FullName}", e); + } + } + + [DataTestMethod] + [DataRow(typeof(TBinaryProtocol))] + [DataRow(typeof(TCompactProtocol))] + [DataRow(typeof(TJsonProtocol))] + public async Task WriteReadByte_Test(Type protocolType) + { + var expected = sbyte.MaxValue; + + try + { + var tuple = GetProtocolInstance(protocolType); + using (var stream = tuple.Item1) + { + var protocol = tuple.Item2; + + await protocol.WriteByteAsync(expected, default); + + stream?.Seek(0, SeekOrigin.Begin); + + var actual = await protocol.ReadByteAsync(default); + + var result = _compareLogic.Compare(expected, actual); + Assert.IsTrue(result.AreEqual, result.DifferencesString); + } + } + catch (Exception e) + { + throw new Exception($"Exception during testing of protocol: {protocolType.FullName}", e); + } + } + + [DataTestMethod] + [DataRow(typeof(TBinaryProtocol))] + [DataRow(typeof(TCompactProtocol))] + [DataRow(typeof(TJsonProtocol))] + public async Task WriteReadI16_Test(Type protocolType) + { + var expected = short.MaxValue; + + try + { + var tuple = GetProtocolInstance(protocolType); + using (var stream = tuple.Item1) + { + var protocol = tuple.Item2; + + await protocol.WriteI16Async(expected, default); + + stream?.Seek(0, SeekOrigin.Begin); + + var actual = await protocol.ReadI16Async(default); + + var result = _compareLogic.Compare(expected, actual); + Assert.IsTrue(result.AreEqual, result.DifferencesString); + } + } + catch (Exception e) + { + throw new Exception($"Exception during testing of protocol: {protocolType.FullName}", e); + } + } + + [DataTestMethod] + [DataRow(typeof(TBinaryProtocol))] + [DataRow(typeof(TCompactProtocol))] + [DataRow(typeof(TJsonProtocol))] + public async Task WriteReadI32_Test(Type protocolType) + { + var expected = int.MaxValue; + + try + { + var tuple = GetProtocolInstance(protocolType); + using (var stream = tuple.Item1) + { + var protocol = tuple.Item2; + + await protocol.WriteI32Async(expected, default); + + stream?.Seek(0, SeekOrigin.Begin); + + var actual = await protocol.ReadI32Async(default); + + var result = _compareLogic.Compare(expected, actual); + Assert.IsTrue(result.AreEqual, result.DifferencesString); + } + } + catch (Exception e) + { + throw new Exception($"Exception during testing of protocol: {protocolType.FullName}", e); + } + } + + [DataTestMethod] + [DataRow(typeof(TBinaryProtocol))] + [DataRow(typeof(TCompactProtocol))] + [DataRow(typeof(TJsonProtocol))] + public async Task WriteReadI64_Test(Type protocolType) + { + var expected = long.MaxValue; + + try + { + var tuple = GetProtocolInstance(protocolType); + using (var stream = tuple.Item1) + { + var protocol = tuple.Item2; + + await protocol.WriteI64Async(expected, default); + + stream?.Seek(0, SeekOrigin.Begin); + + var actual = await protocol.ReadI64Async(default); + + var result = _compareLogic.Compare(expected, actual); + Assert.IsTrue(result.AreEqual, result.DifferencesString); + } + } + catch (Exception e) + { + throw new Exception($"Exception during testing of protocol: {protocolType.FullName}", e); + } + } + + [DataTestMethod] + [DataRow(typeof(TBinaryProtocol))] + [DataRow(typeof(TCompactProtocol))] + [DataRow(typeof(TJsonProtocol))] + public async Task WriteReadDouble_Test(Type protocolType) + { + var expected = double.MaxValue; + + try + { + var tuple = GetProtocolInstance(protocolType); + using (var stream = tuple.Item1) + { + var protocol = tuple.Item2; + + await protocol.WriteDoubleAsync(expected, default); + + stream?.Seek(0, SeekOrigin.Begin); + + var actual = await protocol.ReadDoubleAsync(default); + + var result = _compareLogic.Compare(expected, actual); + Assert.IsTrue(result.AreEqual, result.DifferencesString); + } + } + catch (Exception e) + { + throw new Exception($"Exception during testing of protocol: {protocolType.FullName}", e); + } + } + + [DataTestMethod] + [DataRow(typeof(TBinaryProtocol))] + [DataRow(typeof(TCompactProtocol))] + [DataRow(typeof(TJsonProtocol))] + public async Task WriteReadString_Test(Type protocolType) + { + var expected = nameof(String); + + try + { + var tuple = GetProtocolInstance(protocolType); + using (var stream = tuple.Item1) + { + var protocol = tuple.Item2; + + await protocol.WriteStringAsync(expected, default); + + stream?.Seek(0, SeekOrigin.Begin); + + var actual = await protocol.ReadStringAsync(default); + + var result = _compareLogic.Compare(expected, actual); + Assert.IsTrue(result.AreEqual, result.DifferencesString); + } + } + catch (Exception e) + { + throw new Exception($"Exception during testing of protocol: {protocolType.FullName}", e); + } + } + + [DataTestMethod] + [DataRow(typeof(TBinaryProtocol))] + [DataRow(typeof(TCompactProtocol))] + [DataRow(typeof(TJsonProtocol))] + public async Task WriteReadBinary_Test(Type protocolType) + { + var expected = Encoding.UTF8.GetBytes(nameof(String)); + + try + { + var tuple = GetProtocolInstance(protocolType); + using (var stream = tuple.Item1) + { + var protocol = tuple.Item2; + + await protocol.WriteBinaryAsync(expected, default); + + stream?.Seek(0, SeekOrigin.Begin); + + var actual = await protocol.ReadBinaryAsync(default); + + var result = _compareLogic.Compare(expected, actual); + Assert.IsTrue(result.AreEqual, result.DifferencesString); + } + } + catch (Exception e) + { + throw new Exception($"Exception during testing of protocol: {protocolType.FullName}", e); + } + } + + private static Tuple GetProtocolInstance(Type protocolType) + { + var memoryStream = new MemoryStream(); + var streamClientTransport = new TStreamTransport(memoryStream, memoryStream,Configuration); + if( Activator.CreateInstance(protocolType, streamClientTransport) is TProtocol protocol) + return new Tuple(memoryStream, protocol); + throw new Exception("Unexpected"); + } + } +} diff --git a/lib/csharp/thrift_netstd/Tests/Thrift.IntegrationTests/Thrift.IntegrationTests.csproj b/lib/csharp/thrift_netstd/Tests/Thrift.IntegrationTests/Thrift.IntegrationTests.csproj new file mode 100644 index 000000000..8c1b6a96e --- /dev/null +++ b/lib/csharp/thrift_netstd/Tests/Thrift.IntegrationTests/Thrift.IntegrationTests.csproj @@ -0,0 +1,53 @@ + + + + + net6.0 + Thrift.IntegrationTests + Thrift.IntegrationTests + 0.17.0.0 + Exe + false + false + false + false + false + false + enable + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/CassandraTest.thrift b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/CassandraTest.thrift new file mode 100644 index 000000000..26cb380c5 --- /dev/null +++ b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/CassandraTest.thrift @@ -0,0 +1,705 @@ +#!/usr/local/bin/thrift --java --php --py +# 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. + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# *** PLEASE REMEMBER TO EDIT THE VERSION CONSTANT WHEN MAKING CHANGES *** +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# +# Interface definition for Cassandra Service +# + +namespace netstd Apache.Cassandra.Test + +# Thrift.rb has a bug where top-level modules that include modules +# with the same name are not properly referenced, so we can't do +# Cassandra::Cassandra::Client. +namespace rb CassandraThrift + +# The API version (NOT the product version), composed as a dot delimited +# string with major, minor, and patch level components. +# +# - Major: Incremented for backward incompatible changes. An example would +# be changes to the number or disposition of method arguments. +# - Minor: Incremented for backward compatible changes. An example would +# be the addition of a new (optional) method. +# - Patch: Incremented for bug fixes. The patch level should be increased +# for every edit that doesn't result in a change to major/minor. +# +# See the Semantic Versioning Specification (SemVer) http://semver.org. +const string VERSION = "19.24.0" + + +# +# data structures +# + +/** Basic unit of data within a ColumnFamily. + * @param name, the name by which this column is set and retrieved. Maximum 64KB long. + * @param value. The data associated with the name. Maximum 2GB long, but in practice you should limit it to small numbers of MB (since Thrift must read the full value into memory to operate on it). + * @param timestamp. The timestamp is used for conflict detection/resolution when two columns with same name need to be compared. + * @param ttl. An optional, positive delay (in seconds) after which the column will be automatically deleted. + */ +struct Column { + 1: required binary name, + 2: optional binary value, + 3: optional i64 timestamp, + 4: optional i32 ttl, +} + +/** A named list of columns. + * @param name. see Column.name. + * @param columns. A collection of standard Columns. The columns within a super column are defined in an adhoc manner. + * Columns within a super column do not have to have matching structures (similarly named child columns). + */ +struct SuperColumn { + 1: required binary name, + 2: required list columns, +} + +struct CounterColumn { + 1: required binary name, + 2: required i64 value +} + +struct CounterSuperColumn { + 1: required binary name, + 2: required list columns +} + +/** + Methods for fetching rows/records from Cassandra will return either a single instance of ColumnOrSuperColumn or a list + of ColumnOrSuperColumns (get_slice()). If you're looking up a SuperColumn (or list of SuperColumns) then the resulting + instances of ColumnOrSuperColumn will have the requested SuperColumn in the attribute super_column. For queries resulting + in Columns, those values will be in the attribute column. This change was made between 0.3 and 0.4 to standardize on + single query methods that may return either a SuperColumn or Column. + + If the query was on a counter column family, you will either get a counter_column (instead of a column) or a + counter_super_column (instead of a super_column) + + @param column. The Column returned by get() or get_slice(). + @param super_column. The SuperColumn returned by get() or get_slice(). + @param counter_column. The Counterolumn returned by get() or get_slice(). + @param counter_super_column. The CounterSuperColumn returned by get() or get_slice(). + */ +struct ColumnOrSuperColumn { + 1: optional Column column, + 2: optional SuperColumn super_column, + 3: optional CounterColumn counter_column, + 4: optional CounterSuperColumn counter_super_column +} + + +# +# Exceptions +# (note that internal server errors will raise a TApplicationException, courtesy of Thrift) +# + +/** A specific column was requested that does not exist. */ +exception NotFoundException { +} + +/** Invalid request could mean keyspace or column family does not exist, required parameters are missing, or a parameter is malformed. + why contains an associated error message. +*/ +exception InvalidRequestException { + 1: required string why +} + +/** Not all the replicas required could be created and/or read. */ +exception UnavailableException { +} + +/** RPC timeout was exceeded. either a node failed mid-operation, or load was too high, or the requested op was too large. */ +exception TimedOutException { +} + +/** invalid authentication request (invalid keyspace, user does not exist, or credentials invalid) */ +exception AuthenticationException { + 1: required string why +} + +/** invalid authorization request (user does not have access to keyspace) */ +exception AuthorizationException { + 1: required string why +} + +/** schemas are not in agreement across all nodes */ +exception SchemaDisagreementException { +} + + +# +# service api +# +/** + * The ConsistencyLevel is an enum that controls both read and write + * behavior based on the ReplicationFactor of the keyspace. The + * different consistency levels have different meanings, depending on + * if you're doing a write or read operation. + * + * If W + R > ReplicationFactor, where W is the number of nodes to + * block for on write, and R the number to block for on reads, you + * will have strongly consistent behavior; that is, readers will + * always see the most recent write. Of these, the most interesting is + * to do QUORUM reads and writes, which gives you consistency while + * still allowing availability in the face of node failures up to half + * of . Of course if latency is more important than + * consistency then you can use lower values for either or both. + * + * Some ConsistencyLevels (ONE, TWO, THREE) refer to a specific number + * of replicas rather than a logical concept that adjusts + * automatically with the replication factor. Of these, only ONE is + * commonly used; TWO and (even more rarely) THREE are only useful + * when you care more about guaranteeing a certain level of + * durability, than consistency. + * + * Write consistency levels make the following guarantees before reporting success to the client: + * ANY Ensure that the write has been written once somewhere, including possibly being hinted in a non-target node. + * ONE Ensure that the write has been written to at least 1 node's commit log and memory table + * TWO Ensure that the write has been written to at least 2 node's commit log and memory table + * THREE Ensure that the write has been written to at least 3 node's commit log and memory table + * QUORUM Ensure that the write has been written to / 2 + 1 nodes + * LOCAL_QUORUM Ensure that the write has been written to / 2 + 1 nodes, within the local datacenter (requires NetworkTopologyStrategy) + * EACH_QUORUM Ensure that the write has been written to / 2 + 1 nodes in each datacenter (requires NetworkTopologyStrategy) + * ALL Ensure that the write is written to <ReplicationFactor> nodes before responding to the client. + * + * Read consistency levels make the following guarantees before returning successful results to the client: + * ANY Not supported. You probably want ONE instead. + * ONE Returns the record obtained from a single replica. + * TWO Returns the record with the most recent timestamp once two replicas have replied. + * THREE Returns the record with the most recent timestamp once three replicas have replied. + * QUORUM Returns the record with the most recent timestamp once a majority of replicas have replied. + * LOCAL_QUORUM Returns the record with the most recent timestamp once a majority of replicas within the local datacenter have replied. + * EACH_QUORUM Returns the record with the most recent timestamp once a majority of replicas within each datacenter have replied. + * ALL Returns the record with the most recent timestamp once all replicas have replied (implies no replica may be down).. +*/ +enum ConsistencyLevel { + ONE = 1, + QUORUM = 2, + LOCAL_QUORUM = 3, + EACH_QUORUM = 4, + ALL = 5, + ANY = 6, + TWO = 7, + THREE = 8, +} + +/** + ColumnParent is used when selecting groups of columns from the same ColumnFamily. In directory structure terms, imagine + ColumnParent as ColumnPath + '/../'. + + See also ColumnPath + */ +struct ColumnParent { + 3: required string column_family, + 4: optional binary super_column, +} + +/** The ColumnPath is the path to a single column in Cassandra. It might make sense to think of ColumnPath and + * ColumnParent in terms of a directory structure. + * + * ColumnPath is used to looking up a single column. + * + * @param column_family. The name of the CF of the column being looked up. + * @param super_column. The super column name. + * @param column. The column name. + */ +struct ColumnPath { + 3: required string column_family, + 4: optional binary super_column, + 5: optional binary column, +} + +/** + A slice range is a structure that stores basic range, ordering and limit information for a query that will return + multiple columns. It could be thought of as Cassandra's version of LIMIT and ORDER BY + + @param start. The column name to start the slice with. This attribute is not required, though there is no default value, + and can be safely set to '', i.e., an empty byte array, to start with the first column name. Otherwise, it + must a valid value under the rules of the Comparator defined for the given ColumnFamily. + @param finish. The column name to stop the slice at. This attribute is not required, though there is no default value, + and can be safely set to an empty byte array to not stop until 'count' results are seen. Otherwise, it + must also be a valid value to the ColumnFamily Comparator. + @param reversed. Whether the results should be ordered in reversed order. Similar to ORDER BY blah DESC in SQL. + @param count. How many columns to return. Similar to LIMIT in SQL. May be arbitrarily large, but Thrift will + materialize the whole result into memory before returning it to the client, so be aware that you may + be better served by iterating through slices by passing the last value of one call in as the 'start' + of the next instead of increasing 'count' arbitrarily large. + */ +struct SliceRange { + 1: required binary start, + 2: required binary finish, + 3: required bool reversed=0, + 4: required i32 count=100, +} + +/** + A SlicePredicate is similar to a mathematic predicate (see http://en.wikipedia.org/wiki/Predicate_(mathematical_logic)), + which is described as "a property that the elements of a set have in common." + + SlicePredicate's in Cassandra are described with either a list of column_names or a SliceRange. If column_names is + specified, slice_range is ignored. + + @param column_name. A list of column names to retrieve. This can be used similar to Memcached's "multi-get" feature + to fetch N known column names. For instance, if you know you wish to fetch columns 'Joe', 'Jack', + and 'Jim' you can pass those column names as a list to fetch all three at once. + @param slice_range. A SliceRange describing how to range, order, and/or limit the slice. + */ +struct SlicePredicate { + 1: optional list column_names, + 2: optional SliceRange slice_range, +} + +enum IndexOperator { + EQ, + GTE, + GT, + LTE, + LT +} + +struct IndexExpression { + 1: required binary column_name, + 2: required IndexOperator op, + 3: required binary value, +} + +struct IndexClause { + 1: required list expressions + 2: required binary start_key, + 3: required i32 count=100, +} + +/** +The semantics of start keys and tokens are slightly different. +Keys are start-inclusive; tokens are start-exclusive. Token +ranges may also wrap -- that is, the end token may be less +than the start one. Thus, a range from keyX to keyX is a +one-element range, but a range from tokenY to tokenY is the +full ring. +*/ +struct KeyRange { + 1: optional binary start_key, + 2: optional binary end_key, + 3: optional string start_token, + 4: optional string end_token, + 5: required i32 count=100 +} + +/** + A KeySlice is key followed by the data it maps to. A collection of KeySlice is returned by the get_range_slice operation. + + @param key. a row key + @param columns. List of data represented by the key. Typically, the list is pared down to only the columns specified by + a SlicePredicate. + */ +struct KeySlice { + 1: required binary key, + 2: required list columns, +} + +struct KeyCount { + 1: required binary key, + 2: required i32 count +} + +/** + * Note that the timestamp is only optional in case of counter deletion. + */ +struct Deletion { + 1: optional i64 timestamp, + 2: optional binary super_column, + 3: optional SlicePredicate predicate, +} + +/** + A Mutation is either an insert (represented by filling column_or_supercolumn) or a deletion (represented by filling the deletion attribute). + @param column_or_supercolumn. An insert to a column or supercolumn (possibly counter column or supercolumn) + @param deletion. A deletion of a column or supercolumn +*/ +struct Mutation { + 1: optional ColumnOrSuperColumn column_or_supercolumn, + 2: optional Deletion deletion, +} + +struct EndpointDetails { + 1: string host, + 2: string datacenter, + 3: optional string rack +} + +/** + A TokenRange describes part of the Cassandra ring, it is a mapping from a range to + endpoints responsible for that range. + @param start_token The first token in the range + @param end_token The last token in the range + @param endpoints The endpoints responsible for the range (listed by their configured listen_address) + @param rpc_endpoints The endpoints responsible for the range (listed by their configured rpc_address) +*/ +struct TokenRange { + 1: required string start_token, + 2: required string end_token, + 3: required list endpoints, + 4: optional list rpc_endpoints + 5: optional list endpoint_details, +} + +/** + Authentication requests can contain any data, dependent on the IAuthenticator used +*/ +struct AuthenticationRequest { + 1: required map credentials +} + +enum IndexType { + KEYS, + CUSTOM +} + +/* describes a column in a column family. */ +struct ColumnDef { + 1: required binary name, + 2: required string validation_class, + 3: optional IndexType index_type, + 4: optional string index_name, + 5: optional map index_options +} + + +/* describes a column family. */ +struct CfDef { + 1: required string keyspace, + 2: required string name, + 3: optional string column_type="Standard", + 5: optional string comparator_type="BytesType", + 6: optional string subcomparator_type, + 8: optional string comment, + 12: optional double read_repair_chance=1.0, + 13: optional list column_metadata, + 14: optional i32 gc_grace_seconds, + 15: optional string default_validation_class, + 16: optional i32 id, + 17: optional i32 min_compaction_threshold, + 18: optional i32 max_compaction_threshold, + 24: optional bool replicate_on_write, + 25: optional double merge_shards_chance, + 26: optional string key_validation_class, + 28: optional binary key_alias, + 29: optional string compaction_strategy, + 30: optional map compaction_strategy_options, + 32: optional map compression_options, + 33: optional double bloom_filter_fp_chance, +} + +/* describes a keyspace. */ +struct KsDef { + 1: required string name, + 2: required string strategy_class, + 3: optional map strategy_options, + + /** @deprecated */ + 4: optional i32 replication_factor, + + 5: required list cf_defs, + 6: optional bool durable_writes=1, +} + +/** CQL query compression */ +enum Compression { + GZIP = 1, + NONE = 2 +} + +enum CqlResultType { + ROWS = 1, + VOID = 2, + INT = 3 +} + +/** Row returned from a CQL query */ +struct CqlRow { + 1: required binary key, + 2: required list columns +} + +struct CqlMetadata { + 1: required map name_types, + 2: required map value_types, + 3: required string default_name_type, + 4: required string default_value_type +} + +struct CqlResult { + 1: required CqlResultType type, + 2: optional list rows, + 3: optional i32 num, + 4: optional CqlMetadata schema +} + +struct CqlPreparedResult { + 1: required i32 itemId, + 2: required i32 count +} + + +service Cassandra { + # auth methods + void login(1: required AuthenticationRequest auth_request) throws (1:AuthenticationException authnx, 2:AuthorizationException authzx), + + # set keyspace + void set_keyspace(1: required string keyspace) throws (1:InvalidRequestException ire), + + # retrieval methods + + /** + Get the Column or SuperColumn at the given column_path. If no value is present, NotFoundException is thrown. (This is + the only method that can throw an exception under non-failure conditions.) + */ + ColumnOrSuperColumn get(1:required binary key, + 2:required ColumnPath column_path, + 3:required ConsistencyLevel consistency_level=ConsistencyLevel.ONE) + throws (1:InvalidRequestException ire, 2:NotFoundException nfe, 3:UnavailableException ue, 4:TimedOutException te), + + /** + Get the group of columns contained by column_parent (either a ColumnFamily name or a ColumnFamily/SuperColumn name + pair) specified by the given SlicePredicate. If no matching values are found, an empty list is returned. + */ + list get_slice(1:required binary key, + 2:required ColumnParent column_parent, + 3:required SlicePredicate predicate, + 4:required ConsistencyLevel consistency_level=ConsistencyLevel.ONE) + throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te), + + /** + returns the number of columns matching predicate for a particular key, + ColumnFamily and optionally SuperColumn. + */ + i32 get_count(1:required binary key, + 2:required ColumnParent column_parent, + 3:required SlicePredicate predicate, + 4:required ConsistencyLevel consistency_level=ConsistencyLevel.ONE) + throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te), + + /** + Performs a get_slice for column_parent and predicate for the given keys in parallel. + */ + map> multiget_slice(1:required list keys, + 2:required ColumnParent column_parent, + 3:required SlicePredicate predicate, + 4:required ConsistencyLevel consistency_level=ConsistencyLevel.ONE) + throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te), + + /** + Perform a get_count in parallel on the given list keys. The return value maps keys to the count found. + */ + map multiget_count(1:required list keys, + 2:required ColumnParent column_parent, + 3:required SlicePredicate predicate, + 4:required ConsistencyLevel consistency_level=ConsistencyLevel.ONE) + throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te), + + /** + returns a subset of columns for a contiguous range of keys. + */ + list get_range_slices(1:required ColumnParent column_parent, + 2:required SlicePredicate predicate, + 3:required KeyRange range, + 4:required ConsistencyLevel consistency_level=ConsistencyLevel.ONE) + throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te), + + /** Returns the subset of columns specified in SlicePredicate for the rows matching the IndexClause */ + list get_indexed_slices(1:required ColumnParent column_parent, + 2:required IndexClause index_clause, + 3:required SlicePredicate column_predicate, + 4:required ConsistencyLevel consistency_level=ConsistencyLevel.ONE) + throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te), + + # modification methods + + /** + * Insert a Column at the given column_parent.column_family and optional column_parent.super_column. + */ + void insert(1:required binary key, + 2:required ColumnParent column_parent, + 3:required Column column, + 4:required ConsistencyLevel consistency_level=ConsistencyLevel.ONE) + throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te), + + /** + * Increment or decrement a counter. + */ + void add(1:required binary key, + 2:required ColumnParent column_parent, + 3:required CounterColumn column, + 4:required ConsistencyLevel consistency_level=ConsistencyLevel.ONE) + throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te), + + /** + Remove data from the row specified by key at the granularity specified by column_path, and the given timestamp. Note + that all the values in column_path besides column_path.column_family are truly optional: you can remove the entire + row by just specifying the ColumnFamily, or you can remove a SuperColumn or a single Column by specifying those levels too. + */ + void remove(1:required binary key, + 2:required ColumnPath column_path, + 3:required i64 timestamp, + 4:ConsistencyLevel consistency_level=ConsistencyLevel.ONE) + throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te), + + /** + * Remove a counter at the specified location. + * Note that counters have limited support for deletes: if you remove a counter, you must wait to issue any following update + * until the delete has reached all the nodes and all of them have been fully compacted. + */ + void remove_counter(1:required binary key, + 2:required ColumnPath path, + 3:required ConsistencyLevel consistency_level=ConsistencyLevel.ONE) + throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te), + + + /** + Mutate many columns or super columns for many row keys. See also: Mutation. + + mutation_map maps key to column family to a list of Mutation objects to take place at that scope. + **/ + void batch_mutate(1:required map>> mutation_map, + 2:required ConsistencyLevel consistency_level=ConsistencyLevel.ONE) + throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te), + + /** + Truncate will mark and entire column family as deleted. + From the user's perspective a successful call to truncate will result complete data deletion from cfname. + Internally, however, disk space will not be immediatily released, as with all deletes in cassandra, this one + only marks the data as deleted. + The operation succeeds only if all hosts in the cluster at available and will throw an UnavailableException if + some hosts are down. + */ + void truncate(1:required string cfname) + throws (1: InvalidRequestException ire, 2: UnavailableException ue, 3: TimedOutException te), + + + + // Meta-APIs -- APIs to get information about the node or cluster, + // rather than user data. The nodeprobe program provides usage examples. + + /** + * for each schema version present in the cluster, returns a list of nodes at that version. + * hosts that do not respond will be under the key DatabaseDescriptor.INITIAL_VERSION. + * the cluster is all on the same version if the size of the map is 1. + */ + map> describe_schema_versions() + throws (1: InvalidRequestException ire), + + /** list the defined keyspaces in this cluster */ + list describe_keyspaces() + throws (1:InvalidRequestException ire), + + /** get the cluster name */ + string describe_cluster_name(), + + /** get the thrift api version */ + string describe_version(), + + /** get the token ring: a map of ranges to host addresses, + represented as a set of TokenRange instead of a map from range + to list of endpoints, because you can't use Thrift structs as + map keys: + https://issues.apache.org/jira/browse/THRIFT-162 + + for the same reason, we can't return a set here, even though + order is neither important nor predictable. */ + list describe_ring(1:required string keyspace) + throws (1:InvalidRequestException ire), + + /** returns the partitioner used by this cluster */ + string describe_partitioner(), + + /** returns the snitch used by this cluster */ + string describe_snitch(), + + /** describe specified keyspace */ + KsDef describe_keyspace(1:required string keyspace) + throws (1:NotFoundException nfe, 2:InvalidRequestException ire), + + /** experimental API for hadoop/parallel query support. + may change violently and without warning. + + returns list of token strings such that first subrange is (list[0], list[1]], + next is (list[1], list[2]], etc. */ + list describe_splits(1:required string cfName, + 2:required string start_token, + 3:required string end_token, + 4:required i32 keys_per_split) + throws (1:InvalidRequestException ire), + + /** adds a column family. returns the new schema id. */ + string system_add_column_family(1:required CfDef cf_def) + throws (1:InvalidRequestException ire, 2:SchemaDisagreementException sde), + + /** drops a column family. returns the new schema id. */ + string system_drop_column_family(1:required string column_family) + throws (1:InvalidRequestException ire, 2:SchemaDisagreementException sde), + + /** adds a keyspace and any column families that are part of it. returns the new schema id. */ + string system_add_keyspace(1:required KsDef ks_def) + throws (1:InvalidRequestException ire, 2:SchemaDisagreementException sde), + + /** drops a keyspace and any column families that are part of it. returns the new schema id. */ + string system_drop_keyspace(1:required string keyspace) + throws (1:InvalidRequestException ire, 2:SchemaDisagreementException sde), + + /** updates properties of a keyspace. returns the new schema id. */ + string system_update_keyspace(1:required KsDef ks_def) + throws (1:InvalidRequestException ire, 2:SchemaDisagreementException sde), + + /** updates properties of a column family. returns the new schema id. */ + string system_update_column_family(1:required CfDef cf_def) + throws (1:InvalidRequestException ire, 2:SchemaDisagreementException sde), + + /** + * Executes a CQL (Cassandra Query Language) statement and returns a + * CqlResult containing the results. + */ + CqlResult execute_cql_query(1:required binary query, 2:required Compression compression) + throws (1:InvalidRequestException ire, + 2:UnavailableException ue, + 3:TimedOutException te, + 4:SchemaDisagreementException sde) + + + /** + * Prepare a CQL (Cassandra Query Language) statement by compiling and returning + * - the type of CQL statement + * - an id token of the compiled CQL stored on the server side. + * - a count of the discovered bound markers in the statement + */ + CqlPreparedResult prepare_cql_query(1:required binary query, 2:required Compression compression) + throws (1:InvalidRequestException ire) + + + /** + * Executes a prepared CQL (Cassandra Query Language) statement by passing an id token and a list of variables + * to bind and returns a CqlResult containing the results. + */ + CqlResult execute_prepared_cql_query(1:required i32 itemId, 2:required list values) + throws (1:InvalidRequestException ire, + 2:UnavailableException ue, + 3:TimedOutException te, + 4:SchemaDisagreementException sde) + + +} diff --git a/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/GlobalSuppressions.cs b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/GlobalSuppressions.cs new file mode 100644 index 000000000..77cd3fc72 --- /dev/null +++ b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/GlobalSuppressions.cs @@ -0,0 +1,9 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Performance", "CA1822", Justification = "", Scope = "module")] +[assembly: SuppressMessage("Style", "IDE0090", Justification = "", Scope = "module")] diff --git a/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Impl/Thrift5253/MyService.cs b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Impl/Thrift5253/MyService.cs new file mode 100644 index 000000000..f42337630 --- /dev/null +++ b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Impl/Thrift5253/MyService.cs @@ -0,0 +1,70 @@ +// 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. + +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Thrift5253; + +namespace Thrift.PublicInterfaces.Compile.Tests.Impl.Thrift5253 +{ + class MyServiceImpl : MyService.IAsync + { + public Task AsyncProcessor_(AsyncProcessor? input, CancellationToken cancellationToken = default) + { + return Task.FromResult(new AsyncProcessor() { Foo = input?.Foo ?? 0 }); + } + + public Task Broken(BrokenArgs? input, CancellationToken cancellationToken = default) + { + return Task.FromResult(new BrokenResult() { Foo = input?.Foo ?? 0 }); + } + + public Task Client_(Client? input, CancellationToken cancellationToken = default) + { + _ = cancellationToken; + return Task.FromResult(new Client() { Foo = input?.Foo ?? 0 }); + } + + public Task IAsync_(IAsync? input, CancellationToken cancellationToken = default) + { + return Task.FromResult(new IAsync() { Foo = input?.Foo ?? 0 }); + } + + public Task InternalStructs_(InternalStructs? input, CancellationToken cancellationToken = default) + { + return Task.FromResult(new InternalStructs() { Foo = input?.Foo ?? 0 }); + } + + public Task TestAsync(CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + + public Task TestXsync(CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + + public Task Works(WorksArrrgs? input, CancellationToken cancellationToken = default) + { + return Task.FromResult(new WorksRslt() { Foo = input?.Foo ?? 0 }); + } + } +} diff --git a/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Properties/AssemblyInfo.cs b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..0bb460ff4 --- /dev/null +++ b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,40 @@ +// 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. + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. + +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("The Apache Software Foundation")] +[assembly: AssemblyProduct("Thrift")] +[assembly: AssemblyCopyright("The Apache Software Foundation")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. + +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM + +[assembly: Guid("d0d3706b-fed5-4cf5-b984-04f448de9d7b")] \ No newline at end of file diff --git a/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj new file mode 100644 index 000000000..42a139cc8 --- /dev/null +++ b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj @@ -0,0 +1,81 @@ + + + + + 0.17.0 + Thrift version $(ThriftVersion) + net6.0 + $(ThriftVersion).0 + Thrift.PublicInterfaces.Compile.Tests + Thrift.PublicInterfaces.Compile.Tests + false + false + false + false + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift5253.thrift b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift5253.thrift new file mode 100644 index 000000000..224ac8574 --- /dev/null +++ b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift5253.thrift @@ -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. + +// Testcase for THRIFT-5253 using Result in result name generates wrong IAsync interface + +namespace * Thrift5253 + + +// this works +struct WorksArrrgs { 1: i32 foo } +struct WorksRslt { 1: i32 foo } + +// this does not +struct BrokenResult{ 1: i32 foo } +struct BrokenArgs { 1: i32 foo } + +struct InternalStructs { 1: optional i32 foo } +struct AsyncProcessor { 1: optional i32 foo } +struct Client { 1: optional i32 foo } +struct IAsync { 1: optional i32 foo } + +struct ReservedMemberName { 1: optional i32 Isset } + +service MyService{ + BrokenResult Broken( 1 : BrokenArgs foo) + WorksRslt Works( 1 : WorksArrrgs foo) + + InternalStructs InternalStructs( 1: InternalStructs foo) + AsyncProcessor AsyncProcessor ( 1: AsyncProcessor foo) + Client Client ( 1: Client foo) + IAsync IAsync ( 1: IAsync foo) + + // inconsistent treatment of methods ending in "Async" + void TestXsync() + void TestAsync() +} + diff --git a/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift5320.enum.thrift b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift5320.enum.thrift new file mode 100644 index 000000000..d4392d6a8 --- /dev/null +++ b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift5320.enum.thrift @@ -0,0 +1,25 @@ +# 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. + +// Testcase for THRIFT-5320 Usage of "Task" as IDL identifier generates uncompileable code + +namespace * Thrift5320.enums + +enum Task { + Zero, + More +} + diff --git a/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift5320.exception.thrift b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift5320.exception.thrift new file mode 100644 index 000000000..b6b8cdab1 --- /dev/null +++ b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift5320.exception.thrift @@ -0,0 +1,27 @@ +# 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. + +// Testcase for THRIFT-5320 Usage of "Task" as IDL identifier generates uncompileable code + +namespace * Thrift5320.exceptions + + + +exception Task { + 1: Task left + 2: Task right +} + diff --git a/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift5320.struct.thrift b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift5320.struct.thrift new file mode 100644 index 000000000..771c48702 --- /dev/null +++ b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift5320.struct.thrift @@ -0,0 +1,26 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +// Testcase for THRIFT-5320 Usage of "Task" as IDL identifier generates uncompileable code + +namespace * Thrift5320.structs + + +struct Task { + 1: Task left + 2: Task right +} + diff --git a/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift5320.thrift b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift5320.thrift new file mode 100644 index 000000000..f677973b0 --- /dev/null +++ b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift5320.thrift @@ -0,0 +1,40 @@ +# 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. + +// Testcase for THRIFT-5320 Usage of "Task" as IDL identifier generates uncompileable code + +namespace * Thrift5320.Task + +include "Thrift5320.enum.thrift" +include "Thrift5320.exception.thrift" +include "Thrift5320.struct.thrift" + +enum Foobar { + Task = 0 +} + + +service Task { + Thrift5320.enum.Task Task( + 1 : Thrift5320.struct.Task foo, + 2: Foobar bar + ) throws ( + 1: Thrift5320.exception.Task error + ) +} + + + diff --git a/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift5382.objs.thrift b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift5382.objs.thrift new file mode 100644 index 000000000..095d7bd67 --- /dev/null +++ b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift5382.objs.thrift @@ -0,0 +1,24 @@ +# 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. + +// Testcase for THRIFT-5382 Netstd default list/set enums values are generated incorrectly + +namespace * Thrift5382.objs + +enum FoobarEnum { + Val_1 = 0, + Val_2 = 1 +} diff --git a/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift5382.thrift b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift5382.thrift new file mode 100644 index 000000000..db53dfa5d --- /dev/null +++ b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift5382.thrift @@ -0,0 +1,36 @@ +# 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. + +// Testcase for THRIFT-5382 Netstd default list/set enums values are generated incorrectly + +namespace * Thrift5382 + +include "Thrift5382.objs.thrift" + +struct RequestModel { + // Breaks + 1: optional set data_1 = [ FoobarEnum.Val_1, FoobarEnum.Val_2 ], + // Breaks + 2: optional list data_2 = [ FoobarEnum.Val_1, FoobarEnum.Val_2 ], + // Works + 3: optional Thrift5382.objs.FoobarEnum data_3 = FoobarEnum.Val_1 +} + +service Test { + void CallMe( + 1 : RequestModel foo, + ) +} diff --git a/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/name_conflicts.enum.thrift b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/name_conflicts.enum.thrift new file mode 100644 index 000000000..c3ca127fa --- /dev/null +++ b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/name_conflicts.enum.thrift @@ -0,0 +1,36 @@ +# 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. + +// Testcases for +// - THRIFT-5091 Netstd generator produces uncompileable code for struct names ending with "_result" or "_args" +// - THRIFT-5444 netstd generator produces uncompileable code for enums ending with "_result" or "_args" + +namespace * name_conflicts_enum + +enum some_result { + foo, + bar, + baz +} + +enum some_args { + foo, + bar, + baz +} + + +// EOF diff --git a/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/name_conflicts.thrift b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/name_conflicts.thrift new file mode 100644 index 000000000..959dc3a25 --- /dev/null +++ b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/name_conflicts.thrift @@ -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. + +// Testcases for +// - THRIFT-5091 Netstd generator produces uncompileable code for struct names ending with "_result" or "_args" +// - THRIFT-5444 netstd generator produces uncompileable code for enums ending with "_result" or "_args" +// - THRIFT-5445 "cancellationToken" cannot be used as argument name + +namespace * name_conflicts + +include "name_conflicts.enum.thrift" + +struct some_struct_args { + 1: name_conflicts.enum.some_args some_args + 2: name_conflicts.enum.some_result some_result + 3: required i32 cancellationToken +} + +exception some_error_result { + 1: name_conflicts.enum.some_args some_args + 2: name_conflicts.enum.some_result some_result + 3: optional i32 cancellationToken +} + +service some_service { + + name_conflicts.enum.some_result some_method( + 1: name_conflicts.enum.some_args some_args + 2: some_struct_args more_args + 3: i32 cancellationToken + ) throws ( + 1: some_error_result cancellationToken + ) + +} + +// EOF diff --git a/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/optional_required_default.thrift b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/optional_required_default.thrift new file mode 100644 index 000000000..4a38205dc --- /dev/null +++ b/lib/csharp/thrift_netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/optional_required_default.thrift @@ -0,0 +1,151 @@ +# 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. + +// Testcase for THRIFT-5216 generate DeepCopy methods + +namespace netstd OptReqDefTest + +enum Distance +{ + foo = 0, + bar = 1, + baz = 2 +} + +struct RaceDetails +{ + // this is really the max field index used here, intentionally placed at the beginning + 666: required Distance triplesix + + // without default values + + 1: optional Distance opt_one + 2: optional double opt_two + 3: optional i16 opt_three + 4: optional string opt_four + 5: optional binary opt_five + 6: optional list opt_six + 7: optional set opt_seven + 8: optional map opt_eight + + 11: required Distance req_one + 12: required double req_two + 13: required i16 req_three + 14: required string req_four + 15: required binary req_five + 16: required list req_six + 17: required set req_seven + 18: required map req_eight + + 21: Distance def_one + 22: double def_two + 23: i16 def_three + 24: string def_four + 25: binary def_five + 26: list def_six + 27: set def_seven + 28: map def_eight + + // having default values + + 31: optional Distance opt_one_with_value = Distance.bar + 32: optional double opt_two_with_value = 2.22 + 33: optional i16 opt_three_with_value = 3 + 34: optional string opt_four_with_value = "four" + 35: optional binary opt_five_with_value = "five\t" + 36: optional list opt_six_with_value = [6] + 37: optional set opt_seven_with_value = [7] + 38: optional map opt_eight_with_value = { 8 : 8 } + + 41: required Distance req_one_with_value = Distance.bar + 42: required double req_two_with_value = 2.22 + 43: required i16 req_three_with_value = 3 + 44: required string req_four_with_value = "four" + 45: required binary req_five_with_value = "five" + 46: required list req_six_with_value = [6] + 47: required set req_seven_with_value = [7] + 48: required map req_eight_with_value = { 8 : 8 } + + 51: Distance def_one_with_value = Distance.bar + 52: double def_two_with_value = 2.22 + 53: i16 def_three_with_value = 3 + 54: string def_four_with_value = "four" + 55: binary def_five_with_value = "five" + 56: list def_six_with_value = [6] + 57: set def_seven_with_value = [7] + 58: map def_eight_with_value = { 8 : 8 } + + 90: optional bool last_of_the_mohicans + + // some more complicated ones, including recursion + + 300: required list far_list + 301: optional set far_set + 302: map far_map + + 310: required set> far_set_list + 311: optional list>> far_list_map_set + 312: map far_map_dist_to_rds + + 320: required RaceDetails req_nested + 321: optional RaceDetails opt_nested + 322: RaceDetails def_nested + + 330: required jack req_union + 331: optional jack opt_union + 332: jack def_union +} + +union jack { + 1: list stars + 2: list stripes + + 310: set> far_set_list + 311: list>> far_list_map_set + 312: map far_map_dist_to_rds + + 320: jack nested_union + 321: RaceDetails nested_struct + + 401: optional Distance opt_one + 402: optional double opt_two + 403: optional i16 opt_three + 404: optional string opt_four + 405: optional binary opt_five + 406: optional list opt_six + 407: optional set opt_seven + 408: optional map opt_eight +} + +typedef RaceDetails RaceDetails2 +typedef list RDs + +exception CrashBoomBang { + 1 : i32 MyErrorCode +} + +service foobar { + set>> DoItNow( 1 : list>> rd, 2: i32 mitDefault = 42) throws (1: CrashBoomBang cbb) +} + +service deprecate_everything { + void Foo( ) ( deprecated = "This method has neither 'x' nor \"y\"" ) + void Bar( ) ( deprecated = "Fails to deliver 中文 колбаса" ) + void Baz( ) ( deprecated = "Need this to work with tabs (\t) or Umlauts (äöüÄÖÜß) too" ) + void Deprecated() ( deprecated ) // no comment +} + + diff --git a/lib/csharp/thrift_netstd/Tests/Thrift.Tests/Collections/TCollectionsTests.cs b/lib/csharp/thrift_netstd/Tests/Thrift.Tests/Collections/TCollectionsTests.cs new file mode 100644 index 000000000..49108d187 --- /dev/null +++ b/lib/csharp/thrift_netstd/Tests/Thrift.Tests/Collections/TCollectionsTests.cs @@ -0,0 +1,240 @@ +// 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. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography.Xml; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Thrift.Collections; + +namespace Thrift.Tests.Collections +{ + // ReSharper disable once InconsistentNaming + [TestClass] + public class TCollectionsTests + { + //TODO: Add tests for IEnumerable with objects and primitive values inside + + [TestMethod] + public void TCollection_List_Equals_Primitive_Test() + { + var collection1 = new List {1,2,3}; + var collection2 = new List {1,2,3}; + Assert.IsTrue(TCollections.Equals(collection1, collection2)); + Assert.IsTrue(collection1.SequenceEqual(collection2)); + } + + [TestMethod] + public void TCollection_List_Equals_Primitive_Different_Test() + { + var collection1 = new List { 1, 2, 3 }; + var collection2 = new List { 1, 2 }; + Assert.IsFalse(TCollections.Equals(collection1, collection2)); + Assert.IsFalse(collection1.SequenceEqual(collection2)); + + collection2.Add(4); + Assert.IsFalse(TCollections.Equals(collection1, collection2)); + Assert.IsFalse(collection1.SequenceEqual(collection2)); + } + + [TestMethod] + public void TCollection_List_Equals_Objects_Test() + { + var collection1 = new List { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } }; + var collection2 = new List { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } }; + Assert.IsTrue(TCollections.Equals(collection1, collection2)); + Assert.IsTrue(collection1.SequenceEqual(collection2)); + } + + [TestMethod] + public void TCollection_List_List_Equals_Objects_Test() + { + var collection1 = new List> { new List { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } } }; + var collection2 = new List> { new List { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } } }; + Assert.IsTrue(TCollections.Equals(collection1, collection2)); + Assert.IsFalse(collection1.SequenceEqual(collection2)); // SequenceEqual() calls Equals() of the inner list instead of SequenceEqual() + } + + [TestMethod] + public void TCollection_List_Equals_OneAndTheSameObject_Test() + { + var collection1 = new List { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } }; + var collection2 = collection1; + Assert.IsTrue(TCollections.Equals(collection1, collection2)); + Assert.IsTrue(collection1.SequenceEqual(collection2)); + } + + [TestMethod] + public void TCollection_Set_Equals_Primitive_Test() + { + var collection1 = new HashSet {1,2,3}; + var collection2 = new HashSet {1,2,3}; + Assert.IsTrue(TCollections.Equals(collection1, collection2)); + Assert.IsTrue(collection1.SequenceEqual(collection2)); + } + + [TestMethod] + public void TCollection_Set_Equals_Primitive_Different_Test() + { + var collection1 = new HashSet { 1, 2, 3 }; + var collection2 = new HashSet { 1, 2 }; + Assert.IsFalse(TCollections.Equals(collection1, collection2)); + Assert.IsFalse(collection1.SequenceEqual(collection2)); + + collection2.Add(4); + Assert.IsFalse(TCollections.Equals(collection1, collection2)); + Assert.IsFalse(collection1.SequenceEqual(collection2)); + } + + [TestMethod] + public void TCollection_Set_Equals_Objects_Test() + { + var collection1 = new HashSet { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } }; + var collection2 = new HashSet { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } }; + Assert.IsTrue(TCollections.Equals(collection1, collection2)); + Assert.IsTrue(collection1.SequenceEqual(collection2)); + } + + [TestMethod] + public void TCollection_Set_Set_Equals_Objects_Test() + { + var collection1 = new HashSet> { new HashSet { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } } }; + var collection2 = new HashSet> { new HashSet { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } } }; + Assert.IsTrue(TCollections.Equals(collection1, collection2)); + Assert.IsFalse(collection1.SequenceEqual(collection2)); // SequenceEqual() calls Equals() of the inner list instead of SequenceEqual() + } + + [TestMethod] + public void TCollection_Set_Equals_OneAndTheSameObject_Test() + { + var collection1 = new HashSet { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } }; + var collection2 = collection1; // references to one and the same collection + Assert.IsTrue(TCollections.Equals(collection1, collection2)); + Assert.IsTrue(collection1.SequenceEqual(collection2)); + } + + + [TestMethod] + public void TCollection_Map_Equals_Primitive_Test() + { + var collection1 = new Dictionary { [1] = 1, [2] = 2, [3] = 3 }; + var collection2 = new Dictionary { [1] = 1, [2] = 2, [3] = 3 }; + Assert.IsTrue(TCollections.Equals(collection1, collection2)); + Assert.IsTrue(collection1.SequenceEqual(collection2)); + } + + [TestMethod] + public void TCollection_Map_Equals_Primitive_Different_Test() + { + var collection1 = new Dictionary { [1] = 1, [2] = 2, [3] = 3 }; + var collection2 = new Dictionary { [1] = 1, [2] = 2 }; + Assert.IsFalse(TCollections.Equals(collection1, collection2)); + Assert.IsFalse(collection1.SequenceEqual(collection2)); + + collection2[3] = 3; + Assert.IsTrue(TCollections.Equals(collection1, collection2)); + Assert.IsTrue(collection1.SequenceEqual(collection2)); + + collection2[3] = 4; + Assert.IsFalse(TCollections.Equals(collection1, collection2)); + } + + [TestMethod] + public void TCollection_Map_Equals_Objects_Test() + { + var collection1 = new Dictionary + { + [1] = new ExampleClass { X = 1 }, + [-1] = new ExampleClass { X = 2 } + }; + var collection2 = new Dictionary + { + [1] = new ExampleClass { X = 1 }, + [-1] = new ExampleClass { X = 2 } + }; + + Assert.IsTrue(TCollections.Equals(collection1, collection2)); + Assert.IsTrue(collection1.SequenceEqual(collection2)); + } + + [TestMethod] + public void TCollection_Map_Map_Equals_Objects_Test() + { + var collection1 = new Dictionary> + { + [0] = new Dictionary + { + [1] = new ExampleClass { X = 1 }, + [-1] = new ExampleClass { X = 2 } + } + }; + var collection2 = new Dictionary> + { + [0] = new Dictionary + { + [1] = new ExampleClass { X = 1 }, + [-1] = new ExampleClass { X = 2 } + } + }; + + Assert.IsTrue(TCollections.Equals(collection1, collection2)); + Assert.IsFalse(collection1.SequenceEqual(collection2)); // SequenceEqual() calls Equals() of the inner list instead of SequenceEqual() + } + + [TestMethod] + public void TCollection_Map_Equals_OneAndTheSameObject_Test() + { + var collection1 = new Dictionary + { + [1] = new ExampleClass { X = 1 }, + [-1] = new ExampleClass { X = 2 } + }; + var collection2 = collection1; + Assert.IsTrue(TCollections.Equals(collection1, collection2)); + Assert.IsTrue(collection1.SequenceEqual(collection2)); + } + + + private class ExampleClass + { + public int X { get; set; } + + // all Thrift-generated classes override Equals(), we do just the same + public override bool Equals(object? that) + { + if (that is not ExampleClass other) return false; + if (ReferenceEquals(this, other)) return true; + + return this.X == other.X; + } + + // overriding Equals() requires GetHashCode() as well + public override int GetHashCode() + { + int hashcode = 157; + unchecked + { + hashcode = (hashcode * 397) + X.GetHashCode(); + } + return hashcode; + } + } + } +} + diff --git a/lib/csharp/thrift_netstd/Tests/Thrift.Tests/Collections/THashSetTests.cs b/lib/csharp/thrift_netstd/Tests/Thrift.Tests/Collections/THashSetTests.cs new file mode 100644 index 000000000..73921ea87 --- /dev/null +++ b/lib/csharp/thrift_netstd/Tests/Thrift.Tests/Collections/THashSetTests.cs @@ -0,0 +1,73 @@ +// 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. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Thrift.Collections; + +#pragma warning disable IDE0063 // simplify using + +namespace Thrift.Tests.Collections +{ + // ReSharper disable once InconsistentNaming + [TestClass] + public class THashSetTests + { + [TestMethod] + public void THashSet_Equals_Primitive_Test() + { + const int value = 1; + + var hashSet = new HashSet {value}; + + Assert.IsTrue(hashSet.Contains(value)); + + hashSet.Remove(value); + + Assert.IsTrue(hashSet.Count == 0); + + hashSet.Add(value); + + Assert.IsTrue(hashSet.Contains(value)); + + hashSet.Clear(); + + Assert.IsTrue(hashSet.Count == 0); + + var newArr = new int[1]; + hashSet.Add(value); + hashSet.CopyTo(newArr, 0); + + Assert.IsTrue(newArr.Contains(value)); + + var en = hashSet.GetEnumerator(); + en.MoveNext(); + + Assert.IsTrue((int)en.Current == value); + + using (var ien = ((IEnumerable)hashSet).GetEnumerator()) + { + ien.MoveNext(); + + Assert.IsTrue(ien.Current == value); + } + } + } +} diff --git a/lib/csharp/thrift_netstd/Tests/Thrift.Tests/DataModel/DeepCopy.cs b/lib/csharp/thrift_netstd/Tests/Thrift.Tests/DataModel/DeepCopy.cs new file mode 100644 index 000000000..afffed5f6 --- /dev/null +++ b/lib/csharp/thrift_netstd/Tests/Thrift.Tests/DataModel/DeepCopy.cs @@ -0,0 +1,610 @@ +// 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. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OptReqDefTest; +using Thrift.Collections; + +namespace Thrift.Tests.DataModel +{ + // ReSharper disable once InconsistentNaming + [TestClass] + public class DeepCopyTests + { + [TestMethod] + public void Test_Complex_DeepCopy() + { + var first = InitializeInstance(new RaceDetails()); + VerifyIdenticalContent(first, InitializeInstance(new RaceDetails())); + + var second = first.DeepCopy(); + VerifyIdenticalContent(first, second); + ModifyInstance(second,0); + VerifyDifferentContent(first, second); + VerifyIdenticalContent(first, InitializeInstance(new RaceDetails())); + + var third = second.DeepCopy(); + VerifyIdenticalContent(second, third); + ModifyInstance(third,0); + VerifyDifferentContent(second, third); + VerifyIdenticalContent(first, InitializeInstance(new RaceDetails())); + } + + private RaceDetails? MakeNestedRaceDetails(int nesting) + { + if (++nesting > 1) + return null; + + var instance = new RaceDetails(); + InitializeInstance(instance,nesting); + return instance; + } + + private jack? MakeNestedUnion(int nesting) + { + if (++nesting > 1) + return null; + + var details = new RaceDetails(); + InitializeInstance(details,nesting); + return new jack.nested_struct(details); + } + + + private RaceDetails InitializeInstance(RaceDetails instance, int nesting = 0) + { + // at init, we intentionally leave all non-required fields unset + Assert.IsFalse(instance.__isset.opt_one); + Assert.IsFalse(instance.__isset.opt_two); + Assert.IsFalse(instance.__isset.opt_three); + Assert.IsFalse(instance.__isset.opt_four); + Assert.IsFalse(instance.__isset.opt_five); + Assert.IsFalse(instance.__isset.opt_six); + Assert.IsFalse(instance.__isset.opt_seven); + Assert.IsFalse(instance.__isset.opt_eight); + + // set all required to null/default + instance.Req_one = default; + instance.Req_two = default; + instance.Req_three = default; + Assert.IsNotNull(instance.Req_four); + Assert.IsNotNull(instance.Req_five); + instance.Req_six = default; + instance.Req_seven = default;; + instance.Req_eight = default; + + // leave non-required fields unset again + Assert.IsFalse(instance.__isset.def_one); + Assert.IsFalse(instance.__isset.def_two); + Assert.IsFalse(instance.__isset.def_three); + Assert.IsFalse(instance.__isset.def_four); + Assert.IsFalse(instance.__isset.def_five); + Assert.IsFalse(instance.__isset.def_six); + Assert.IsFalse(instance.__isset.def_seven); + Assert.IsFalse(instance.__isset.def_eight); + + // these should have IDL defaults set + + Assert.IsTrue(instance.__isset.opt_one_with_value); + Assert.IsTrue(instance.__isset.opt_two_with_value); + Assert.IsTrue(instance.__isset.opt_three_with_value); + Assert.IsTrue(instance.__isset.opt_four_with_value); + Assert.IsTrue(instance.__isset.opt_five_with_value); + Assert.IsTrue(instance.__isset.opt_six_with_value); + Assert.IsTrue(instance.__isset.opt_seven_with_value); + Assert.IsTrue(instance.__isset.opt_eight_with_value); + + Assert.AreEqual(instance.Req_one_with_value, (Distance)1); + Assert.AreEqual(instance.Req_two_with_value, 2.22); + Assert.AreEqual(instance.Req_three_with_value, 3); + Assert.AreEqual(instance.Req_four_with_value, "four"); + Assert.AreEqual("five", Encoding.UTF8.GetString(instance.Req_five_with_value!)); + + Assert.IsTrue(instance.Req_six_with_value!.Count == 1); + Assert.AreEqual(instance.Req_six_with_value[0], 6 ); + + Assert.IsTrue(instance.Req_seven_with_value!.Count == 1); + Assert.IsTrue(instance.Req_seven_with_value.Contains(7)); + + Assert.IsTrue(instance.Req_eight_with_value!.Count == 1); + Assert.IsTrue(instance.Req_eight_with_value[8] == 8); + + Assert.IsTrue(instance.__isset.def_one_with_value); + Assert.IsTrue(instance.__isset.def_two_with_value); + Assert.IsTrue(instance.__isset.def_three_with_value); + Assert.IsTrue(instance.__isset.def_four_with_value); + Assert.IsTrue(instance.__isset.def_five_with_value); + Assert.IsTrue(instance.__isset.def_six_with_value); + Assert.IsTrue(instance.__isset.def_seven_with_value); + Assert.IsTrue(instance.__isset.def_eight_with_value); + + instance.Last_of_the_mohicans = true; + + if (nesting < 2) + { + instance.Far_list = new List() { Distance.foo, Distance.bar, Distance.baz }; + instance.Far_set = new HashSet() { Distance.foo, Distance.bar, Distance.baz }; + instance.Far_map = new Dictionary() { [Distance.foo] = Distance.foo, [Distance.bar] = Distance.bar, [Distance.baz] = Distance.baz }; + + instance.Far_set_list = new HashSet>() { new List() { Distance.foo } }; + instance.Far_list_map_set = new List>>() { new Dictionary>() { [1] = new HashSet() { Distance.baz } } }; + + instance.Far_map_dist_to_rds = new Dictionary>() { [Distance.bar] = new List() }; + var details = MakeNestedRaceDetails(nesting); + if (details != null) + instance.Far_map_dist_to_rds[Distance.bar].Add(details); + + instance.Req_nested = MakeNestedRaceDetails(nesting); + Assert.IsFalse(instance.__isset.opt_nested); + Assert.IsFalse(instance.__isset.def_nested); + + instance.Req_union = MakeNestedUnion(nesting); + Assert.IsFalse(instance.__isset.opt_union); + Assert.IsFalse(instance.__isset.def_union); + } + + instance.Triplesix = (Distance)666; + + return instance; + } + + private void ModifyInstance(RaceDetails instance, int level) + { + if ((instance == null) || (++level > 4)) + return; + + instance.Opt_one = ModifyValue(instance.Opt_one); + instance.Opt_two = ModifyValue(instance.Opt_two); + instance.Opt_three = ModifyValue(instance.Opt_three); + instance.Opt_four = ModifyValue(instance.Opt_four); + instance.Opt_five = ModifyValue(instance.Opt_five); + instance.Opt_six = ModifyValue(instance.Opt_six); + instance.Opt_seven = ModifyValue(instance.Opt_seven); + instance.Opt_eight = ModifyValue(instance.Opt_eight); + + instance.Req_one = ModifyValue(instance.Req_one); + instance.Req_two = ModifyValue(instance.Req_two); + instance.Req_three = ModifyValue(instance.Req_three); + instance.Req_four = ModifyValue(instance.Req_four); + instance.Req_five = ModifyValue(instance.Req_five); + instance.Req_six = ModifyValue(instance.Req_six); + instance.Req_seven = ModifyValue(instance.Req_seven); + instance.Req_eight = ModifyValue(instance.Req_eight); + + instance.Def_one = ModifyValue(instance.Def_one); + instance.Def_two = ModifyValue(instance.Def_two); + instance.Def_three = ModifyValue(instance.Def_three); + instance.Def_four = ModifyValue(instance.Def_four); + instance.Def_five = ModifyValue(instance.Def_five); + instance.Def_six = ModifyValue(instance.Def_six); + instance.Def_seven = ModifyValue(instance.Def_seven); + instance.Def_eight = ModifyValue(instance.Def_eight); + + instance.Opt_one_with_value = ModifyValue(instance.Opt_one_with_value); + instance.Opt_two_with_value = ModifyValue(instance.Opt_two_with_value); + instance.Opt_three_with_value = ModifyValue(instance.Opt_three_with_value); + instance.Opt_four_with_value = ModifyValue(instance.Opt_four_with_value); + instance.Opt_five_with_value = ModifyValue(instance.Opt_five_with_value); + instance.Opt_six_with_value = ModifyValue(instance.Opt_six_with_value); + instance.Opt_seven_with_value = ModifyValue(instance.Opt_seven_with_value); + instance.Opt_eight_with_value = ModifyValue(instance.Opt_eight_with_value); + + instance.Req_one_with_value = ModifyValue(instance.Req_one_with_value); + instance.Req_two_with_value = ModifyValue(instance.Req_two_with_value); + instance.Req_three_with_value = ModifyValue(instance.Req_three_with_value); + instance.Req_four_with_value = ModifyValue(instance.Req_four_with_value); + instance.Req_five_with_value = ModifyValue(instance.Req_five_with_value); + instance.Req_six_with_value = ModifyValue(instance.Req_six_with_value); + instance.Req_seven_with_value = ModifyValue(instance.Req_seven_with_value); + instance.Req_eight_with_value = ModifyValue(instance.Req_eight_with_value); + + instance.Def_one_with_value = ModifyValue(instance.Def_one_with_value); + instance.Def_two_with_value = ModifyValue(instance.Def_two_with_value); + instance.Def_three_with_value = ModifyValue(instance.Def_three_with_value); + instance.Def_four_with_value = ModifyValue(instance.Def_four_with_value); + instance.Def_five_with_value = ModifyValue(instance.Def_five_with_value); + instance.Def_six_with_value = ModifyValue(instance.Def_six_with_value); + instance.Def_seven_with_value = ModifyValue(instance.Def_seven_with_value); + instance.Def_eight_with_value = ModifyValue(instance.Def_eight_with_value); + + instance.Last_of_the_mohicans = ModifyValue(instance.Last_of_the_mohicans); + + instance.Far_list = ModifyValue(instance.Far_list); + instance.Far_set = ModifyValue(instance.Far_set); + instance.Far_map = ModifyValue(instance.Far_map); + + instance.Far_set_list = ModifyValue(instance.Far_set_list); + instance.Far_list_map_set = ModifyValue(instance.Far_list_map_set); + instance.Far_map_dist_to_rds = ModifyValue(instance.Far_map_dist_to_rds, level); + + instance.Req_nested = ModifyValue(instance.Req_nested, level); + instance.Opt_nested = ModifyValue(instance.Opt_nested, level); + instance.Def_nested = ModifyValue(instance.Def_nested, level); + + instance.Req_union = ModifyValue(instance.Req_union, level); + instance.Opt_union = ModifyValue(instance.Opt_union, level); + instance.Def_union = ModifyValue(instance.Def_union, level); + + instance.Triplesix = ModifyValue(instance.Triplesix); + } + + private jack? ModifyValue(jack? value, int level) + { + if (++level > 4) + return value; + + if (value == null) + value = MakeNestedUnion(0); + Debug.Assert(value?.As_nested_struct != null); + ModifyInstance(value.As_nested_struct, level); + return value; + } + + private RaceDetails? ModifyValue(RaceDetails? value, int level) + { + if (++level > 4) + return value; + + if (value == null) + value = new RaceDetails(); + ModifyInstance(value,level); + return value; + } + + private Dictionary> ModifyValue(Dictionary>? value, int level) + { + if (value == null) + value = new Dictionary>(); + + if (++level > 4) + return value; + + var details = new RaceDetails(); + InitializeInstance(details); + value[Distance.foo] = new List() { details }; + + if (value.TryGetValue(Distance.bar, out var list) && (list.Count > 0)) + { + ModifyInstance(list[0], level); + //list.Add(null); -- Thrift does not allow null values in containers + } + + // Thrift does not allow null values in containers + //value[Distance.baz] = null; + + return value; + } + + private static List>> ModifyValue(List>>? value) + { + if (value == null) + value = new List>>(); + + if (value.Count == 0) + value.Add(new Dictionary>()); + //else + //value.Add(null); --Thrift does not allow null values in containers + + sbyte key = (sbyte)(value[0].Count + 10); + if (value[0].Count == 0) + value[0].Add(key, new HashSet()); + //else + //value[0].Add(key, null); --Thrift does not allow null values in containers + + foreach (var entry in value) + { + if (entry != null) + { + foreach (var pair in entry) + { + if (pair.Value != null) + { + if (pair.Value.Contains(Distance.baz)) + pair.Value.Remove(Distance.baz); + else + pair.Value.Add(Distance.baz); + } + } + } + } + + return value; + } + + private static HashSet> ModifyValue(HashSet>? value) + { + if (value == null) + value = new HashSet>(); + + if (value.Count == 0) + value.Add(new List()); + //else + //value.Add(null); -- Thrift does not allow null values in containers + + foreach (var entry in value) + if( entry != null) + entry.Add(Distance.baz); + + return value; + } + + private static Dictionary ModifyValue(Dictionary? value) + { + if (value == null) + value = new Dictionary(); + value[Distance.foo] = value.ContainsKey(Distance.foo) ? ++value[Distance.foo] : Distance.foo; + value[Distance.bar] = value.ContainsKey(Distance.bar) ? ++value[Distance.bar] : Distance.bar; + value[Distance.baz] = value.ContainsKey(Distance.baz) ? ++value[Distance.baz] : Distance.baz; + return value; + } + + private static HashSet ModifyValue(HashSet? value) + { + if (value == null) + value = new HashSet(); + + if (value.Contains(Distance.foo)) + value.Remove(Distance.foo); + else + value.Add(Distance.foo); + + if (value.Contains(Distance.bar)) + value.Remove(Distance.bar); + else + value.Add(Distance.bar); + + if (value.Contains(Distance.baz)) + value.Remove(Distance.baz); + else + value.Add(Distance.baz); + + return value; + } + + private static List ModifyValue(List? value) + { + if (value == null) + value = new List(); + value.Add(Distance.foo); + value.Add(Distance.bar); + value.Add(Distance.baz); + return value; + } + + private static bool ModifyValue(bool value) + { + return !value; + } + + private static Dictionary ModifyValue(Dictionary? value) + { + if (value == null) + value = new Dictionary(); + value.Add((sbyte)(value.Count + 10), (short)value.Count); + return value; + } + + private static HashSet ModifyValue(HashSet? value) + { + if (value == null) + value = new HashSet(); + value.Add(value.Count+100); + return value; + } + + private static List ModifyValue(List? value) + { + if (value == null) + value = new List(); + value.Add(value.Count); + return value; + } + + private static byte[] ModifyValue(byte[]? value) + { + if (value == null) + value = new byte[1] { 0 }; + if (value.Length > 0) + value[0] = (value[0] < 0xFF) ? ++value[0] : (byte)0; + else + value = new byte[1] { 0 }; + return value; + } + + private static string ModifyValue(string? value) + { + return value + "1"; + } + + private static double ModifyValue(double value) + { + return value + 1.1; + } + + private static short ModifyValue(short value) + { + return ++value; + } + + private static Distance ModifyValue(Distance value) + { + return ++value; + } + + private static void VerifyDifferentContent(RaceDetails first, RaceDetails second) + { + Assert.AreNotEqual(first, second); + + Assert.AreNotEqual(first.Opt_two, second.Opt_two); + Assert.AreNotEqual(first.Opt_three, second.Opt_three); + Assert.AreNotEqual(first.Opt_four, second.Opt_four); + Assert.IsFalse(TCollections.Equals(first.Opt_five, second.Opt_five)); + Assert.IsFalse(TCollections.Equals(first.Opt_six, second.Opt_six)); + Assert.IsFalse(TCollections.Equals(first.Opt_seven, second.Opt_seven)); + Assert.IsFalse(TCollections.Equals(first.Opt_eight, second.Opt_eight)); + + Assert.AreNotEqual(first.Req_one, second.Req_one); + Assert.AreNotEqual(first.Req_two, second.Req_two); + Assert.AreNotEqual(first.Req_three, second.Req_three); + Assert.AreNotEqual(first.Req_four, second.Req_four); + Assert.IsFalse(TCollections.Equals(first.Req_five, second.Req_five)); + Assert.IsFalse(TCollections.Equals(first.Req_six, second.Req_six)); + Assert.IsFalse(TCollections.Equals(first.Req_seven, second.Req_seven)); + Assert.IsFalse(TCollections.Equals(first.Req_eight, second.Req_eight)); + + Assert.AreNotEqual(first.Def_one, second.Def_one); + Assert.AreNotEqual(first.Def_two, second.Def_two); + Assert.AreNotEqual(first.Def_three, second.Def_three); + Assert.AreNotEqual(first.Def_four, second.Def_four); + Assert.IsFalse(TCollections.Equals(first.Def_five, second.Def_five)); + Assert.IsFalse(TCollections.Equals(first.Def_six, second.Def_six)); + Assert.IsFalse(TCollections.Equals(first.Def_seven, second.Def_seven)); + Assert.IsFalse(TCollections.Equals(first.Def_eight, second.Def_eight)); + + Assert.AreNotEqual(first.Opt_one_with_value, second.Opt_one_with_value); + Assert.AreNotEqual(first.Opt_two_with_value, second.Opt_two_with_value); + Assert.AreNotEqual(first.Opt_three_with_value, second.Opt_three_with_value); + Assert.AreNotEqual(first.Opt_four_with_value, second.Opt_four_with_value); + Assert.IsFalse(TCollections.Equals(first.Opt_five_with_value, second.Opt_five_with_value)); + Assert.IsFalse(TCollections.Equals(first.Opt_six_with_value, second.Opt_six_with_value)); + Assert.IsFalse(TCollections.Equals(first.Opt_seven_with_value, second.Opt_seven_with_value)); + Assert.IsFalse(TCollections.Equals(first.Opt_eight_with_value, second.Opt_eight_with_value)); + + Assert.AreNotEqual(first.Req_one_with_value, second.Req_one_with_value); + Assert.AreNotEqual(first.Req_two_with_value, second.Req_two_with_value); + Assert.AreNotEqual(first.Req_three_with_value, second.Req_three_with_value); + Assert.AreNotEqual(first.Req_four_with_value, second.Req_four_with_value); + Assert.IsFalse(TCollections.Equals(first.Req_five_with_value, second.Req_five_with_value)); + Assert.IsFalse(TCollections.Equals(first.Req_six_with_value, second.Req_six_with_value)); + Assert.IsFalse(TCollections.Equals(first.Req_seven_with_value, second.Req_seven_with_value)); + Assert.IsFalse(TCollections.Equals(first.Req_eight_with_value, second.Req_eight_with_value)); + + Assert.AreNotEqual(first.Def_one_with_value, second.Def_one_with_value); + Assert.AreNotEqual(first.Def_two_with_value, second.Def_two_with_value); + Assert.AreNotEqual(first.Def_three_with_value, second.Def_three_with_value); + Assert.AreNotEqual(first.Def_four_with_value, second.Def_four_with_value); + Assert.IsFalse(TCollections.Equals(first.Def_five_with_value, second.Def_five_with_value)); + Assert.IsFalse(TCollections.Equals(first.Def_six_with_value, second.Def_six_with_value)); + Assert.IsFalse(TCollections.Equals(first.Def_seven_with_value, second.Def_seven_with_value)); + Assert.IsFalse(TCollections.Equals(first.Def_eight_with_value, second.Def_eight_with_value)); + + Assert.AreNotEqual(first.Last_of_the_mohicans, second.Last_of_the_mohicans); + + Assert.IsFalse(TCollections.Equals(first.Far_list, second.Far_list)); + Assert.IsFalse(TCollections.Equals(first.Far_set, second.Far_set)); + Assert.IsFalse(TCollections.Equals(first.Far_map, second.Far_map)); + + Assert.IsFalse(TCollections.Equals(first.Far_set_list, second.Far_set_list)); + Assert.IsFalse(TCollections.Equals(first.Far_list_map_set, second.Far_list_map_set)); + Assert.IsFalse(TCollections.Equals(first.Far_map_dist_to_rds, second.Far_map_dist_to_rds)); + + Assert.AreNotEqual(first.Req_nested, second.Req_nested); + Assert.AreNotEqual(first.Opt_nested, second.Opt_nested); + Assert.AreNotEqual(first.Def_nested, second.Def_nested); + + Assert.AreNotEqual(first.Req_union, second.Req_union); + Assert.AreNotEqual(first.Opt_union, second.Opt_union); + Assert.AreNotEqual(first.Def_union, second.Def_union); + + Assert.AreNotEqual(first.Triplesix, second.Triplesix); + } + + private static void VerifyIdenticalContent(RaceDetails first, RaceDetails second) + { + Assert.AreEqual(first, second); + + Assert.AreEqual(first.Opt_two, second.Opt_two); + Assert.AreEqual(first.Opt_three, second.Opt_three); + Assert.AreEqual(first.Opt_four, second.Opt_four); + Assert.IsTrue(TCollections.Equals(first.Opt_five, second.Opt_five)); + Assert.IsTrue(TCollections.Equals(first.Opt_six, second.Opt_six)); + Assert.IsTrue(TCollections.Equals(first.Opt_seven, second.Opt_seven)); + Assert.IsTrue(TCollections.Equals(first.Opt_eight, second.Opt_eight)); + + Assert.AreEqual(first.Req_one, second.Req_one); + Assert.AreEqual(first.Req_two, second.Req_two); + Assert.AreEqual(first.Req_three, second.Req_three); + Assert.AreEqual(first.Req_four, second.Req_four); + Assert.IsTrue(TCollections.Equals(first.Req_five, second.Req_five)); + Assert.IsTrue(TCollections.Equals(first.Req_six, second.Req_six)); + Assert.IsTrue(TCollections.Equals(first.Req_seven, second.Req_seven)); + Assert.IsTrue(TCollections.Equals(first.Req_eight, second.Req_eight)); + + Assert.AreEqual(first.Def_one, second.Def_one); + Assert.AreEqual(first.Def_two, second.Def_two); + Assert.AreEqual(first.Def_three, second.Def_three); + Assert.AreEqual(first.Def_four, second.Def_four); + Assert.IsTrue(TCollections.Equals(first.Def_five, second.Def_five)); + Assert.IsTrue(TCollections.Equals(first.Def_six, second.Def_six)); + Assert.IsTrue(TCollections.Equals(first.Def_seven, second.Def_seven)); + Assert.IsTrue(TCollections.Equals(first.Def_eight, second.Def_eight)); + + Assert.AreEqual(first.Opt_one_with_value, second.Opt_one_with_value); + Assert.AreEqual(first.Opt_two_with_value, second.Opt_two_with_value); + Assert.AreEqual(first.Opt_three_with_value, second.Opt_three_with_value); + Assert.AreEqual(first.Opt_four_with_value, second.Opt_four_with_value); + Assert.IsTrue(TCollections.Equals(first.Opt_five_with_value, second.Opt_five_with_value)); + Assert.IsTrue(TCollections.Equals(first.Opt_six_with_value, second.Opt_six_with_value)); + Assert.IsTrue(TCollections.Equals(first.Opt_seven_with_value, second.Opt_seven_with_value)); + Assert.IsTrue(TCollections.Equals(first.Opt_eight_with_value, second.Opt_eight_with_value)); + + Assert.AreEqual(first.Req_one_with_value, second.Req_one_with_value); + Assert.AreEqual(first.Req_two_with_value, second.Req_two_with_value); + Assert.AreEqual(first.Req_three_with_value, second.Req_three_with_value); + Assert.AreEqual(first.Req_four_with_value, second.Req_four_with_value); + Assert.IsTrue(TCollections.Equals(first.Req_five_with_value, second.Req_five_with_value)); + Assert.IsTrue(TCollections.Equals(first.Req_six_with_value, second.Req_six_with_value)); + Assert.IsTrue(TCollections.Equals(first.Req_seven_with_value, second.Req_seven_with_value)); + Assert.IsTrue(TCollections.Equals(first.Req_eight_with_value, second.Req_eight_with_value)); + + Assert.AreEqual(first.Def_one_with_value, second.Def_one_with_value); + Assert.AreEqual(first.Def_two_with_value, second.Def_two_with_value); + Assert.AreEqual(first.Def_three_with_value, second.Def_three_with_value); + Assert.AreEqual(first.Def_four_with_value, second.Def_four_with_value); + Assert.IsTrue(TCollections.Equals(first.Def_five_with_value, second.Def_five_with_value)); + Assert.IsTrue(TCollections.Equals(first.Def_six_with_value, second.Def_six_with_value)); + Assert.IsTrue(TCollections.Equals(first.Def_seven_with_value, second.Def_seven_with_value)); + Assert.IsTrue(TCollections.Equals(first.Def_eight_with_value, second.Def_eight_with_value)); + + Assert.AreEqual(first.Last_of_the_mohicans, second.Last_of_the_mohicans); + + Assert.IsTrue(TCollections.Equals(first.Far_list, second.Far_list)); + Assert.IsTrue(TCollections.Equals(first.Far_set, second.Far_set)); + Assert.IsTrue(TCollections.Equals(first.Far_map, second.Far_map)); + + Assert.IsTrue(TCollections.Equals(first.Far_set_list, second.Far_set_list)); + Assert.IsTrue(TCollections.Equals(first.Far_list_map_set, second.Far_list_map_set)); + Assert.IsTrue(TCollections.Equals(first.Far_map_dist_to_rds, second.Far_map_dist_to_rds)); + + Assert.AreEqual(first.Req_nested, second.Req_nested); + Assert.AreEqual(first.Opt_nested, second.Opt_nested); + Assert.AreEqual(first.Def_nested, second.Def_nested); + + Assert.AreEqual(first.Req_union, second.Req_union); + Assert.AreEqual(first.Opt_union, second.Opt_union); + Assert.AreEqual(first.Def_union, second.Def_union); + + Assert.AreEqual(first.Triplesix, second.Triplesix); + } + + } +} diff --git a/lib/csharp/thrift_netstd/Tests/Thrift.Tests/DataModel/NullValuesSet.cs b/lib/csharp/thrift_netstd/Tests/Thrift.Tests/DataModel/NullValuesSet.cs new file mode 100644 index 000000000..ebc171747 --- /dev/null +++ b/lib/csharp/thrift_netstd/Tests/Thrift.Tests/DataModel/NullValuesSet.cs @@ -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. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OptReqDefTest; +using Thrift.Collections; + +#pragma warning disable IDE0017 // init can be simplified - we don't want that here + +namespace Thrift.Tests.DataModel +{ + // ReSharper disable once InconsistentNaming + [TestClass] + public class Thrift_5238 + { + private static void CheckInstance(RaceDetails instance) + { + // object + Assert.IsTrue(instance.__isset.def_nested); + Assert.IsTrue(instance.__isset.opt_nested); + Assert.IsNull(instance.Def_nested); + Assert.IsNull(instance.Opt_nested); + + // string + Assert.IsTrue(instance.__isset.def_four); + Assert.IsTrue(instance.__isset.opt_four); + Assert.IsTrue(string.IsNullOrEmpty(instance.Req_four)); + Assert.IsNull(instance.Def_four); + Assert.IsNull(instance.Opt_four); + + // byte[] + Assert.IsTrue(instance.__isset.def_five); + Assert.IsTrue(instance.__isset.opt_five); + Assert.IsTrue((instance.Req_five == null) || (instance.Req_five.Length == 0)); + Assert.IsNull(instance.Def_five); + Assert.IsNull(instance.Opt_five); + + // list<> + Assert.IsTrue(instance.__isset.def_six); + Assert.IsTrue(instance.__isset.opt_six); + Assert.IsNull(instance.Req_six); + Assert.IsNull(instance.Opt_six); + Assert.IsNull(instance.Def_six); + } + + [TestMethod] + public void Thrift_5238_ProperNullChecks() + { + var instance = new OptReqDefTest.RaceDetails(); + + // the following code INTENTIONALLY assigns null to non.nullable reftypes + #pragma warning disable CS8625 + + // object + instance.Def_nested = null; + instance.Opt_nested = null; + + // string + instance.Req_four = null; + instance.Def_four = null; + instance.Opt_four = null; + + // byte[] + instance.Req_five = null; + instance.Def_five = null; + instance.Opt_five = null; + + // list<> + instance.Req_six = null; + instance.Opt_six = null; + instance.Def_six = null; + + // back to normal + #pragma warning restore CS8625 + + // test the setup + CheckInstance(instance); + + // validate proper null checks , any of these throws if not + instance.ToString(); + instance.GetHashCode(); + + // validate proper null checks , any of these throws if not + var copy = instance.DeepCopy(); + CheckInstance(copy); + } + + } +} diff --git a/lib/csharp/thrift_netstd/Tests/Thrift.Tests/Protocols/TJsonProtocolHelperTests.cs b/lib/csharp/thrift_netstd/Tests/Thrift.Tests/Protocols/TJsonProtocolHelperTests.cs new file mode 100644 index 000000000..6d391516e --- /dev/null +++ b/lib/csharp/thrift_netstd/Tests/Thrift.Tests/Protocols/TJsonProtocolHelperTests.cs @@ -0,0 +1,172 @@ +// 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. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Thrift.Protocol; +using Thrift.Protocol.Entities; +using Thrift.Protocol.Utilities; + +namespace Thrift.Tests.Protocols +{ + [TestClass] + public class TJSONProtocolHelperTests + { + [TestMethod] + public void GetTypeNameForTypeId_Test() + { + // input/output + var sets = new List> + { + new Tuple(TType.Bool, TJSONProtocolConstants.TypeNames.NameBool), + new Tuple(TType.Byte, TJSONProtocolConstants.TypeNames.NameByte), + new Tuple(TType.I16, TJSONProtocolConstants.TypeNames.NameI16), + new Tuple(TType.I32, TJSONProtocolConstants.TypeNames.NameI32), + new Tuple(TType.I64, TJSONProtocolConstants.TypeNames.NameI64), + new Tuple(TType.Double, TJSONProtocolConstants.TypeNames.NameDouble), + new Tuple(TType.String, TJSONProtocolConstants.TypeNames.NameString), + new Tuple(TType.Struct, TJSONProtocolConstants.TypeNames.NameStruct), + new Tuple(TType.Map, TJSONProtocolConstants.TypeNames.NameMap), + new Tuple(TType.Set, TJSONProtocolConstants.TypeNames.NameSet), + new Tuple(TType.List, TJSONProtocolConstants.TypeNames.NameList), + }; + + foreach (var t in sets) + { + Assert.IsTrue(TJSONProtocolHelper.GetTypeNameForTypeId(t.Item1) == t.Item2, $"Wrong mapping of TypeName {t.Item2} to TType: {t.Item1}"); + } + } + + [TestMethod] + [ExpectedException(typeof(TProtocolException))] + public void GetTypeNameForTypeId_TStop_Test() + { + TJSONProtocolHelper.GetTypeNameForTypeId(TType.Stop); + } + + [TestMethod] + [ExpectedException(typeof(TProtocolException))] + public void GetTypeNameForTypeId_NonExistingTType_Test() + { + TJSONProtocolHelper.GetTypeNameForTypeId((TType)100); + } + + [TestMethod] + public void GetTypeIdForTypeName_Test() + { + // input/output + var sets = new List> + { + new Tuple(TType.Bool, TJSONProtocolConstants.TypeNames.NameBool), + new Tuple(TType.Byte, TJSONProtocolConstants.TypeNames.NameByte), + new Tuple(TType.I16, TJSONProtocolConstants.TypeNames.NameI16), + new Tuple(TType.I32, TJSONProtocolConstants.TypeNames.NameI32), + new Tuple(TType.I64, TJSONProtocolConstants.TypeNames.NameI64), + new Tuple(TType.Double, TJSONProtocolConstants.TypeNames.NameDouble), + new Tuple(TType.String, TJSONProtocolConstants.TypeNames.NameString), + new Tuple(TType.Struct, TJSONProtocolConstants.TypeNames.NameStruct), + new Tuple(TType.Map, TJSONProtocolConstants.TypeNames.NameMap), + new Tuple(TType.Set, TJSONProtocolConstants.TypeNames.NameSet), + new Tuple(TType.List, TJSONProtocolConstants.TypeNames.NameList), + }; + + foreach (var t in sets) + { + Assert.IsTrue(TJSONProtocolHelper.GetTypeIdForTypeName(t.Item2) == t.Item1, $"Wrong mapping of TypeName {t.Item2} to TType: {t.Item1}"); + } + } + + [TestMethod] + [ExpectedException(typeof(TProtocolException))] + public void GetTypeIdForTypeName_TStopTypeName_Test() + { + TJSONProtocolHelper.GetTypeIdForTypeName(new []{(byte)TType.Stop, (byte)TType.Stop}); + } + + [TestMethod] + [ExpectedException(typeof(TProtocolException))] + public void GetTypeIdForTypeName_NonExistingTypeName_Test() + { + TJSONProtocolHelper.GetTypeIdForTypeName(new byte[]{100}); + } + + [TestMethod] + [ExpectedException(typeof(TProtocolException))] + public void GetTypeIdForTypeName_EmptyName_Test() + { + TJSONProtocolHelper.GetTypeIdForTypeName(new byte[] {}); + } + + [TestMethod] + public void IsJsonNumeric_Test() + { + // input/output + var correctJsonNumeric = "+-.0123456789Ee"; + var incorrectJsonNumeric = "AaBcDd/*\\"; + + var sets = correctJsonNumeric.Select(ch => new Tuple((byte) ch, true)).ToList(); + sets.AddRange(incorrectJsonNumeric.Select(ch => new Tuple((byte) ch, false))); + + foreach (var t in sets) + { + Assert.IsTrue(TJSONProtocolHelper.IsJsonNumeric(t.Item1) == t.Item2, $"Wrong mapping of Char {t.Item1} to bool: {t.Item2}"); + } + } + + [TestMethod] + public void ToHexVal_Test() + { + // input/output + var chars = "0123456789abcdef"; + var expectedHexValues = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + + var sets = chars.Select((ch, i) => new Tuple(ch, expectedHexValues[i])).ToList(); + + foreach (var t in sets) + { + var actualResult = TJSONProtocolHelper.ToHexVal((byte)t.Item1); + Assert.IsTrue(actualResult == t.Item2, $"Wrong mapping of char byte {t.Item1} to it expected hex value: {t.Item2}. Actual hex value: {actualResult}"); + } + } + + [TestMethod] + [ExpectedException(typeof(TProtocolException))] + public void ToHexVal_WrongInputChar_Test() + { + TJSONProtocolHelper.ToHexVal((byte)'s'); + } + + [TestMethod] + public void ToHexChar_Test() + { + // input/output + var hexValues = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + var expectedChars = "0123456789abcdef"; + + + var sets = hexValues.Select((hv, i) => new Tuple(hv, expectedChars[i])).ToList(); + + foreach (var t in sets) + { + var actualResult = TJSONProtocolHelper.ToHexChar(t.Item1); + Assert.IsTrue(actualResult == t.Item2, $"Wrong mapping of hex value {t.Item1} to it expected char: {t.Item2}. Actual hex value: {actualResult}"); + } + } + } +} \ No newline at end of file diff --git a/lib/csharp/thrift_netstd/Tests/Thrift.Tests/Protocols/TJsonProtocolTests.cs b/lib/csharp/thrift_netstd/Tests/Thrift.Tests/Protocols/TJsonProtocolTests.cs new file mode 100644 index 000000000..4054a29f2 --- /dev/null +++ b/lib/csharp/thrift_netstd/Tests/Thrift.Tests/Protocols/TJsonProtocolTests.cs @@ -0,0 +1,66 @@ +// 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. + +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Thrift.Protocol; +using Thrift.Protocol.Entities; +using Thrift.Transport; +using Thrift.Transport.Client; + +namespace Thrift.Tests.Protocols +{ + // ReSharper disable once InconsistentNaming + [TestClass] + public class TJSONProtocolTests + { + [TestMethod] + public void TJSONProtocol_Can_Create_Instance_Test() + { + var httpClientTransport = new THttpTransport( new Uri("http://localhost"), null, null, null); + + var result = new TJSONProtocolWrapper(httpClientTransport); + + Assert.IsNotNull(result); + Assert.IsNotNull(result.WrappedContext); + Assert.IsNotNull(result.WrappedReader); + Assert.IsNotNull(result.Transport); + Assert.IsTrue(result.WrappedRecursionDepth == 0); + Assert.IsTrue(result.WrappedRecursionLimit == TConfiguration.DEFAULT_RECURSION_DEPTH); + + Assert.IsTrue(result.Transport.Equals(httpClientTransport)); + Assert.IsTrue(result.WrappedContext.GetType().Name.Equals("JSONBaseContext", StringComparison.OrdinalIgnoreCase)); + Assert.IsTrue(result.WrappedReader.GetType().Name.Equals("LookaheadReader", StringComparison.OrdinalIgnoreCase)); + } + + private class TJSONProtocolWrapper : TJsonProtocol + { + public TJSONProtocolWrapper(TTransport trans) : base(trans) + { + } + + public object WrappedContext => Context; + public object WrappedReader => Reader; + public int WrappedRecursionDepth => RecursionDepth; + public int WrappedRecursionLimit => RecursionLimit; + } + } +} diff --git a/lib/csharp/thrift_netstd/Tests/Thrift.Tests/Thrift.Tests.csproj b/lib/csharp/thrift_netstd/Tests/Thrift.Tests/Thrift.Tests.csproj new file mode 100644 index 000000000..27edf7b8d --- /dev/null +++ b/lib/csharp/thrift_netstd/Tests/Thrift.Tests/Thrift.Tests.csproj @@ -0,0 +1,47 @@ + + + + + net6.0 + 0.17.0.0 + enable + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/csharp/thrift_netstd/Tests/Thrift.Tests/Transports/THttpTransportTests.cs b/lib/csharp/thrift_netstd/Tests/Thrift.Tests/Transports/THttpTransportTests.cs new file mode 100644 index 000000000..2a2d88420 --- /dev/null +++ b/lib/csharp/thrift_netstd/Tests/Thrift.Tests/Transports/THttpTransportTests.cs @@ -0,0 +1,40 @@ +// 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. + +using System.Net.Http; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Thrift.Transport.Client; + +namespace Thrift.Tests.Transports +{ + [TestClass] + public class THttpTransportTests + { + [TestMethod] + public void THttpTransport_Uses_Configured_ConnectionTimeout_Test() + { + var client = new HttpClient(); + var httpClientTransport = new THttpTransport(client, null) + { + ConnectTimeout = 5000 + }; + + Assert.IsTrue(client.Timeout.TotalMilliseconds == 5000); + Assert.IsTrue(httpClientTransport.ConnectTimeout == 5000); + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift.sln b/lib/csharp/thrift_netstd/Thrift.sln new file mode 100644 index 000000000..58c76ced9 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift.sln @@ -0,0 +1,102 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29905.134 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{ED5A45B0-07D1-4507-96B7-83FBD3D031CA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Thrift", "Thrift\Thrift.csproj", "{5B501D21-D428-408D-AB5C-32D6F5355294}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Thrift.IntegrationTests", "Tests\Thrift.IntegrationTests\Thrift.IntegrationTests.csproj", "{837F4084-AAD7-45F5-BC96-10E05A669DB4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Thrift.Tests", "Tests\Thrift.Tests\Thrift.Tests.csproj", "{0790D388-1A3C-4423-8CF2-C97074A8B68B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Thrift.PublicInterfaces.Compile.Tests", "Tests\Thrift.PublicInterfaces.Compile.Tests\Thrift.PublicInterfaces.Compile.Tests.csproj", "{A6AE021D-61CB-4D84-A103-0B663C62AE2C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Benchmarks", "Benchmarks", "{BF7B896B-8BB6-447C-84F8-26871882A14A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thrift.Benchmarks", "Benchmarks\Thrift.Benchmarks\Thrift.Benchmarks.csproj", "{D0559DFF-6632-446C-9EFC-C750DA20B1D9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5B501D21-D428-408D-AB5C-32D6F5355294}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B501D21-D428-408D-AB5C-32D6F5355294}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B501D21-D428-408D-AB5C-32D6F5355294}.Debug|x64.ActiveCfg = Debug|Any CPU + {5B501D21-D428-408D-AB5C-32D6F5355294}.Debug|x64.Build.0 = Debug|Any CPU + {5B501D21-D428-408D-AB5C-32D6F5355294}.Debug|x86.ActiveCfg = Debug|Any CPU + {5B501D21-D428-408D-AB5C-32D6F5355294}.Debug|x86.Build.0 = Debug|Any CPU + {5B501D21-D428-408D-AB5C-32D6F5355294}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B501D21-D428-408D-AB5C-32D6F5355294}.Release|Any CPU.Build.0 = Release|Any CPU + {5B501D21-D428-408D-AB5C-32D6F5355294}.Release|x64.ActiveCfg = Release|Any CPU + {5B501D21-D428-408D-AB5C-32D6F5355294}.Release|x64.Build.0 = Release|Any CPU + {5B501D21-D428-408D-AB5C-32D6F5355294}.Release|x86.ActiveCfg = Release|Any CPU + {5B501D21-D428-408D-AB5C-32D6F5355294}.Release|x86.Build.0 = Release|Any CPU + {837F4084-AAD7-45F5-BC96-10E05A669DB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {837F4084-AAD7-45F5-BC96-10E05A669DB4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {837F4084-AAD7-45F5-BC96-10E05A669DB4}.Debug|x64.ActiveCfg = Debug|Any CPU + {837F4084-AAD7-45F5-BC96-10E05A669DB4}.Debug|x64.Build.0 = Debug|Any CPU + {837F4084-AAD7-45F5-BC96-10E05A669DB4}.Debug|x86.ActiveCfg = Debug|Any CPU + {837F4084-AAD7-45F5-BC96-10E05A669DB4}.Debug|x86.Build.0 = Debug|Any CPU + {837F4084-AAD7-45F5-BC96-10E05A669DB4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {837F4084-AAD7-45F5-BC96-10E05A669DB4}.Release|Any CPU.Build.0 = Release|Any CPU + {837F4084-AAD7-45F5-BC96-10E05A669DB4}.Release|x64.ActiveCfg = Release|Any CPU + {837F4084-AAD7-45F5-BC96-10E05A669DB4}.Release|x64.Build.0 = Release|Any CPU + {837F4084-AAD7-45F5-BC96-10E05A669DB4}.Release|x86.ActiveCfg = Release|Any CPU + {837F4084-AAD7-45F5-BC96-10E05A669DB4}.Release|x86.Build.0 = Release|Any CPU + {0790D388-1A3C-4423-8CF2-C97074A8B68B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0790D388-1A3C-4423-8CF2-C97074A8B68B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0790D388-1A3C-4423-8CF2-C97074A8B68B}.Debug|x64.ActiveCfg = Debug|Any CPU + {0790D388-1A3C-4423-8CF2-C97074A8B68B}.Debug|x64.Build.0 = Debug|Any CPU + {0790D388-1A3C-4423-8CF2-C97074A8B68B}.Debug|x86.ActiveCfg = Debug|Any CPU + {0790D388-1A3C-4423-8CF2-C97074A8B68B}.Debug|x86.Build.0 = Debug|Any CPU + {0790D388-1A3C-4423-8CF2-C97074A8B68B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0790D388-1A3C-4423-8CF2-C97074A8B68B}.Release|Any CPU.Build.0 = Release|Any CPU + {0790D388-1A3C-4423-8CF2-C97074A8B68B}.Release|x64.ActiveCfg = Release|Any CPU + {0790D388-1A3C-4423-8CF2-C97074A8B68B}.Release|x64.Build.0 = Release|Any CPU + {0790D388-1A3C-4423-8CF2-C97074A8B68B}.Release|x86.ActiveCfg = Release|Any CPU + {0790D388-1A3C-4423-8CF2-C97074A8B68B}.Release|x86.Build.0 = Release|Any CPU + {A6AE021D-61CB-4D84-A103-0B663C62AE2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A6AE021D-61CB-4D84-A103-0B663C62AE2C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A6AE021D-61CB-4D84-A103-0B663C62AE2C}.Debug|x64.ActiveCfg = Debug|Any CPU + {A6AE021D-61CB-4D84-A103-0B663C62AE2C}.Debug|x64.Build.0 = Debug|Any CPU + {A6AE021D-61CB-4D84-A103-0B663C62AE2C}.Debug|x86.ActiveCfg = Debug|Any CPU + {A6AE021D-61CB-4D84-A103-0B663C62AE2C}.Debug|x86.Build.0 = Debug|Any CPU + {A6AE021D-61CB-4D84-A103-0B663C62AE2C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A6AE021D-61CB-4D84-A103-0B663C62AE2C}.Release|Any CPU.Build.0 = Release|Any CPU + {A6AE021D-61CB-4D84-A103-0B663C62AE2C}.Release|x64.ActiveCfg = Release|Any CPU + {A6AE021D-61CB-4D84-A103-0B663C62AE2C}.Release|x64.Build.0 = Release|Any CPU + {A6AE021D-61CB-4D84-A103-0B663C62AE2C}.Release|x86.ActiveCfg = Release|Any CPU + {A6AE021D-61CB-4D84-A103-0B663C62AE2C}.Release|x86.Build.0 = Release|Any CPU + {D0559DFF-6632-446C-9EFC-C750DA20B1D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D0559DFF-6632-446C-9EFC-C750DA20B1D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D0559DFF-6632-446C-9EFC-C750DA20B1D9}.Debug|x64.ActiveCfg = Debug|Any CPU + {D0559DFF-6632-446C-9EFC-C750DA20B1D9}.Debug|x64.Build.0 = Debug|Any CPU + {D0559DFF-6632-446C-9EFC-C750DA20B1D9}.Debug|x86.ActiveCfg = Debug|Any CPU + {D0559DFF-6632-446C-9EFC-C750DA20B1D9}.Debug|x86.Build.0 = Debug|Any CPU + {D0559DFF-6632-446C-9EFC-C750DA20B1D9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D0559DFF-6632-446C-9EFC-C750DA20B1D9}.Release|Any CPU.Build.0 = Release|Any CPU + {D0559DFF-6632-446C-9EFC-C750DA20B1D9}.Release|x64.ActiveCfg = Release|Any CPU + {D0559DFF-6632-446C-9EFC-C750DA20B1D9}.Release|x64.Build.0 = Release|Any CPU + {D0559DFF-6632-446C-9EFC-C750DA20B1D9}.Release|x86.ActiveCfg = Release|Any CPU + {D0559DFF-6632-446C-9EFC-C750DA20B1D9}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {837F4084-AAD7-45F5-BC96-10E05A669DB4} = {ED5A45B0-07D1-4507-96B7-83FBD3D031CA} + {0790D388-1A3C-4423-8CF2-C97074A8B68B} = {ED5A45B0-07D1-4507-96B7-83FBD3D031CA} + {A6AE021D-61CB-4D84-A103-0B663C62AE2C} = {ED5A45B0-07D1-4507-96B7-83FBD3D031CA} + {D0559DFF-6632-446C-9EFC-C750DA20B1D9} = {BF7B896B-8BB6-447C-84F8-26871882A14A} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FD20BC4A-0109-41D8-8C0C-893E784D7EF9} + EndGlobalSection +EndGlobal diff --git a/lib/csharp/thrift_netstd/Thrift/.editorconfig b/lib/csharp/thrift_netstd/Thrift/.editorconfig new file mode 100644 index 000000000..54b698cf4 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] + +# CS1591: missing XML comment for public element +dotnet_diagnostic.CS1591.severity = silent diff --git a/lib/csharp/thrift_netstd/Thrift/Collections/TCollections.cs b/lib/csharp/thrift_netstd/Thrift/Collections/TCollections.cs new file mode 100644 index 000000000..21ee3bba7 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Collections/TCollections.cs @@ -0,0 +1,115 @@ +// 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. + +using System.Collections; +using System.Collections.Generic; + +namespace Thrift.Collections +{ + // ReSharper disable once InconsistentNaming + public static class TCollections + { + /// + /// This will return true if the two collections are value-wise the same. + /// If the collection contains a collection, the collections will be compared using this method. + /// + public static bool Equals(IEnumerable first, IEnumerable second) + { + if (first == null && second == null) + { + return true; + } + + if (first == null || second == null) + { + return false; + } + + // for dictionaries, we need to compare keys and values separately + // because KeyValuePair.Equals() will not do what we want + var fdict = first as IDictionary; + var sdict = second as IDictionary; + if ((fdict != null) || (sdict != null)) + { + if ((fdict == null) || (sdict == null)) + return false; + return TCollections.Equals(fdict.Keys, sdict.Keys) + && TCollections.Equals(fdict.Values, sdict.Values); + } + + var fiter = first.GetEnumerator(); + var siter = second.GetEnumerator(); + + var fnext = fiter.MoveNext(); + var snext = siter.MoveNext(); + + while (fnext && snext) + { + var fenum = fiter.Current as IEnumerable; + var senum = siter.Current as IEnumerable; + + if (fenum != null && senum != null) + { + if (!Equals(fenum, senum)) + { + return false; + } + } + else if (fenum == null ^ senum == null) + { + return false; + } + else if (!Equals(fiter.Current, siter.Current)) + { + return false; + } + + fnext = fiter.MoveNext(); + snext = siter.MoveNext(); + } + + return fnext == snext; + } + + /// + /// This returns a hashcode based on the value of the enumerable. + /// + public static int GetHashCode(IEnumerable enumerable) + { + if (enumerable == null) + { + return 0; + } + + var hashcode = 0; + + foreach (var obj in enumerable) + { + var objHash = (obj is IEnumerable enum2) ? GetHashCode(enum2) : obj.GetHashCode(); + + unchecked + { + hashcode = (hashcode * 397) ^ (objHash); + } + } + + return hashcode; + } + + + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Collections/THashSet.cs b/lib/csharp/thrift_netstd/Thrift/Collections/THashSet.cs new file mode 100644 index 000000000..fc2a507b2 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Collections/THashSet.cs @@ -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. + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Thrift.Collections +{ + // ReSharper disable once InconsistentNaming + [Obsolete("deprecated, use HashSet instead")] + public class THashSet : System.Collections.Generic.HashSet + { + public THashSet() + : base() + { + } + + public THashSet(int capacity) +#if NET5_0_OR_GREATER + : base(capacity) +#elif NETFRAMEWORK || NETSTANDARD + : base(/*capacity not supported*/) +#else +#error Unknown platform +#endif + { + } + + public THashSet(IEnumerable collection) + : base(collection) + { + } + + } +} + diff --git a/lib/csharp/thrift_netstd/Thrift/GlobalSuppressions.cs b/lib/csharp/thrift_netstd/Thrift/GlobalSuppressions.cs new file mode 100644 index 000000000..eb7d3d789 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/GlobalSuppressions.cs @@ -0,0 +1,31 @@ +// 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 used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +// suppress certain messages for compatibility reasons with older C# versions we want to support +[assembly: SuppressMessage("Style", "IDE0057", Justification = "compatibility", Scope = "module")] +[assembly: SuppressMessage("Style", "IDE0066", Justification = "compatibility", Scope = "module")] +[assembly: SuppressMessage("Style", "IDE0090", Justification = "compatibility", Scope = "module")] +[assembly: SuppressMessage("Style", "IDE0063", Justification = "compatibility", Scope = "module")] + diff --git a/lib/csharp/thrift_netstd/Thrift/Processor/ITAsyncProcessor.cs b/lib/csharp/thrift_netstd/Thrift/Processor/ITAsyncProcessor.cs new file mode 100644 index 000000000..0ef683d3c --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Processor/ITAsyncProcessor.cs @@ -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. + +using System.Threading; +using System.Threading.Tasks; +using Thrift.Protocol; + +namespace Thrift.Processor +{ + public interface ITAsyncProcessor + { + Task ProcessAsync(TProtocol iprot, TProtocol oprot, CancellationToken cancellationToken = default); + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Processor/ITProcessorFactory.cs b/lib/csharp/thrift_netstd/Thrift/Processor/ITProcessorFactory.cs new file mode 100644 index 000000000..e0fe3d0a8 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Processor/ITProcessorFactory.cs @@ -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. + +using Thrift.Server; +using Thrift.Transport; + +namespace Thrift.Processor +{ + // ReSharper disable once InconsistentNaming + public interface ITProcessorFactory + { + ITAsyncProcessor GetAsyncProcessor(TTransport trans, TServer baseServer = null); + } +} \ No newline at end of file diff --git a/lib/csharp/thrift_netstd/Thrift/Processor/TMultiplexedProcessor.cs b/lib/csharp/thrift_netstd/Thrift/Processor/TMultiplexedProcessor.cs new file mode 100644 index 000000000..9759058b3 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Processor/TMultiplexedProcessor.cs @@ -0,0 +1,135 @@ +// 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. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Thrift.Protocol; +using Thrift.Protocol.Entities; + +namespace Thrift.Processor +{ + // ReSharper disable once InconsistentNaming + public class TMultiplexedProcessor : ITAsyncProcessor + { + //TODO: Localization + + private readonly Dictionary _serviceProcessorMap = + new Dictionary(); + + public async Task ProcessAsync(TProtocol iprot, TProtocol oprot) + { + return await ProcessAsync(iprot, oprot, CancellationToken.None); + } + + public async Task ProcessAsync(TProtocol iprot, TProtocol oprot, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + try + { + var message = await iprot.ReadMessageBeginAsync(cancellationToken); + + if ((message.Type != TMessageType.Call) && (message.Type != TMessageType.Oneway)) + { + await FailAsync(oprot, message, TApplicationException.ExceptionType.InvalidMessageType, + "Message exType CALL or ONEWAY expected", cancellationToken); + return false; + } + + // Extract the service name + var index = message.Name.IndexOf(TMultiplexedProtocol.Separator, StringComparison.Ordinal); + if (index < 0) + { + await FailAsync(oprot, message, TApplicationException.ExceptionType.InvalidProtocol, + $"Service name not found in message name: {message.Name}. Did you forget to use a TMultiplexProtocol in your client?", + cancellationToken); + return false; + } + + // Create a new TMessage, something that can be consumed by any TProtocol + var serviceName = message.Name.Substring(0, index); + if (!_serviceProcessorMap.TryGetValue(serviceName, out ITAsyncProcessor actualProcessor)) + { + await FailAsync(oprot, message, TApplicationException.ExceptionType.InternalError, + $"Service name not found: {serviceName}. Did you forget to call RegisterProcessor()?", + cancellationToken); + return false; + } + + // Create a new TMessage, removing the service name + var newMessage = new TMessage( + message.Name.Substring(serviceName.Length + TMultiplexedProtocol.Separator.Length), + message.Type, + message.SeqID); + + // Dispatch processing to the stored processor + return + await + actualProcessor.ProcessAsync(new StoredMessageProtocol(iprot, newMessage), oprot, + cancellationToken); + } + catch (IOException) + { + return false; // similar to all other processors + } + } + + public void RegisterProcessor(string serviceName, ITAsyncProcessor processor) + { + if (_serviceProcessorMap.ContainsKey(serviceName)) + { + throw new InvalidOperationException( + $"Processor map already contains processor with name: '{serviceName}'"); + } + + _serviceProcessorMap.Add(serviceName, processor); + } + + private static async Task FailAsync(TProtocol oprot, TMessage message, TApplicationException.ExceptionType extype, + string etxt, CancellationToken cancellationToken) + { + var appex = new TApplicationException(extype, etxt); + + var newMessage = new TMessage(message.Name, TMessageType.Exception, message.SeqID); + + await oprot.WriteMessageBeginAsync(newMessage, cancellationToken); + await appex.WriteAsync(oprot, cancellationToken); + await oprot.WriteMessageEndAsync(cancellationToken); + await oprot.Transport.FlushAsync(cancellationToken); + } + + private class StoredMessageProtocol : TProtocolDecorator + { + readonly TMessage _msgBegin; + + public StoredMessageProtocol(TProtocol protocol, TMessage messageBegin) + : base(protocol) + { + _msgBegin = messageBegin; + } + + public override ValueTask ReadMessageBeginAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return new ValueTask(_msgBegin); + } + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Processor/TSingletonProcessorFactory.cs b/lib/csharp/thrift_netstd/Thrift/Processor/TSingletonProcessorFactory.cs new file mode 100644 index 000000000..97ecff65c --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Processor/TSingletonProcessorFactory.cs @@ -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. + +using Thrift.Server; +using Thrift.Transport; + +namespace Thrift.Processor +{ + // ReSharper disable once InconsistentNaming + public class TSingletonProcessorFactory : ITProcessorFactory + { + private readonly ITAsyncProcessor _asyncProcessor; + + public TSingletonProcessorFactory(ITAsyncProcessor asyncProcessor) + { + _asyncProcessor = asyncProcessor; + } + + public ITAsyncProcessor GetAsyncProcessor(TTransport trans, TServer baseServer = null) + { + return _asyncProcessor; + } + } +} \ No newline at end of file diff --git a/lib/csharp/thrift_netstd/Thrift/Properties/AssemblyInfo.cs b/lib/csharp/thrift_netstd/Thrift/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..5aa368f6e --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Properties/AssemblyInfo.cs @@ -0,0 +1,56 @@ +// 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. + +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. + +[assembly: AssemblyTitle("Thrift")] +[assembly: AssemblyDescription("C# .NET Core bindings for the Apache Thrift RPC system")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("The Apache Software Foundation")] +[assembly: AssemblyProduct("Thrift")] +[assembly: AssemblyCopyright("The Apache Software Foundation")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +//@TODO where to put License information? + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a exType in this assembly from +// COM, set the ComVisible attribute to true on that exType. + +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM + +[assembly: Guid("df3f8ef0-e0a3-4c86-a65b-8ec84e016b1d")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: + +[assembly: AssemblyVersion("0.17.0.0")] +[assembly: AssemblyFileVersion("0.17.0.0")] diff --git a/lib/csharp/thrift_netstd/Thrift/Protocol/Entities/TField.cs b/lib/csharp/thrift_netstd/Thrift/Protocol/Entities/TField.cs new file mode 100644 index 000000000..4e29bb5d4 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Protocol/Entities/TField.cs @@ -0,0 +1,37 @@ +// 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. + +namespace Thrift.Protocol.Entities +{ + // ReSharper disable once InconsistentNaming + public struct TField + { + public TField(string name, TType type, short id) + { + Name = name; + Type = type; + ID = id; + } + + public string Name { get; set; } + + public TType Type { get; set; } + + // ReSharper disable once InconsistentNaming - do not rename - it used for generation + public short ID { get; set; } + } +} \ No newline at end of file diff --git a/lib/csharp/thrift_netstd/Thrift/Protocol/Entities/TList.cs b/lib/csharp/thrift_netstd/Thrift/Protocol/Entities/TList.cs new file mode 100644 index 000000000..f59922564 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Protocol/Entities/TList.cs @@ -0,0 +1,33 @@ +// 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. + +namespace Thrift.Protocol.Entities +{ + // ReSharper disable once InconsistentNaming + public struct TList + { + public TList(TType elementType, int count) + { + ElementType = elementType; + Count = count; + } + + public TType ElementType { get; set; } + + public int Count { get; set; } + } +} \ No newline at end of file diff --git a/lib/csharp/thrift_netstd/Thrift/Protocol/Entities/TMap.cs b/lib/csharp/thrift_netstd/Thrift/Protocol/Entities/TMap.cs new file mode 100644 index 000000000..1efebe7a1 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Protocol/Entities/TMap.cs @@ -0,0 +1,36 @@ +// 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. + +namespace Thrift.Protocol.Entities +{ + // ReSharper disable once InconsistentNaming + public struct TMap + { + public TMap(TType keyType, TType valueType, int count) + { + KeyType = keyType; + ValueType = valueType; + Count = count; + } + + public TType KeyType { get; set; } + + public TType ValueType { get; set; } + + public int Count { get; set; } + } +} \ No newline at end of file diff --git a/lib/csharp/thrift_netstd/Thrift/Protocol/Entities/TMessage.cs b/lib/csharp/thrift_netstd/Thrift/Protocol/Entities/TMessage.cs new file mode 100644 index 000000000..08d741d65 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Protocol/Entities/TMessage.cs @@ -0,0 +1,37 @@ +// 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. + +namespace Thrift.Protocol.Entities +{ + // ReSharper disable once InconsistentNaming + public struct TMessage + { + public TMessage(string name, TMessageType type, int seqid) + { + Name = name; + Type = type; + SeqID = seqid; + } + + public string Name { get; set; } + + public TMessageType Type { get; set; } + + // ReSharper disable once InconsistentNaming - do not rename - it used for generation + public int SeqID { get; set; } + } +} \ No newline at end of file diff --git a/lib/csharp/thrift_netstd/Thrift/Protocol/Entities/TMessageType.cs b/lib/csharp/thrift_netstd/Thrift/Protocol/Entities/TMessageType.cs new file mode 100644 index 000000000..24d663e2d --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Protocol/Entities/TMessageType.cs @@ -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. + +namespace Thrift.Protocol.Entities +{ + // ReSharper disable once InconsistentNaming + public enum TMessageType + { + Call = 1, + Reply = 2, + Exception = 3, + Oneway = 4 + } +} \ No newline at end of file diff --git a/lib/csharp/thrift_netstd/Thrift/Protocol/Entities/TSet.cs b/lib/csharp/thrift_netstd/Thrift/Protocol/Entities/TSet.cs new file mode 100644 index 000000000..692d5642c --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Protocol/Entities/TSet.cs @@ -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. + +namespace Thrift.Protocol.Entities +{ + // ReSharper disable once InconsistentNaming + public struct TSet + { + public TSet(TType elementType, int count) + { + ElementType = elementType; + Count = count; + } + + public TSet(TList list) + : this(list.ElementType, list.Count) + { + } + + public TType ElementType { get; set; } + + public int Count { get; set; } + } +} \ No newline at end of file diff --git a/lib/csharp/thrift_netstd/Thrift/Protocol/Entities/TStruct.cs b/lib/csharp/thrift_netstd/Thrift/Protocol/Entities/TStruct.cs new file mode 100644 index 000000000..d87608799 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Protocol/Entities/TStruct.cs @@ -0,0 +1,30 @@ +// 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. + +namespace Thrift.Protocol.Entities +{ + // ReSharper disable once InconsistentNaming + public readonly struct TStruct + { + public TStruct(string name) + { + Name = name; + } + + public string Name { get; } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Protocol/Entities/TType.cs b/lib/csharp/thrift_netstd/Thrift/Protocol/Entities/TType.cs new file mode 100644 index 000000000..4e922a7e7 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Protocol/Entities/TType.cs @@ -0,0 +1,37 @@ +// 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. + +namespace Thrift.Protocol.Entities +{ + // ReSharper disable once InconsistentNaming + public enum TType : byte + { + Stop = 0, + Void = 1, + Bool = 2, + Byte = 3, + Double = 4, + I16 = 6, + I32 = 8, + I64 = 10, + String = 11, + Struct = 12, + Map = 13, + Set = 14, + List = 15 + } +} \ No newline at end of file diff --git a/lib/csharp/thrift_netstd/Thrift/Protocol/TBase.cs b/lib/csharp/thrift_netstd/Thrift/Protocol/TBase.cs new file mode 100644 index 000000000..09bb43f9c --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Protocol/TBase.cs @@ -0,0 +1,36 @@ +// 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. + +using System.Threading; +using System.Threading.Tasks; + +#pragma warning disable IDE1006 // some interfaces here are intentionally not I-prefixed + +namespace Thrift.Protocol +{ + public interface TUnionBase + { + Task WriteAsync(TProtocol tProtocol, CancellationToken cancellationToken = default); + } + + // ReSharper disable once InconsistentNaming + public interface TBase : TUnionBase + { + Task ReadAsync(TProtocol tProtocol, CancellationToken cancellationToken = default); + } + +} diff --git a/lib/csharp/thrift_netstd/Thrift/Protocol/TBinaryProtocol.cs b/lib/csharp/thrift_netstd/Thrift/Protocol/TBinaryProtocol.cs new file mode 100644 index 000000000..eee137ca1 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Protocol/TBinaryProtocol.cs @@ -0,0 +1,472 @@ +// 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. + +using System; +using System.Buffers.Binary; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Thrift.Protocol.Entities; +using Thrift.Transport; + + +namespace Thrift.Protocol +{ + // ReSharper disable once InconsistentNaming + public class TBinaryProtocol : TProtocol + { + protected const uint VersionMask = 0xffff0000; + protected const uint Version1 = 0x80010000; + + protected bool StrictRead; + protected bool StrictWrite; + + // minimize memory allocations by means of an preallocated bytes buffer + // The value of 128 is arbitrarily chosen, the required minimum size must be sizeof(long) + private readonly byte[] PreAllocatedBuffer = new byte[128]; + + public TBinaryProtocol(TTransport trans) + : this(trans, false, true) + { + } + + public TBinaryProtocol(TTransport trans, bool strictRead, bool strictWrite) + : base(trans) + { + StrictRead = strictRead; + StrictWrite = strictWrite; + } + + public override async Task WriteMessageBeginAsync(TMessage message, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (StrictWrite) + { + var version = Version1 | (uint) message.Type; + await WriteI32Async((int) version, cancellationToken); + await WriteStringAsync(message.Name, cancellationToken); + await WriteI32Async(message.SeqID, cancellationToken); + } + else + { + await WriteStringAsync(message.Name, cancellationToken); + await WriteByteAsync((sbyte) message.Type, cancellationToken); + await WriteI32Async(message.SeqID, cancellationToken); + } + } + + public override Task WriteMessageEndAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override Task WriteStructBeginAsync(TStruct @struct, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override Task WriteStructEndAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override async Task WriteFieldBeginAsync(TField field, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + await WriteByteAsync((sbyte) field.Type, cancellationToken); + await WriteI16Async(field.ID, cancellationToken); + } + + public override Task WriteFieldEndAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override async Task WriteFieldStopAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + await WriteByteAsync((sbyte) TType.Stop, cancellationToken); + } + + public override async Task WriteMapBeginAsync(TMap map, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + PreAllocatedBuffer[0] = (byte)map.KeyType; + PreAllocatedBuffer[1] = (byte)map.ValueType; + await Trans.WriteAsync(PreAllocatedBuffer, 0, 2, cancellationToken); + + await WriteI32Async(map.Count, cancellationToken); + } + + public override Task WriteMapEndAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override async Task WriteListBeginAsync(TList list, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + await WriteByteAsync((sbyte) list.ElementType, cancellationToken); + await WriteI32Async(list.Count, cancellationToken); + } + + public override Task WriteListEndAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override async Task WriteSetBeginAsync(TSet set, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + await WriteByteAsync((sbyte) set.ElementType, cancellationToken); + await WriteI32Async(set.Count, cancellationToken); + } + + public override Task WriteSetEndAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override async Task WriteBoolAsync(bool b, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + await WriteByteAsync(b ? (sbyte) 1 : (sbyte) 0, cancellationToken); + } + + public override async Task WriteByteAsync(sbyte b, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + PreAllocatedBuffer[0] = (byte)b; + + await Trans.WriteAsync(PreAllocatedBuffer, 0, 1, cancellationToken); + } + public override async Task WriteI16Async(short i16, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + BinaryPrimitives.WriteInt16BigEndian(PreAllocatedBuffer, i16); + + await Trans.WriteAsync(PreAllocatedBuffer, 0, 2, cancellationToken); + } + + public override async Task WriteI32Async(int i32, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + BinaryPrimitives.WriteInt32BigEndian(PreAllocatedBuffer, i32); + + await Trans.WriteAsync(PreAllocatedBuffer, 0, 4, cancellationToken); + } + + + public override async Task WriteI64Async(long i64, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + BinaryPrimitives.WriteInt64BigEndian(PreAllocatedBuffer, i64); + + await Trans.WriteAsync(PreAllocatedBuffer, 0, 8, cancellationToken); + } + + public override async Task WriteDoubleAsync(double d, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + await WriteI64Async(BitConverter.DoubleToInt64Bits(d), cancellationToken); + } + + + public override async Task WriteBinaryAsync(byte[] bytes, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + await WriteI32Async(bytes.Length, cancellationToken); + await Trans.WriteAsync(bytes, 0, bytes.Length, cancellationToken); + } + + public override async ValueTask ReadMessageBeginAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var message = new TMessage(); + var size = await ReadI32Async(cancellationToken); + if (size < 0) + { + var version = (uint) size & VersionMask; + if (version != Version1) + { + throw new TProtocolException(TProtocolException.BAD_VERSION, + $"Bad version in ReadMessageBegin: {version}"); + } + message.Type = (TMessageType) (size & 0x000000ff); + message.Name = await ReadStringAsync(cancellationToken); + message.SeqID = await ReadI32Async(cancellationToken); + } + else + { + if (StrictRead) + { + throw new TProtocolException(TProtocolException.BAD_VERSION, + "Missing version in ReadMessageBegin, old client?"); + } + message.Name = (size > 0) ? await ReadStringBodyAsync(size, cancellationToken) : string.Empty; + message.Type = (TMessageType) await ReadByteAsync(cancellationToken); + message.SeqID = await ReadI32Async(cancellationToken); + } + return message; + } + + public override Task ReadMessageEndAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override ValueTask ReadStructBeginAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return new ValueTask(AnonymousStruct); + } + + public override Task ReadStructEndAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override async ValueTask ReadFieldBeginAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var type = (TType)await ReadByteAsync(cancellationToken); + if (type == TType.Stop) + { + return StopField; + } + + return new TField { + Type = type, + ID = await ReadI16Async(cancellationToken) + }; + } + + public override Task ReadFieldEndAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override async ValueTask ReadMapBeginAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var map = new TMap + { + KeyType = (TType) await ReadByteAsync(cancellationToken), + ValueType = (TType) await ReadByteAsync(cancellationToken), + Count = await ReadI32Async(cancellationToken) + }; + CheckReadBytesAvailable(map); + return map; + } + + public override Task ReadMapEndAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override async ValueTask ReadListBeginAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var list = new TList + { + ElementType = (TType) await ReadByteAsync(cancellationToken), + Count = await ReadI32Async(cancellationToken) + }; + CheckReadBytesAvailable(list); + return list; + } + + public override Task ReadListEndAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override async ValueTask ReadSetBeginAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var set = new TSet + { + ElementType = (TType) await ReadByteAsync(cancellationToken), + Count = await ReadI32Async(cancellationToken) + }; + CheckReadBytesAvailable(set); + return set; + } + + public override Task ReadSetEndAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override async ValueTask ReadBoolAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + return await ReadByteAsync(cancellationToken) == 1; + } + + public override async ValueTask ReadByteAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + await Trans.ReadAllAsync(PreAllocatedBuffer, 0, 1, cancellationToken); + return (sbyte)PreAllocatedBuffer[0]; + } + + public override async ValueTask ReadI16Async(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + await Trans.ReadAllAsync(PreAllocatedBuffer, 0, 2, cancellationToken); + var result = BinaryPrimitives.ReadInt16BigEndian(PreAllocatedBuffer); + return result; + } + + public override async ValueTask ReadI32Async(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + await Trans.ReadAllAsync(PreAllocatedBuffer, 0, 4, cancellationToken); + + var result = BinaryPrimitives.ReadInt32BigEndian(PreAllocatedBuffer); + + return result; + } + + public override async ValueTask ReadI64Async(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + await Trans.ReadAllAsync(PreAllocatedBuffer, 0, 8, cancellationToken); + return BinaryPrimitives.ReadInt64BigEndian(PreAllocatedBuffer); + } + + public override async ValueTask ReadDoubleAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var d = await ReadI64Async(cancellationToken); + return BitConverter.Int64BitsToDouble(d); + } + + public override async ValueTask ReadBinaryAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var size = await ReadI32Async(cancellationToken); + Transport.CheckReadBytesAvailable(size); + var buf = new byte[size]; + await Trans.ReadAllAsync(buf, 0, size, cancellationToken); + return buf; + } + + public override async ValueTask ReadStringAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var size = await ReadI32Async(cancellationToken); + return size > 0 ? await ReadStringBodyAsync(size, cancellationToken) : string.Empty; + } + + private async ValueTask ReadStringBodyAsync(int size, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (size <= PreAllocatedBuffer.Length) + { + await Trans.ReadAllAsync(PreAllocatedBuffer, 0, size, cancellationToken); + return Encoding.UTF8.GetString(PreAllocatedBuffer, 0, size); + } + + Transport.CheckReadBytesAvailable(size); + var buf = new byte[size]; + await Trans.ReadAllAsync(buf, 0, size, cancellationToken); + return Encoding.UTF8.GetString(buf, 0, buf.Length); + } + + // Return the minimum number of bytes a type will consume on the wire + public override int GetMinSerializedSize(TType type) + { + switch (type) + { + case TType.Stop: return 0; + case TType.Void: return 0; + case TType.Bool: return sizeof(byte); + case TType.Byte: return sizeof(byte); + case TType.Double: return sizeof(double); + case TType.I16: return sizeof(short); + case TType.I32: return sizeof(int); + case TType.I64: return sizeof(long); + case TType.String: return sizeof(int); // string length + case TType.Struct: return 0; // empty struct + case TType.Map: return sizeof(int); // element count + case TType.Set: return sizeof(int); // element count + case TType.List: return sizeof(int); // element count + default: throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, "unrecognized type code"); + } + } + + public class Factory : TProtocolFactory + { + protected bool StrictRead; + protected bool StrictWrite; + + public Factory() + : this(false, true) + { + } + + public Factory(bool strictRead, bool strictWrite) + { + StrictRead = strictRead; + StrictWrite = strictWrite; + } + + public override TProtocol GetProtocol(TTransport trans) + { + return new TBinaryProtocol(trans, StrictRead, StrictWrite); + } + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Protocol/TCompactProtocol.cs b/lib/csharp/thrift_netstd/Thrift/Protocol/TCompactProtocol.cs new file mode 100644 index 000000000..6893ad476 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Protocol/TCompactProtocol.cs @@ -0,0 +1,840 @@ +// 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. + +using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Thrift.Protocol.Entities; +using Thrift.Transport; + + +namespace Thrift.Protocol +{ + + // ReSharper disable once InconsistentNaming + public class TCompactProtocol : TProtocol + { + private const byte ProtocolId = 0x82; + private const byte Version = 1; + private const byte VersionMask = 0x1f; // 0001 1111 + private const byte TypeMask = 0xE0; // 1110 0000 + private const byte TypeBits = 0x07; // 0000 0111 + private const int TypeShiftAmount = 5; + + private const byte NoTypeOverride = 0xFF; + + // ReSharper disable once InconsistentNaming + private static readonly byte[] TTypeToCompactType = new byte[16]; + private static readonly TType[] CompactTypeToTType = new TType[13]; + + /// + /// Used to keep track of the last field for the current and previous structs, so we can do the delta stuff. + /// + private readonly Stack _lastField = new Stack(15); + + /// + /// If we encounter a boolean field begin, save the TField here so it can have the value incorporated. + /// + private TField? _booleanField; + + /// + /// If we Read a field header, and it's a boolean field, save the boolean value here so that ReadBool can use it. + /// + private bool? _boolValue; + + private short _lastFieldId; + + // minimize memory allocations by means of an preallocated bytes buffer + // The value of 128 is arbitrarily chosen, the required minimum size must be sizeof(long) + private readonly byte[] PreAllocatedBuffer = new byte[128]; + + private struct VarInt + { + public byte[] bytes; + public int count; + } + + // minimize memory allocations by means of an preallocated VarInt buffer + private VarInt PreAllocatedVarInt = new VarInt() + { + bytes = new byte[10], // see Int64ToVarInt() + count = 0 + }; + + + + + public TCompactProtocol(TTransport trans) + : base(trans) + { + TTypeToCompactType[(int) TType.Stop] = Types.Stop; + TTypeToCompactType[(int) TType.Bool] = Types.BooleanTrue; + TTypeToCompactType[(int) TType.Byte] = Types.Byte; + TTypeToCompactType[(int) TType.I16] = Types.I16; + TTypeToCompactType[(int) TType.I32] = Types.I32; + TTypeToCompactType[(int) TType.I64] = Types.I64; + TTypeToCompactType[(int) TType.Double] = Types.Double; + TTypeToCompactType[(int) TType.String] = Types.Binary; + TTypeToCompactType[(int) TType.List] = Types.List; + TTypeToCompactType[(int) TType.Set] = Types.Set; + TTypeToCompactType[(int) TType.Map] = Types.Map; + TTypeToCompactType[(int) TType.Struct] = Types.Struct; + + CompactTypeToTType[Types.Stop] = TType.Stop; + CompactTypeToTType[Types.BooleanTrue] = TType.Bool; + CompactTypeToTType[Types.BooleanFalse] = TType.Bool; + CompactTypeToTType[Types.Byte] = TType.Byte; + CompactTypeToTType[Types.I16] = TType.I16; + CompactTypeToTType[Types.I32] = TType.I32; + CompactTypeToTType[Types.I64] = TType.I64; + CompactTypeToTType[Types.Double] = TType.Double; + CompactTypeToTType[Types.Binary] = TType.String; + CompactTypeToTType[Types.List] = TType.List; + CompactTypeToTType[Types.Set] = TType.Set; + CompactTypeToTType[Types.Map] = TType.Map; + CompactTypeToTType[Types.Struct] = TType.Struct; + } + + public void Reset() + { + _lastField.Clear(); + _lastFieldId = 0; + } + + public override async Task WriteMessageBeginAsync(TMessage message, CancellationToken cancellationToken) + { + PreAllocatedBuffer[0] = ProtocolId; + PreAllocatedBuffer[1] = (byte)((Version & VersionMask) | (((uint)message.Type << TypeShiftAmount) & TypeMask)); + await Trans.WriteAsync(PreAllocatedBuffer, 0, 2, cancellationToken); + + Int32ToVarInt((uint) message.SeqID, ref PreAllocatedVarInt); + await Trans.WriteAsync(PreAllocatedVarInt.bytes, 0, PreAllocatedVarInt.count, cancellationToken); + + await WriteStringAsync(message.Name, cancellationToken); + } + + public override Task WriteMessageEndAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + /// + /// Write a struct begin. This doesn't actually put anything on the wire. We + /// use it as an opportunity to put special placeholder markers on the field + /// stack so we can get the field id deltas correct. + /// + public override Task WriteStructBeginAsync(TStruct @struct, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _lastField.Push(_lastFieldId); + _lastFieldId = 0; + + return Task.CompletedTask; + } + + public override Task WriteStructEndAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _lastFieldId = _lastField.Pop(); + + return Task.CompletedTask; + } + + private async Task WriteFieldBeginInternalAsync(TField field, byte fieldType, CancellationToken cancellationToken) + { + // if there's a exType override passed in, use that. Otherwise ask GetCompactType(). + if (fieldType == NoTypeOverride) + fieldType = GetCompactType(field.Type); + + + // check if we can use delta encoding for the field id + if (field.ID > _lastFieldId) + { + var delta = field.ID - _lastFieldId; + if (delta <= 15) + { + // Write them together + PreAllocatedBuffer[0] = (byte)((delta << 4) | fieldType); + await Trans.WriteAsync(PreAllocatedBuffer, 0, 1, cancellationToken); + _lastFieldId = field.ID; + return; + } + } + + // Write them separate + PreAllocatedBuffer[0] = fieldType; + await Trans.WriteAsync(PreAllocatedBuffer, 0, 1, cancellationToken); + await WriteI16Async(field.ID, cancellationToken); + _lastFieldId = field.ID; + } + + public override async Task WriteFieldBeginAsync(TField field, CancellationToken cancellationToken) + { + if (field.Type == TType.Bool) + { + _booleanField = field; + } + else + { + await WriteFieldBeginInternalAsync(field, NoTypeOverride, cancellationToken); + } + } + + public override Task WriteFieldEndAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override async Task WriteFieldStopAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + PreAllocatedBuffer[0] = Types.Stop; + await Trans.WriteAsync(PreAllocatedBuffer, 0, 1, cancellationToken); + } + + protected async Task WriteCollectionBeginAsync(TType elemType, int size, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + /* + Abstract method for writing the start of lists and sets. List and sets on + the wire differ only by the exType indicator. + */ + + if (size <= 14) + { + PreAllocatedBuffer[0] = (byte)((size << 4) | GetCompactType(elemType)); + await Trans.WriteAsync(PreAllocatedBuffer, 0, 1, cancellationToken); + } + else + { + PreAllocatedBuffer[0] = (byte)(0xf0 | GetCompactType(elemType)); + await Trans.WriteAsync(PreAllocatedBuffer, 0, 1, cancellationToken); + + Int32ToVarInt((uint) size, ref PreAllocatedVarInt); + await Trans.WriteAsync(PreAllocatedVarInt.bytes, 0, PreAllocatedVarInt.count, cancellationToken); + } + } + + public override async Task WriteListBeginAsync(TList list, CancellationToken cancellationToken) + { + await WriteCollectionBeginAsync(list.ElementType, list.Count, cancellationToken); + } + + public override Task WriteListEndAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override async Task WriteSetBeginAsync(TSet set, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + await WriteCollectionBeginAsync(set.ElementType, set.Count, cancellationToken); + } + + public override Task WriteSetEndAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override async Task WriteBoolAsync(bool b, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + /* + Write a boolean value. Potentially, this could be a boolean field, in + which case the field header info isn't written yet. If so, decide what the + right exType header is for the value and then Write the field header. + Otherwise, Write a single byte. + */ + + if (_booleanField != null) + { + // we haven't written the field header yet + var type = b ? Types.BooleanTrue : Types.BooleanFalse; + await WriteFieldBeginInternalAsync(_booleanField.Value, type, cancellationToken); + _booleanField = null; + } + else + { + // we're not part of a field, so just write the value. + PreAllocatedBuffer[0] = b ? Types.BooleanTrue : Types.BooleanFalse; + await Trans.WriteAsync(PreAllocatedBuffer, 0, 1, cancellationToken); + } + } + + public override async Task WriteByteAsync(sbyte b, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + PreAllocatedBuffer[0] = (byte)b; + await Trans.WriteAsync(PreAllocatedBuffer, 0, 1, cancellationToken); + } + + public override async Task WriteI16Async(short i16, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + Int32ToVarInt(IntToZigzag(i16), ref PreAllocatedVarInt); + await Trans.WriteAsync(PreAllocatedVarInt.bytes, 0, PreAllocatedVarInt.count, cancellationToken); + } + + private static void Int32ToVarInt(uint n, ref VarInt varint) + { + // Write an i32 as a varint. Results in 1 - 5 bytes on the wire. + varint.count = 0; + Debug.Assert(varint.bytes.Length >= 5); + + while (true) + { + if ((n & ~0x7F) == 0) + { + varint.bytes[varint.count++] = (byte)n; + break; + } + + varint.bytes[varint.count++] = (byte)((n & 0x7F) | 0x80); + n >>= 7; + } + } + + public override async Task WriteI32Async(int i32, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + Int32ToVarInt(IntToZigzag(i32), ref PreAllocatedVarInt); + await Trans.WriteAsync(PreAllocatedVarInt.bytes, 0, PreAllocatedVarInt.count, cancellationToken); + } + + static private void Int64ToVarInt(ulong n, ref VarInt varint) + { + // Write an i64 as a varint. Results in 1-10 bytes on the wire. + varint.count = 0; + Debug.Assert(varint.bytes.Length >= 10); + + while (true) + { + if ((n & ~(ulong)0x7FL) == 0) + { + varint.bytes[varint.count++] = (byte)n; + break; + } + varint.bytes[varint.count++] = (byte)((n & 0x7F) | 0x80); + n >>= 7; + } + } + + public override async Task WriteI64Async(long i64, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + Int64ToVarInt(LongToZigzag(i64), ref PreAllocatedVarInt); + await Trans.WriteAsync(PreAllocatedVarInt.bytes, 0, PreAllocatedVarInt.count, cancellationToken); + } + + public override async Task WriteDoubleAsync(double d, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + BinaryPrimitives.WriteInt64LittleEndian(PreAllocatedBuffer, BitConverter.DoubleToInt64Bits(d)); + await Trans.WriteAsync(PreAllocatedBuffer, 0, 8, cancellationToken); + } + + public override async Task WriteStringAsync(string str, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var buf = ArrayPool.Shared.Rent(Encoding.UTF8.GetByteCount(str)); + try + { + var numberOfBytes = Encoding.UTF8.GetBytes(str, 0, str.Length, buf, 0); + + Int32ToVarInt((uint)numberOfBytes, ref PreAllocatedVarInt); + await Trans.WriteAsync(PreAllocatedVarInt.bytes, 0, PreAllocatedVarInt.count, cancellationToken); + await Trans.WriteAsync(buf, 0, numberOfBytes, cancellationToken); + } + finally + { + ArrayPool.Shared.Return(buf); + } + } + + public override async Task WriteBinaryAsync(byte[] bytes, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + Int32ToVarInt((uint) bytes.Length, ref PreAllocatedVarInt); + await Trans.WriteAsync(PreAllocatedVarInt.bytes, 0, PreAllocatedVarInt.count, cancellationToken); + await Trans.WriteAsync(bytes, 0, bytes.Length, cancellationToken); + } + + public override async Task WriteMapBeginAsync(TMap map, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (map.Count == 0) + { + PreAllocatedBuffer[0] = 0; + await Trans.WriteAsync( PreAllocatedBuffer, 0, 1, cancellationToken); + } + else + { + Int32ToVarInt((uint) map.Count, ref PreAllocatedVarInt); + await Trans.WriteAsync(PreAllocatedVarInt.bytes, 0, PreAllocatedVarInt.count, cancellationToken); + + PreAllocatedBuffer[0] = (byte)((GetCompactType(map.KeyType) << 4) | GetCompactType(map.ValueType)); + await Trans.WriteAsync(PreAllocatedBuffer, 0, 1, cancellationToken); + } + } + + public override Task WriteMapEndAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override async ValueTask ReadMessageBeginAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var protocolId = (byte) await ReadByteAsync(cancellationToken); + if (protocolId != ProtocolId) + { + throw new TProtocolException($"Expected protocol id {ProtocolId:X} but got {protocolId:X}"); + } + + var versionAndType = (byte) await ReadByteAsync(cancellationToken); + var version = (byte) (versionAndType & VersionMask); + + if (version != Version) + { + throw new TProtocolException($"Expected version {Version} but got {version}"); + } + + var type = (byte) ((versionAndType >> TypeShiftAmount) & TypeBits); + var seqid = (int) await ReadVarInt32Async(cancellationToken); + var messageName = await ReadStringAsync(cancellationToken); + + return new TMessage(messageName, (TMessageType) type, seqid); + } + + public override Task ReadMessageEndAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override ValueTask ReadStructBeginAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _lastField.Push(_lastFieldId); + _lastFieldId = 0; + + return new ValueTask(AnonymousStruct); + } + + public override Task ReadStructEndAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + /* + Doesn't actually consume any wire data, just removes the last field for + this struct from the field stack. + */ + + // consume the last field we Read off the wire. + _lastFieldId = _lastField.Pop(); + + return Task.CompletedTask; + } + + public override async ValueTask ReadFieldBeginAsync(CancellationToken cancellationToken) + { + // Read a field header off the wire. + var type = (byte) await ReadByteAsync(cancellationToken); + + // if it's a stop, then we can return immediately, as the struct is over. + if (type == Types.Stop) + { + return StopField; + } + + + // mask off the 4 MSB of the exType header. it could contain a field id delta. + var modifier = (short) ((type & 0xf0) >> 4); + var compactType = (byte)(type & 0x0f); + + short fieldId; + if (modifier == 0) + { + fieldId = await ReadI16Async(cancellationToken); + } + else + { + fieldId = (short) (_lastFieldId + modifier); + } + + var ttype = GetTType(compactType); + var field = new TField(string.Empty, ttype, fieldId); + + // if this happens to be a boolean field, the value is encoded in the exType + if( ttype == TType.Bool) + { + _boolValue = (compactType == Types.BooleanTrue); + } + + // push the new field onto the field stack so we can keep the deltas going. + _lastFieldId = field.ID; + return field; + } + + public override Task ReadFieldEndAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override async ValueTask ReadMapBeginAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + /* + Read a map header off the wire. If the size is zero, skip Reading the key + and value exType. This means that 0-length maps will yield TMaps without the + "correct" types. + */ + + var size = (int) await ReadVarInt32Async(cancellationToken); + var keyAndValueType = size == 0 ? (byte) 0 : (byte) await ReadByteAsync(cancellationToken); + var map = new TMap(GetTType((byte) (keyAndValueType >> 4)), GetTType((byte) (keyAndValueType & 0xf)), size); + CheckReadBytesAvailable(map); + return map; + } + + public override Task ReadMapEndAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override async ValueTask ReadSetBeginAsync(CancellationToken cancellationToken) + { + /* + Read a set header off the wire. If the set size is 0-14, the size will + be packed into the element exType header. If it's a longer set, the 4 MSB + of the element exType header will be 0xF, and a varint will follow with the + true size. + */ + + return new TSet(await ReadListBeginAsync(cancellationToken)); + } + + public override ValueTask ReadBoolAsync(CancellationToken cancellationToken) + { + /* + Read a boolean off the wire. If this is a boolean field, the value should + already have been Read during ReadFieldBegin, so we'll just consume the + pre-stored value. Otherwise, Read a byte. + */ + + if (_boolValue != null) + { + var result = _boolValue.Value; + _boolValue = null; + return new ValueTask(result); + } + + return InternalCall(); + + async ValueTask InternalCall() + { + var data = await ReadByteAsync(cancellationToken); + return (data == Types.BooleanTrue); + } + } + + + public override async ValueTask ReadByteAsync(CancellationToken cancellationToken) + { + // Read a single byte off the wire. Nothing interesting here. + await Trans.ReadAllAsync(PreAllocatedBuffer, 0, 1, cancellationToken); + return (sbyte)PreAllocatedBuffer[0]; + } + + public override async ValueTask ReadI16Async(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + return (short) ZigzagToInt(await ReadVarInt32Async(cancellationToken)); + } + + public override async ValueTask ReadI32Async(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + return ZigzagToInt(await ReadVarInt32Async(cancellationToken)); + } + + public override async ValueTask ReadI64Async(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + return ZigzagToLong(await ReadVarInt64Async(cancellationToken)); + } + + public override async ValueTask ReadDoubleAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + await Trans.ReadAllAsync(PreAllocatedBuffer, 0, 8, cancellationToken); + + return BitConverter.Int64BitsToDouble(BinaryPrimitives.ReadInt64LittleEndian(PreAllocatedBuffer)); + } + + public override async ValueTask ReadStringAsync(CancellationToken cancellationToken) + { + // read length + var length = (int) await ReadVarInt32Async(cancellationToken); + if (length == 0) + { + return string.Empty; + } + + // read and decode data + if (length < PreAllocatedBuffer.Length) + { + await Trans.ReadAllAsync(PreAllocatedBuffer, 0, length, cancellationToken); + return Encoding.UTF8.GetString(PreAllocatedBuffer, 0, length); + } + + Transport.CheckReadBytesAvailable(length); + + var buf = ArrayPool.Shared.Rent(length); + try + { + await Trans.ReadAllAsync(buf, 0, length, cancellationToken); + return Encoding.UTF8.GetString(buf, 0, length); + } + finally + { + ArrayPool.Shared.Return(buf); + } + } + + public override async ValueTask ReadBinaryAsync(CancellationToken cancellationToken) + { + // read length + var length = (int) await ReadVarInt32Async(cancellationToken); + if (length == 0) + { + return Array.Empty(); + } + + // read data + Transport.CheckReadBytesAvailable(length); + var buf = new byte[length]; + await Trans.ReadAllAsync(buf, 0, length, cancellationToken); + return buf; + } + + public override async ValueTask ReadListBeginAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + /* + Read a list header off the wire. If the list size is 0-14, the size will + be packed into the element exType header. If it's a longer list, the 4 MSB + of the element exType header will be 0xF, and a varint will follow with the + true size. + */ + + var sizeAndType = (byte) await ReadByteAsync(cancellationToken); + var size = (sizeAndType >> 4) & 0x0f; + if (size == 15) + { + size = (int) await ReadVarInt32Async(cancellationToken); + } + + var type = GetTType(sizeAndType); + var list = new TList(type, size); + CheckReadBytesAvailable(list); + return list; + } + + public override Task ReadListEndAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override Task ReadSetEndAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + private static byte GetCompactType(TType ttype) + { + // Given a TType value, find the appropriate TCompactProtocol.Types constant. + return TTypeToCompactType[(int) ttype]; + } + + + private async ValueTask ReadVarInt32Async(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + /* + Read an i32 from the wire as a varint. The MSB of each byte is set + if there is another byte to follow. This can Read up to 5 bytes. + */ + + uint result = 0; + var shift = 0; + + while (true) + { + var b = (byte) await ReadByteAsync(cancellationToken); + result |= (uint) (b & 0x7f) << shift; + if ((b & 0x80) != 0x80) + { + break; + } + shift += 7; + } + + return result; + } + + private async ValueTask ReadVarInt64Async(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + /* + Read an i64 from the wire as a proper varint. The MSB of each byte is set + if there is another byte to follow. This can Read up to 10 bytes. + */ + + var shift = 0; + ulong result = 0; + while (true) + { + var b = (byte) await ReadByteAsync(cancellationToken); + result |= (ulong) (b & 0x7f) << shift; + if ((b & 0x80) != 0x80) + { + break; + } + shift += 7; + } + + return result; + } + + private static int ZigzagToInt(uint n) + { + return (int) (n >> 1) ^ -(int) (n & 1); + } + + private static long ZigzagToLong(ulong n) + { + return (long) (n >> 1) ^ -(long) (n & 1); + } + + private static TType GetTType(byte type) + { + // Given a TCompactProtocol.Types constant, convert it to its corresponding TType value. + return CompactTypeToTType[type & 0x0f]; + } + + private static ulong LongToZigzag(long n) + { + // Convert l into a zigzag long. This allows negative numbers to be represented compactly as a varint + return (ulong) (n << 1) ^ (ulong) (n >> 63); + } + + private static uint IntToZigzag(int n) + { + // Convert n into a zigzag int. This allows negative numbers to be represented compactly as a varint + return (uint) (n << 1) ^ (uint) (n >> 31); + } + + // Return the minimum number of bytes a type will consume on the wire + public override int GetMinSerializedSize(TType type) + { + switch (type) + { + case TType.Stop: return 0; + case TType.Void: return 0; + case TType.Bool: return sizeof(byte); + case TType.Double: return 8; // uses fixedLongToBytes() which always writes 8 bytes + case TType.Byte: return sizeof(byte); + case TType.I16: return sizeof(byte); // zigzag + case TType.I32: return sizeof(byte); // zigzag + case TType.I64: return sizeof(byte); // zigzag + case TType.String: return sizeof(byte); // string length + case TType.Struct: return 0; // empty struct + case TType.Map: return sizeof(byte); // element count + case TType.Set: return sizeof(byte); // element count + case TType.List: return sizeof(byte); // element count + default: throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, "unrecognized type code"); + } + } + + public class Factory : TProtocolFactory + { + public override TProtocol GetProtocol(TTransport trans) + { + return new TCompactProtocol(trans); + } + } + + /// + /// All of the on-wire exType codes. + /// + private static class Types + { + public const byte Stop = 0x00; + public const byte BooleanTrue = 0x01; + public const byte BooleanFalse = 0x02; + public const byte Byte = 0x03; + public const byte I16 = 0x04; + public const byte I32 = 0x05; + public const byte I64 = 0x06; + public const byte Double = 0x07; + public const byte Binary = 0x08; + public const byte List = 0x09; + public const byte Set = 0x0A; + public const byte Map = 0x0B; + public const byte Struct = 0x0C; + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Protocol/TJSONProtocol.cs b/lib/csharp/thrift_netstd/Thrift/Protocol/TJSONProtocol.cs new file mode 100644 index 000000000..8799026ba --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Protocol/TJSONProtocol.cs @@ -0,0 +1,1028 @@ +// 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. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Thrift.Protocol.Entities; +using Thrift.Protocol.Utilities; +using Thrift.Transport; + + +namespace Thrift.Protocol +{ + /// + /// JSON protocol implementation for thrift. + /// This is a full-featured protocol supporting Write and Read. + /// Please see the C++ class header for a detailed description of the + /// protocol's wire format. + /// Adapted from the Java version. + /// + // ReSharper disable once InconsistentNaming + public class TJsonProtocol : TProtocol + { + private const long Version = 1; + + // Temporary buffer used by several methods + private readonly byte[] _tempBuffer = new byte[4]; + + // Current context that we are in + protected JSONBaseContext Context; + + // Stack of nested contexts that we may be in + protected Stack ContextStack = new Stack(); + + // Reader that manages a 1-byte buffer + protected LookaheadReader Reader; + + // Default encoding + protected Encoding Utf8Encoding = Encoding.UTF8; + + /// + /// TJsonProtocol Constructor + /// + public TJsonProtocol(TTransport trans) + : base(trans) + { + Context = new JSONBaseContext(this); + Reader = new LookaheadReader(this); + } + + /// + /// Push a new JSON context onto the stack. + /// + protected void PushContext(JSONBaseContext c) + { + ContextStack.Push(Context); + Context = c; + } + + /// + /// Pop the last JSON context off the stack + /// + protected void PopContext() + { + Context = ContextStack.Pop(); + } + + /// + /// Resets the context stack to pristine state. Allows for reusal of the protocol + /// even in cases where the protocol instance was in an undefined state due to + /// dangling/stale/obsolete contexts + /// + private void ResetContext() + { + ContextStack.Clear(); + Context = new JSONBaseContext(this); + } + /// + /// Read a byte that must match b[0]; otherwise an exception is thrown. + /// Marked protected to avoid synthetic accessor in JSONListContext.Read + /// and JSONPairContext.Read + /// + protected async Task ReadJsonSyntaxCharAsync(byte[] bytes, CancellationToken cancellationToken) + { + var ch = await Reader.ReadAsync(cancellationToken); + if (ch != bytes[0]) + { + throw new TProtocolException(TProtocolException.INVALID_DATA, $"Unexpected character: {(char) ch}"); + } + } + + /// + /// Write the bytes in array buf as a JSON characters, escaping as needed + /// + private async Task WriteJsonStringAsync(byte[] bytes, CancellationToken cancellationToken) + { + await Context.WriteConditionalDelimiterAsync(cancellationToken); + await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken); + + var len = bytes.Length; + for (var i = 0; i < len; i++) + { + if ((bytes[i] & 0x00FF) >= 0x30) + { + if (bytes[i] == TJSONProtocolConstants.Backslash[0]) + { + await Trans.WriteAsync(TJSONProtocolConstants.Backslash, cancellationToken); + await Trans.WriteAsync(TJSONProtocolConstants.Backslash, cancellationToken); + } + else + { + await Trans.WriteAsync(bytes, i, 1, cancellationToken); + } + } + else + { + _tempBuffer[0] = TJSONProtocolConstants.JsonCharTable[bytes[i]]; + if (_tempBuffer[0] == 1) + { + await Trans.WriteAsync(bytes, i, 1, cancellationToken); + } + else if (_tempBuffer[0] > 1) + { + await Trans.WriteAsync(TJSONProtocolConstants.Backslash, cancellationToken); + await Trans.WriteAsync(_tempBuffer, 0, 1, cancellationToken); + } + else + { + await Trans.WriteAsync(TJSONProtocolConstants.EscSequences, cancellationToken); + _tempBuffer[0] = TJSONProtocolHelper.ToHexChar((byte) (bytes[i] >> 4)); + _tempBuffer[1] = TJSONProtocolHelper.ToHexChar(bytes[i]); + await Trans.WriteAsync(_tempBuffer, 0, 2, cancellationToken); + } + } + } + await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken); + } + + /// + /// Write out number as a JSON value. If the context dictates so, it will be + /// wrapped in quotes to output as a JSON string. + /// + private async Task WriteJsonIntegerAsync(long num, CancellationToken cancellationToken) + { + await Context.WriteConditionalDelimiterAsync(cancellationToken); + var str = num.ToString(); + + var escapeNum = Context.EscapeNumbers(); + if (escapeNum) + { + await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken); + } + + var bytes = Utf8Encoding.GetBytes(str); + await Trans.WriteAsync(bytes, cancellationToken); + + if (escapeNum) + { + await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken); + } + } + + /// + /// Write out a double as a JSON value. If it is NaN or infinity or if the + /// context dictates escaping, Write out as JSON string. + /// + private async Task WriteJsonDoubleAsync(double num, CancellationToken cancellationToken) + { + await Context.WriteConditionalDelimiterAsync(cancellationToken); + var str = num.ToString("G17", CultureInfo.InvariantCulture); + var special = false; + + switch (str[0]) + { + case 'N': // NaN + case 'I': // Infinity + special = true; + break; + case '-': + if (str[1] == 'I') + { + // -Infinity + special = true; + } + break; + } + + var escapeNum = special || Context.EscapeNumbers(); + + if (escapeNum) + { + await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken); + } + + await Trans.WriteAsync(Utf8Encoding.GetBytes(str), cancellationToken); + + if (escapeNum) + { + await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken); + } + } + + /// + /// Write out contents of byte array b as a JSON string with base-64 encoded + /// data + /// + private async Task WriteJsonBase64Async(byte[] bytes, CancellationToken cancellationToken) + { + await Context.WriteConditionalDelimiterAsync(cancellationToken); + await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken); + + var len = bytes.Length; + var off = 0; + + while (len >= 3) + { + // Encode 3 bytes at a time + TBase64Utils.Encode(bytes, off, 3, _tempBuffer, 0); + await Trans.WriteAsync(_tempBuffer, 0, 4, cancellationToken); + off += 3; + len -= 3; + } + + if (len > 0) + { + // Encode remainder + TBase64Utils.Encode(bytes, off, len, _tempBuffer, 0); + await Trans.WriteAsync(_tempBuffer, 0, len + 1, cancellationToken); + } + + await Trans.WriteAsync(TJSONProtocolConstants.Quote, cancellationToken); + } + + private async Task WriteJsonObjectStartAsync(CancellationToken cancellationToken) + { + await Context.WriteConditionalDelimiterAsync(cancellationToken); + await Trans.WriteAsync(TJSONProtocolConstants.LeftBrace, cancellationToken); + PushContext(new JSONPairContext(this)); + } + + private async Task WriteJsonObjectEndAsync(CancellationToken cancellationToken) + { + PopContext(); + await Trans.WriteAsync(TJSONProtocolConstants.RightBrace, cancellationToken); + } + + private async Task WriteJsonArrayStartAsync(CancellationToken cancellationToken) + { + await Context.WriteConditionalDelimiterAsync(cancellationToken); + await Trans.WriteAsync(TJSONProtocolConstants.LeftBracket, cancellationToken); + PushContext(new JSONListContext(this)); + } + + private async Task WriteJsonArrayEndAsync(CancellationToken cancellationToken) + { + PopContext(); + await Trans.WriteAsync(TJSONProtocolConstants.RightBracket, cancellationToken); + } + + public override async Task WriteMessageBeginAsync(TMessage message, CancellationToken cancellationToken) + { + ResetContext(); + await WriteJsonArrayStartAsync(cancellationToken); + await WriteJsonIntegerAsync(Version, cancellationToken); + + var b = Utf8Encoding.GetBytes(message.Name); + await WriteJsonStringAsync(b, cancellationToken); + + await WriteJsonIntegerAsync((long) message.Type, cancellationToken); + await WriteJsonIntegerAsync(message.SeqID, cancellationToken); + } + + public override async Task WriteMessageEndAsync(CancellationToken cancellationToken) + { + await WriteJsonArrayEndAsync(cancellationToken); + } + + public override async Task WriteStructBeginAsync(TStruct @struct, CancellationToken cancellationToken) + { + await WriteJsonObjectStartAsync(cancellationToken); + } + + public override async Task WriteStructEndAsync(CancellationToken cancellationToken) + { + await WriteJsonObjectEndAsync(cancellationToken); + } + + public override async Task WriteFieldBeginAsync(TField field, CancellationToken cancellationToken) + { + await WriteJsonIntegerAsync(field.ID, cancellationToken); + await WriteJsonObjectStartAsync(cancellationToken); + await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(field.Type), cancellationToken); + } + + public override async Task WriteFieldEndAsync(CancellationToken cancellationToken) + { + await WriteJsonObjectEndAsync(cancellationToken); + } + + public override Task WriteFieldStopAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override async Task WriteMapBeginAsync(TMap map, CancellationToken cancellationToken) + { + await WriteJsonArrayStartAsync(cancellationToken); + await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(map.KeyType), cancellationToken); + await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(map.ValueType), cancellationToken); + await WriteJsonIntegerAsync(map.Count, cancellationToken); + await WriteJsonObjectStartAsync(cancellationToken); + } + + public override async Task WriteMapEndAsync(CancellationToken cancellationToken) + { + await WriteJsonObjectEndAsync(cancellationToken); + await WriteJsonArrayEndAsync(cancellationToken); + } + + public override async Task WriteListBeginAsync(TList list, CancellationToken cancellationToken) + { + await WriteJsonArrayStartAsync(cancellationToken); + await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(list.ElementType), cancellationToken); + await WriteJsonIntegerAsync(list.Count, cancellationToken); + } + + public override async Task WriteListEndAsync(CancellationToken cancellationToken) + { + await WriteJsonArrayEndAsync(cancellationToken); + } + + public override async Task WriteSetBeginAsync(TSet set, CancellationToken cancellationToken) + { + await WriteJsonArrayStartAsync(cancellationToken); + await WriteJsonStringAsync(TJSONProtocolHelper.GetTypeNameForTypeId(set.ElementType), cancellationToken); + await WriteJsonIntegerAsync(set.Count, cancellationToken); + } + + public override async Task WriteSetEndAsync(CancellationToken cancellationToken) + { + await WriteJsonArrayEndAsync(cancellationToken); + } + + public override async Task WriteBoolAsync(bool b, CancellationToken cancellationToken) + { + await WriteJsonIntegerAsync(b ? 1 : 0, cancellationToken); + } + + public override async Task WriteByteAsync(sbyte b, CancellationToken cancellationToken) + { + await WriteJsonIntegerAsync(b, cancellationToken); + } + + public override async Task WriteI16Async(short i16, CancellationToken cancellationToken) + { + await WriteJsonIntegerAsync(i16, cancellationToken); + } + + public override async Task WriteI32Async(int i32, CancellationToken cancellationToken) + { + await WriteJsonIntegerAsync(i32, cancellationToken); + } + + public override async Task WriteI64Async(long i64, CancellationToken cancellationToken) + { + await WriteJsonIntegerAsync(i64, cancellationToken); + } + + public override async Task WriteDoubleAsync(double d, CancellationToken cancellationToken) + { + await WriteJsonDoubleAsync(d, cancellationToken); + } + + public override async Task WriteStringAsync(string s, CancellationToken cancellationToken) + { + var b = Utf8Encoding.GetBytes(s); + await WriteJsonStringAsync(b, cancellationToken); + } + + public override async Task WriteBinaryAsync(byte[] bytes, CancellationToken cancellationToken) + { + await WriteJsonBase64Async(bytes, cancellationToken); + } + + /// + /// Read in a JSON string, unescaping as appropriate.. Skip Reading from the + /// context if skipContext is true. + /// + private async ValueTask ReadJsonStringAsync(bool skipContext, CancellationToken cancellationToken) + { + using (var buffer = new MemoryStream()) + { + var codeunits = new List(); + + + if (!skipContext) + { + await Context.ReadConditionalDelimiterAsync(cancellationToken); + } + + await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Quote, cancellationToken); + + while (true) + { + var ch = await Reader.ReadAsync(cancellationToken); + if (ch == TJSONProtocolConstants.Quote[0]) + { + break; + } + + // escaped? + if (ch != TJSONProtocolConstants.EscSequences[0]) + { +#if NETSTANDARD2_0 + await buffer.WriteAsync(new[] {ch}, 0, 1, cancellationToken); +#else + var wbuf = new[] { ch }; + await buffer.WriteAsync(wbuf.AsMemory(0, 1), cancellationToken); +#endif + continue; + } + + // distinguish between \uXXXX and \? + ch = await Reader.ReadAsync(cancellationToken); + if (ch != TJSONProtocolConstants.EscSequences[1]) // control chars like \n + { + var off = Array.IndexOf(TJSONProtocolConstants.EscapeChars, (char) ch); + if (off == -1) + { + throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected control char"); + } + ch = TJSONProtocolConstants.EscapeCharValues[off]; +#if NETSTANDARD2_0 + await buffer.WriteAsync(new[] {ch}, 0, 1, cancellationToken); +#else + var wbuf = new[] { ch }; + await buffer.WriteAsync( wbuf.AsMemory(0, 1), cancellationToken); +#endif + continue; + } + + // it's \uXXXX + await Trans.ReadAllAsync(_tempBuffer, 0, 4, cancellationToken); + + var wch = (short) ((TJSONProtocolHelper.ToHexVal(_tempBuffer[0]) << 12) + + (TJSONProtocolHelper.ToHexVal(_tempBuffer[1]) << 8) + + (TJSONProtocolHelper.ToHexVal(_tempBuffer[2]) << 4) + + TJSONProtocolHelper.ToHexVal(_tempBuffer[3])); + + if (char.IsHighSurrogate((char) wch)) + { + if (codeunits.Count > 0) + { + throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected low surrogate char"); + } + codeunits.Add((char) wch); + } + else if (char.IsLowSurrogate((char) wch)) + { + if (codeunits.Count == 0) + { + throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected high surrogate char"); + } + + codeunits.Add((char) wch); + var tmp = Utf8Encoding.GetBytes(codeunits.ToArray()); +#if NETSTANDARD2_0 + await buffer.WriteAsync(tmp, 0, tmp.Length, cancellationToken); +#else + await buffer.WriteAsync(tmp.AsMemory(0, tmp.Length), cancellationToken); +#endif + codeunits.Clear(); + } + else + { + var tmp = Utf8Encoding.GetBytes(new[] { (char)wch }); +#if NETSTANDARD2_0 + await buffer.WriteAsync(tmp, 0, tmp.Length, cancellationToken); +#else + await buffer.WriteAsync(tmp.AsMemory( 0, tmp.Length), cancellationToken); +#endif + } + } + + if (codeunits.Count > 0) + { + throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected low surrogate char"); + } + + return buffer.ToArray(); + } + } + + /// + /// Read in a sequence of characters that are all valid in JSON numbers. Does + /// not do a complete regex check to validate that this is actually a number. + /// + private async ValueTask ReadJsonNumericCharsAsync(CancellationToken cancellationToken) + { + var strbld = new StringBuilder(); + while (true) + { + //TODO: workaround for primitive types with TJsonProtocol, think - how to rewrite into more easy form without exceptions + try + { + var ch = await Reader.PeekAsync(cancellationToken); + if (!TJSONProtocolHelper.IsJsonNumeric(ch)) + { + break; + } + var c = (char)await Reader.ReadAsync(cancellationToken); + strbld.Append(c); + } + catch (TTransportException) + { + break; + } + } + return strbld.ToString(); + } + + /// + /// Read in a JSON number. If the context dictates, Read in enclosing quotes. + /// + private async ValueTask ReadJsonIntegerAsync(CancellationToken cancellationToken) + { + await Context.ReadConditionalDelimiterAsync(cancellationToken); + if (Context.EscapeNumbers()) + { + await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Quote, cancellationToken); + } + + var str = await ReadJsonNumericCharsAsync(cancellationToken); + if (Context.EscapeNumbers()) + { + await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Quote, cancellationToken); + } + + try + { + return long.Parse(str); + } + catch (FormatException) + { + throw new TProtocolException(TProtocolException.INVALID_DATA, "Bad data encounted in numeric data"); + } + } + + /// + /// Read in a JSON double value. Throw if the value is not wrapped in quotes + /// when expected or if wrapped in quotes when not expected. + /// + private async ValueTask ReadJsonDoubleAsync(CancellationToken cancellationToken) + { + await Context.ReadConditionalDelimiterAsync(cancellationToken); + if (await Reader.PeekAsync(cancellationToken) == TJSONProtocolConstants.Quote[0]) + { + var arr = await ReadJsonStringAsync(true, cancellationToken); + var dub = double.Parse(Utf8Encoding.GetString(arr, 0, arr.Length), CultureInfo.InvariantCulture); + + if (!Context.EscapeNumbers() && !double.IsNaN(dub) && !double.IsInfinity(dub)) + { + // Throw exception -- we should not be in a string in this case + throw new TProtocolException(TProtocolException.INVALID_DATA, "Numeric data unexpectedly quoted"); + } + + return dub; + } + + if (Context.EscapeNumbers()) + { + // This will throw - we should have had a quote if escapeNum == true + await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Quote, cancellationToken); + } + + try + { + return double.Parse(await ReadJsonNumericCharsAsync(cancellationToken), CultureInfo.InvariantCulture); + } + catch (FormatException) + { + throw new TProtocolException(TProtocolException.INVALID_DATA, "Bad data encounted in numeric data"); + } + } + + /// + /// Read in a JSON string containing base-64 encoded data and decode it. + /// + private async ValueTask ReadJsonBase64Async(CancellationToken cancellationToken) + { + var b = await ReadJsonStringAsync(false, cancellationToken); + var len = b.Length; + var off = 0; + var size = 0; + + // reduce len to ignore fill bytes + while ((len > 0) && (b[len - 1] == '=')) + { + --len; + } + + // read & decode full byte triplets = 4 source bytes + while (len > 4) + { + // Decode 4 bytes at a time + TBase64Utils.Decode(b, off, 4, b, size); // NB: decoded in place + off += 4; + len -= 4; + size += 3; + } + + // Don't decode if we hit the end or got a single leftover byte (invalid + // base64 but legal for skip of regular string exType) + if (len > 1) + { + // Decode remainder + TBase64Utils.Decode(b, off, len, b, size); // NB: decoded in place + size += len - 1; + } + + // Sadly we must copy the byte[] (any way around this?) + var result = new byte[size]; + Array.Copy(b, 0, result, 0, size); + return result; + } + + private async Task ReadJsonObjectStartAsync(CancellationToken cancellationToken) + { + await Context.ReadConditionalDelimiterAsync(cancellationToken); + await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.LeftBrace, cancellationToken); + PushContext(new JSONPairContext(this)); + } + + private async Task ReadJsonObjectEndAsync(CancellationToken cancellationToken) + { + await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.RightBrace, cancellationToken); + PopContext(); + } + + private async Task ReadJsonArrayStartAsync(CancellationToken cancellationToken) + { + await Context.ReadConditionalDelimiterAsync(cancellationToken); + await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.LeftBracket, cancellationToken); + PushContext(new JSONListContext(this)); + } + + private async Task ReadJsonArrayEndAsync(CancellationToken cancellationToken) + { + await ReadJsonSyntaxCharAsync(TJSONProtocolConstants.RightBracket, cancellationToken); + PopContext(); + } + + public override async ValueTask ReadMessageBeginAsync(CancellationToken cancellationToken) + { + var message = new TMessage(); + + ResetContext(); + await ReadJsonArrayStartAsync(cancellationToken); + if (await ReadJsonIntegerAsync(cancellationToken) != Version) + { + throw new TProtocolException(TProtocolException.BAD_VERSION, "Message contained bad version."); + } + + var buf = await ReadJsonStringAsync(false, cancellationToken); + message.Name = Utf8Encoding.GetString(buf, 0, buf.Length); + message.Type = (TMessageType) await ReadJsonIntegerAsync(cancellationToken); + message.SeqID = (int) await ReadJsonIntegerAsync(cancellationToken); + return message; + } + + public override async Task ReadMessageEndAsync(CancellationToken cancellationToken) + { + await ReadJsonArrayEndAsync(cancellationToken); + } + + public override async ValueTask ReadStructBeginAsync(CancellationToken cancellationToken) + { + await ReadJsonObjectStartAsync(cancellationToken); + + return AnonymousStruct; + } + + public override async Task ReadStructEndAsync(CancellationToken cancellationToken) + { + await ReadJsonObjectEndAsync(cancellationToken); + } + + public override async ValueTask ReadFieldBeginAsync(CancellationToken cancellationToken) + { + var ch = await Reader.PeekAsync(cancellationToken); + if (ch == TJSONProtocolConstants.RightBrace[0]) + { + return StopField; + } + + var field = new TField() + { + ID = (short)await ReadJsonIntegerAsync(cancellationToken) + }; + + await ReadJsonObjectStartAsync(cancellationToken); + field.Type = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken)); + return field; + } + + public override async Task ReadFieldEndAsync(CancellationToken cancellationToken) + { + await ReadJsonObjectEndAsync(cancellationToken); + } + + public override async ValueTask ReadMapBeginAsync(CancellationToken cancellationToken) + { + var map = new TMap(); + await ReadJsonArrayStartAsync(cancellationToken); + map.KeyType = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken)); + map.ValueType = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken)); + map.Count = (int) await ReadJsonIntegerAsync(cancellationToken); + CheckReadBytesAvailable(map); + await ReadJsonObjectStartAsync(cancellationToken); + return map; + } + + public override async Task ReadMapEndAsync(CancellationToken cancellationToken) + { + await ReadJsonObjectEndAsync(cancellationToken); + await ReadJsonArrayEndAsync(cancellationToken); + } + + public override async ValueTask ReadListBeginAsync(CancellationToken cancellationToken) + { + var list = new TList(); + await ReadJsonArrayStartAsync(cancellationToken); + list.ElementType = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken)); + list.Count = (int) await ReadJsonIntegerAsync(cancellationToken); + CheckReadBytesAvailable(list); + return list; + } + + public override async Task ReadListEndAsync(CancellationToken cancellationToken) + { + await ReadJsonArrayEndAsync(cancellationToken); + } + + public override async ValueTask ReadSetBeginAsync(CancellationToken cancellationToken) + { + var set = new TSet(); + await ReadJsonArrayStartAsync(cancellationToken); + set.ElementType = TJSONProtocolHelper.GetTypeIdForTypeName(await ReadJsonStringAsync(false, cancellationToken)); + set.Count = (int) await ReadJsonIntegerAsync(cancellationToken); + CheckReadBytesAvailable(set); + return set; + } + + public override async Task ReadSetEndAsync(CancellationToken cancellationToken) + { + await ReadJsonArrayEndAsync(cancellationToken); + } + + public override async ValueTask ReadBoolAsync(CancellationToken cancellationToken) + { + return await ReadJsonIntegerAsync(cancellationToken) != 0; + } + + public override async ValueTask ReadByteAsync(CancellationToken cancellationToken) + { + return (sbyte) await ReadJsonIntegerAsync(cancellationToken); + } + + public override async ValueTask ReadI16Async(CancellationToken cancellationToken) + { + return (short) await ReadJsonIntegerAsync(cancellationToken); + } + + public override async ValueTask ReadI32Async(CancellationToken cancellationToken) + { + return (int) await ReadJsonIntegerAsync(cancellationToken); + } + + public override async ValueTask ReadI64Async(CancellationToken cancellationToken) + { + return await ReadJsonIntegerAsync(cancellationToken); + } + + public override async ValueTask ReadDoubleAsync(CancellationToken cancellationToken) + { + return await ReadJsonDoubleAsync(cancellationToken); + } + + public override async ValueTask ReadStringAsync(CancellationToken cancellationToken) + { + var buf = await ReadJsonStringAsync(false, cancellationToken); + return Utf8Encoding.GetString(buf, 0, buf.Length); + } + + public override async ValueTask ReadBinaryAsync(CancellationToken cancellationToken) + { + return await ReadJsonBase64Async(cancellationToken); + } + + // Return the minimum number of bytes a type will consume on the wire + public override int GetMinSerializedSize(TType type) + { + switch (type) + { + case TType.Stop: return 0; + case TType.Void: return 0; + case TType.Bool: return 1; // written as int + case TType.Byte: return 1; + case TType.Double: return 1; + case TType.I16: return 1; + case TType.I32: return 1; + case TType.I64: return 1; + case TType.String: return 2; // empty string + case TType.Struct: return 2; // empty struct + case TType.Map: return 2; // empty map + case TType.Set: return 2; // empty set + case TType.List: return 2; // empty list + default: throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, "unrecognized type code"); + } + } + + /// + /// Factory for JSON protocol objects + /// + public class Factory : TProtocolFactory + { + public override TProtocol GetProtocol(TTransport trans) + { + return new TJsonProtocol(trans); + } + } + + /// + /// Base class for tracking JSON contexts that may require + /// inserting/Reading additional JSON syntax characters + /// This base context does nothing. + /// + protected class JSONBaseContext + { + protected TJsonProtocol Proto; + + public JSONBaseContext(TJsonProtocol proto) + { + Proto = proto; + } + + public virtual Task WriteConditionalDelimiterAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public virtual Task ReadConditionalDelimiterAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public virtual bool EscapeNumbers() + { + return false; + } + } + + /// + /// Context for JSON lists. Will insert/Read commas before each item except + /// for the first one + /// + protected class JSONListContext : JSONBaseContext + { + private bool _first = true; + + public JSONListContext(TJsonProtocol protocol) + : base(protocol) + { + } + + public override async Task WriteConditionalDelimiterAsync(CancellationToken cancellationToken) + { + if (_first) + { + _first = false; + } + else + { + await Proto.Trans.WriteAsync(TJSONProtocolConstants.Comma, cancellationToken); + } + } + + public override async Task ReadConditionalDelimiterAsync(CancellationToken cancellationToken) + { + if (_first) + { + _first = false; + } + else + { + await Proto.ReadJsonSyntaxCharAsync(TJSONProtocolConstants.Comma, cancellationToken); + } + } + } + + /// + /// Context for JSON records. Will insert/Read colons before the value portion + /// of each record pair, and commas before each key except the first. In + /// addition, will indicate that numbers in the key position need to be + /// escaped in quotes (since JSON keys must be strings). + /// + // ReSharper disable once InconsistentNaming + protected class JSONPairContext : JSONBaseContext + { + private bool _colon = true; + + private bool _first = true; + + public JSONPairContext(TJsonProtocol proto) + : base(proto) + { + } + + public override async Task WriteConditionalDelimiterAsync(CancellationToken cancellationToken) + { + if (_first) + { + _first = false; + _colon = true; + } + else + { + await Proto.Trans.WriteAsync(_colon ? TJSONProtocolConstants.Colon : TJSONProtocolConstants.Comma, cancellationToken); + _colon = !_colon; + } + } + + public override async Task ReadConditionalDelimiterAsync(CancellationToken cancellationToken) + { + if (_first) + { + _first = false; + _colon = true; + } + else + { + await Proto.ReadJsonSyntaxCharAsync(_colon ? TJSONProtocolConstants.Colon : TJSONProtocolConstants.Comma, cancellationToken); + _colon = !_colon; + } + } + + public override bool EscapeNumbers() + { + return _colon; + } + } + + /// + /// Holds up to one byte from the transport + /// + protected class LookaheadReader + { + private readonly byte[] _data = new byte[1]; + + private bool _hasData; + protected TJsonProtocol Proto; + + public LookaheadReader(TJsonProtocol proto) + { + Proto = proto; + } + + /// + /// Return and consume the next byte to be Read, either taking it from the + /// data buffer if present or getting it from the transport otherwise. + /// + public async ValueTask ReadAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (_hasData) + { + _hasData = false; + } + else + { + // find more easy way to avoid exception on reading primitive types + await Proto.Trans.ReadAllAsync(_data, 0, 1, cancellationToken); + } + return _data[0]; + } + + /// + /// Return the next byte to be Read without consuming, filling the data + /// buffer if it has not been filled alReady. + /// + public async ValueTask PeekAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (!_hasData) + { + // find more easy way to avoid exception on reading primitive types + await Proto.Trans.ReadAllAsync(_data, 0, 1, cancellationToken); + _hasData = true; + } + return _data[0]; + } + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Protocol/TMultiplexedProtocol.cs b/lib/csharp/thrift_netstd/Thrift/Protocol/TMultiplexedProtocol.cs new file mode 100644 index 000000000..fbc8c05cc --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Protocol/TMultiplexedProtocol.cs @@ -0,0 +1,91 @@ +// 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. + +using System.Threading; +using System.Threading.Tasks; +using Thrift.Protocol.Entities; + +namespace Thrift.Protocol +{ + /** + * TMultiplexedProtocol is a protocol-independent concrete decorator that allows a Thrift + * client to communicate with a multiplexing Thrift server, by prepending the service name + * to the function name during function calls. + * + * NOTE: THIS IS NOT TO BE USED BY SERVERS. + * On the server, use TMultiplexedProcessor to handle requests from a multiplexing client. + * + * This example uses a single socket transport to invoke two services: + * + * TSocketTransport transport = new TSocketTransport("localhost", 9090); + * transport.open(); + * + * TBinaryProtocol protocol = new TBinaryProtocol(transport); + * + * TMultiplexedProtocol mp = new TMultiplexedProtocol(protocol, "Calculator"); + * Calculator.Client service = new Calculator.Client(mp); + * + * TMultiplexedProtocol mp2 = new TMultiplexedProtocol(protocol, "WeatherReport"); + * WeatherReport.Client service2 = new WeatherReport.Client(mp2); + * + * System.out.println(service.add(2,2)); + * System.out.println(service2.getTemperature()); + * + */ + + //TODO: implementation of TProtocol + + // ReSharper disable once InconsistentNaming + public class TMultiplexedProtocol : TProtocolDecorator + { + /** Used to delimit the service name from the function name */ + public const string Separator = ":"; + + private readonly string _serviceName; + + /** + * Wrap the specified protocol, allowing it to be used to communicate with a + * multiplexing server. The serviceName is required as it is + * prepended to the message header so that the multiplexing server can broker + * the function call to the proper service. + * + * Args: + * protocol Your communication protocol of choice, e.g. TBinaryProtocol + * serviceName The service name of the service communicating via this protocol. + */ + + public TMultiplexedProtocol(TProtocol protocol, string serviceName) + : base(protocol) + { + _serviceName = serviceName; + } + + public override async Task WriteMessageBeginAsync(TMessage message, CancellationToken cancellationToken) + { + switch (message.Type) + { + case TMessageType.Call: + case TMessageType.Oneway: + await base.WriteMessageBeginAsync(new TMessage($"{_serviceName}{Separator}{message.Name}", message.Type, message.SeqID), cancellationToken); + break; + default: + await base.WriteMessageBeginAsync(message, cancellationToken); + break; + } + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Protocol/TProtocol.cs b/lib/csharp/thrift_netstd/Thrift/Protocol/TProtocol.cs new file mode 100644 index 000000000..cd9383389 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Protocol/TProtocol.cs @@ -0,0 +1,196 @@ +// 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. + +using System; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Thrift.Protocol.Entities; +using Thrift.Transport; + +namespace Thrift.Protocol +{ + // ReSharper disable once InconsistentNaming + public abstract class TProtocol : IDisposable + { + private bool _isDisposed; + protected int RecursionDepth; + + protected TTransport Trans; + + protected static readonly TStruct AnonymousStruct = new TStruct(string.Empty); + protected static readonly TField StopField = new TField() { Type = TType.Stop }; + + + protected TProtocol(TTransport trans) + { + Trans = trans; + RecursionLimit = trans.Configuration.RecursionLimit; + RecursionDepth = 0; + } + + public TTransport Transport => Trans; + + protected int RecursionLimit { get; set; } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public void IncrementRecursionDepth() + { + if (RecursionDepth < RecursionLimit) + { + ++RecursionDepth; + } + else + { + throw new TProtocolException(TProtocolException.DEPTH_LIMIT, "Depth limit exceeded"); + } + } + + public void DecrementRecursionDepth() + { + --RecursionDepth; + } + + protected virtual void Dispose(bool disposing) + { + if (!_isDisposed) + { + if (disposing) + { + (Trans as IDisposable)?.Dispose(); + } + } + _isDisposed = true; + } + + + protected void CheckReadBytesAvailable(TSet set) + { + Transport.CheckReadBytesAvailable(set.Count * GetMinSerializedSize(set.ElementType)); + } + + protected void CheckReadBytesAvailable(TList list) + { + Transport.CheckReadBytesAvailable(list.Count * GetMinSerializedSize(list.ElementType)); + } + + protected void CheckReadBytesAvailable(TMap map) + { + var elmSize = GetMinSerializedSize(map.KeyType) + GetMinSerializedSize(map.ValueType); + Transport.CheckReadBytesAvailable(map.Count * elmSize); + } + + // Returns the minimum amount of bytes needed to store the smallest possible instance of TType. + public abstract int GetMinSerializedSize(TType type); + + + public abstract Task WriteMessageBeginAsync(TMessage message, CancellationToken cancellationToken = default); + + public abstract Task WriteMessageEndAsync(CancellationToken cancellationToken = default); + + public abstract Task WriteStructBeginAsync(TStruct @struct, CancellationToken cancellationToken = default); + + public abstract Task WriteStructEndAsync(CancellationToken cancellationToken = default); + + public abstract Task WriteFieldBeginAsync(TField field, CancellationToken cancellationToken = default); + + public abstract Task WriteFieldEndAsync(CancellationToken cancellationToken = default); + + public abstract Task WriteFieldStopAsync(CancellationToken cancellationToken = default); + + public abstract Task WriteMapBeginAsync(TMap map, CancellationToken cancellationToken = default); + + public abstract Task WriteMapEndAsync(CancellationToken cancellationToken = default); + + public abstract Task WriteListBeginAsync(TList list, CancellationToken cancellationToken = default); + + public abstract Task WriteListEndAsync(CancellationToken cancellationToken = default); + + public abstract Task WriteSetBeginAsync(TSet set, CancellationToken cancellationToken = default); + + public abstract Task WriteSetEndAsync(CancellationToken cancellationToken = default); + + public abstract Task WriteBoolAsync(bool b, CancellationToken cancellationToken = default); + + public abstract Task WriteByteAsync(sbyte b, CancellationToken cancellationToken = default); + + public abstract Task WriteI16Async(short i16, CancellationToken cancellationToken = default); + + public abstract Task WriteI32Async(int i32, CancellationToken cancellationToken = default); + + public abstract Task WriteI64Async(long i64, CancellationToken cancellationToken = default); + + public abstract Task WriteDoubleAsync(double d, CancellationToken cancellationToken = default); + + public virtual async Task WriteStringAsync(string s, CancellationToken cancellationToken = default) + { + var bytes = Encoding.UTF8.GetBytes(s); + await WriteBinaryAsync(bytes, cancellationToken); + } + + public abstract Task WriteBinaryAsync(byte[] bytes, CancellationToken cancellationToken = default); + + public abstract ValueTask ReadMessageBeginAsync(CancellationToken cancellationToken = default); + + public abstract Task ReadMessageEndAsync(CancellationToken cancellationToken = default); + + public abstract ValueTask ReadStructBeginAsync(CancellationToken cancellationToken = default); + + public abstract Task ReadStructEndAsync(CancellationToken cancellationToken = default); + + public abstract ValueTask ReadFieldBeginAsync(CancellationToken cancellationToken = default); + + public abstract Task ReadFieldEndAsync(CancellationToken cancellationToken = default); + + public abstract ValueTask ReadMapBeginAsync(CancellationToken cancellationToken = default); + + public abstract Task ReadMapEndAsync(CancellationToken cancellationToken = default); + + public abstract ValueTask ReadListBeginAsync(CancellationToken cancellationToken = default); + + public abstract Task ReadListEndAsync(CancellationToken cancellationToken = default); + + public abstract ValueTask ReadSetBeginAsync(CancellationToken cancellationToken = default); + + public abstract Task ReadSetEndAsync(CancellationToken cancellationToken = default); + + public abstract ValueTask ReadBoolAsync(CancellationToken cancellationToken = default); + + public abstract ValueTask ReadByteAsync(CancellationToken cancellationToken = default); + + public abstract ValueTask ReadI16Async(CancellationToken cancellationToken = default); + + public abstract ValueTask ReadI32Async(CancellationToken cancellationToken = default); + + public abstract ValueTask ReadI64Async(CancellationToken cancellationToken = default); + + public abstract ValueTask ReadDoubleAsync(CancellationToken cancellationToken = default); + + public virtual async ValueTask ReadStringAsync(CancellationToken cancellationToken = default) + { + var buf = await ReadBinaryAsync(cancellationToken); + return Encoding.UTF8.GetString(buf, 0, buf.Length); + } + + public abstract ValueTask ReadBinaryAsync(CancellationToken cancellationToken = default); + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Protocol/TProtocolDecorator.cs b/lib/csharp/thrift_netstd/Thrift/Protocol/TProtocolDecorator.cs new file mode 100644 index 000000000..b032e836a --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Protocol/TProtocolDecorator.cs @@ -0,0 +1,255 @@ +// 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. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Thrift.Protocol.Entities; + +namespace Thrift.Protocol +{ + // ReSharper disable once InconsistentNaming + /// + /// TProtocolDecorator forwards all requests to an enclosed TProtocol instance, + /// providing a way to author concise concrete decorator subclasses.While it has + /// no abstract methods, it is marked abstract as a reminder that by itself, + /// it does not modify the behaviour of the enclosed TProtocol. + /// + public abstract class TProtocolDecorator : TProtocol + { + private readonly TProtocol _wrappedProtocol; + + protected TProtocolDecorator(TProtocol protocol) + : base(protocol.Transport) + { + _wrappedProtocol = protocol ?? throw new ArgumentNullException(nameof(protocol)); + } + + public override async Task WriteMessageBeginAsync(TMessage message, CancellationToken cancellationToken) + { + await _wrappedProtocol.WriteMessageBeginAsync(message, cancellationToken); + } + + public override async Task WriteMessageEndAsync(CancellationToken cancellationToken) + { + await _wrappedProtocol.WriteMessageEndAsync(cancellationToken); + } + + public override async Task WriteStructBeginAsync(TStruct @struct, CancellationToken cancellationToken) + { + await _wrappedProtocol.WriteStructBeginAsync(@struct, cancellationToken); + } + + public override async Task WriteStructEndAsync(CancellationToken cancellationToken) + { + await _wrappedProtocol.WriteStructEndAsync(cancellationToken); + } + + public override async Task WriteFieldBeginAsync(TField field, CancellationToken cancellationToken) + { + await _wrappedProtocol.WriteFieldBeginAsync(field, cancellationToken); + } + + public override async Task WriteFieldEndAsync(CancellationToken cancellationToken) + { + await _wrappedProtocol.WriteFieldEndAsync(cancellationToken); + } + + public override async Task WriteFieldStopAsync(CancellationToken cancellationToken) + { + await _wrappedProtocol.WriteFieldStopAsync(cancellationToken); + } + + public override async Task WriteMapBeginAsync(TMap map, CancellationToken cancellationToken) + { + await _wrappedProtocol.WriteMapBeginAsync(map, cancellationToken); + } + + public override async Task WriteMapEndAsync(CancellationToken cancellationToken) + { + await _wrappedProtocol.WriteMapEndAsync(cancellationToken); + } + + public override async Task WriteListBeginAsync(TList list, CancellationToken cancellationToken) + { + await _wrappedProtocol.WriteListBeginAsync(list, cancellationToken); + } + + public override async Task WriteListEndAsync(CancellationToken cancellationToken) + { + await _wrappedProtocol.WriteListEndAsync(cancellationToken); + } + + public override async Task WriteSetBeginAsync(TSet set, CancellationToken cancellationToken) + { + await _wrappedProtocol.WriteSetBeginAsync(set, cancellationToken); + } + + public override async Task WriteSetEndAsync(CancellationToken cancellationToken) + { + await _wrappedProtocol.WriteSetEndAsync(cancellationToken); + } + + public override async Task WriteBoolAsync(bool b, CancellationToken cancellationToken) + { + await _wrappedProtocol.WriteBoolAsync(b, cancellationToken); + } + + public override async Task WriteByteAsync(sbyte b, CancellationToken cancellationToken) + { + await _wrappedProtocol.WriteByteAsync(b, cancellationToken); + } + + public override async Task WriteI16Async(short i16, CancellationToken cancellationToken) + { + await _wrappedProtocol.WriteI16Async(i16, cancellationToken); + } + + public override async Task WriteI32Async(int i32, CancellationToken cancellationToken) + { + await _wrappedProtocol.WriteI32Async(i32, cancellationToken); + } + + public override async Task WriteI64Async(long i64, CancellationToken cancellationToken) + { + await _wrappedProtocol.WriteI64Async(i64, cancellationToken); + } + + public override async Task WriteDoubleAsync(double d, CancellationToken cancellationToken) + { + await _wrappedProtocol.WriteDoubleAsync(d, cancellationToken); + } + + public override async Task WriteStringAsync(string s, CancellationToken cancellationToken) + { + await _wrappedProtocol.WriteStringAsync(s, cancellationToken); + } + + public override async Task WriteBinaryAsync(byte[] bytes, CancellationToken cancellationToken) + { + await _wrappedProtocol.WriteBinaryAsync(bytes, cancellationToken); + } + + public override async ValueTask ReadMessageBeginAsync(CancellationToken cancellationToken) + { + return await _wrappedProtocol.ReadMessageBeginAsync(cancellationToken); + } + + public override async Task ReadMessageEndAsync(CancellationToken cancellationToken) + { + await _wrappedProtocol.ReadMessageEndAsync(cancellationToken); + } + + public override async ValueTask ReadStructBeginAsync(CancellationToken cancellationToken) + { + return await _wrappedProtocol.ReadStructBeginAsync(cancellationToken); + } + + public override async Task ReadStructEndAsync(CancellationToken cancellationToken) + { + await _wrappedProtocol.ReadStructEndAsync(cancellationToken); + } + + public override async ValueTask ReadFieldBeginAsync(CancellationToken cancellationToken) + { + return await _wrappedProtocol.ReadFieldBeginAsync(cancellationToken); + } + + public override async Task ReadFieldEndAsync(CancellationToken cancellationToken) + { + await _wrappedProtocol.ReadFieldEndAsync(cancellationToken); + } + + public override async ValueTask ReadMapBeginAsync(CancellationToken cancellationToken) + { + return await _wrappedProtocol.ReadMapBeginAsync(cancellationToken); + } + + public override async Task ReadMapEndAsync(CancellationToken cancellationToken) + { + await _wrappedProtocol.ReadMapEndAsync(cancellationToken); + } + + public override async ValueTask ReadListBeginAsync(CancellationToken cancellationToken) + { + return await _wrappedProtocol.ReadListBeginAsync(cancellationToken); + } + + public override async Task ReadListEndAsync(CancellationToken cancellationToken) + { + await _wrappedProtocol.ReadListEndAsync(cancellationToken); + } + + public override async ValueTask ReadSetBeginAsync(CancellationToken cancellationToken) + { + return await _wrappedProtocol.ReadSetBeginAsync(cancellationToken); + } + + public override async Task ReadSetEndAsync(CancellationToken cancellationToken) + { + await _wrappedProtocol.ReadSetEndAsync(cancellationToken); + } + + public override async ValueTask ReadBoolAsync(CancellationToken cancellationToken) + { + return await _wrappedProtocol.ReadBoolAsync(cancellationToken); + } + + public override async ValueTask ReadByteAsync(CancellationToken cancellationToken) + { + return await _wrappedProtocol.ReadByteAsync(cancellationToken); + } + + public override async ValueTask ReadI16Async(CancellationToken cancellationToken) + { + return await _wrappedProtocol.ReadI16Async(cancellationToken); + } + + public override async ValueTask ReadI32Async(CancellationToken cancellationToken) + { + return await _wrappedProtocol.ReadI32Async(cancellationToken); + } + + public override async ValueTask ReadI64Async(CancellationToken cancellationToken) + { + return await _wrappedProtocol.ReadI64Async(cancellationToken); + } + + public override async ValueTask ReadDoubleAsync(CancellationToken cancellationToken) + { + return await _wrappedProtocol.ReadDoubleAsync(cancellationToken); + } + + public override async ValueTask ReadStringAsync(CancellationToken cancellationToken) + { + return await _wrappedProtocol.ReadStringAsync(cancellationToken); + } + + public override async ValueTask ReadBinaryAsync(CancellationToken cancellationToken) + { + return await _wrappedProtocol.ReadBinaryAsync(cancellationToken); + } + + // Returns the minimum amount of bytes needed to store the smallest possible instance of TType. + public override int GetMinSerializedSize(TType type) + { + return _wrappedProtocol.GetMinSerializedSize(type); + } + + + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Protocol/TProtocolException.cs b/lib/csharp/thrift_netstd/Thrift/Protocol/TProtocolException.cs new file mode 100644 index 000000000..328babd05 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Protocol/TProtocolException.cs @@ -0,0 +1,62 @@ +// 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. + +// ReSharper disable InconsistentNaming +using System; + +namespace Thrift.Protocol +{ + public class TProtocolException : TException + { + // do not rename public constants - they used in generated files + public const int UNKNOWN = 0; + public const int INVALID_DATA = 1; + public const int NEGATIVE_SIZE = 2; + public const int SIZE_LIMIT = 3; + public const int BAD_VERSION = 4; + public const int NOT_IMPLEMENTED = 5; + public const int DEPTH_LIMIT = 6; + + protected int Type = UNKNOWN; + + public TProtocolException() + { + } + + public TProtocolException(int type, Exception inner = null) + : base(string.Empty, inner) + { + Type = type; + } + + public TProtocolException(int type, string message, Exception inner = null) + : base(message, inner) + { + Type = type; + } + + public TProtocolException(string message, Exception inner = null) + : base(message, inner) + { + } + + public int GetExceptionType() + { + return Type; + } + } +} \ No newline at end of file diff --git a/lib/csharp/thrift_netstd/Thrift/Protocol/TProtocolFactory.cs b/lib/csharp/thrift_netstd/Thrift/Protocol/TProtocolFactory.cs new file mode 100644 index 000000000..31b05148b --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Protocol/TProtocolFactory.cs @@ -0,0 +1,27 @@ +// 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. + +using Thrift.Transport; + +namespace Thrift.Protocol +{ + // ReSharper disable once InconsistentNaming + public abstract class TProtocolFactory + { + public abstract TProtocol GetProtocol(TTransport trans); + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Protocol/ToString.cs b/lib/csharp/thrift_netstd/Thrift/Protocol/ToString.cs new file mode 100644 index 000000000..50dd6df6d --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Protocol/ToString.cs @@ -0,0 +1,82 @@ +// 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. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using Thrift.Protocol; + +namespace Thrift.Protocol +{ + + + public static class ToStringExtensions + { + public static void ToString(this object self, StringBuilder sb, bool first = true) + { + if (!first) + sb.Append(", "); + + bool first_child = true; + if (self is string) // string is IEnumerable + { + sb.Append('"'); + sb.Append(self); + sb.Append('"'); + } + else if (self is IDictionary) + { + sb.Append("{ "); + foreach (DictionaryEntry pair in (self as IDictionary)) + { + if (first_child) + first_child = false; + else + sb.Append(','); + + sb.Append("{ "); + pair.Key.ToString(sb); + sb.Append(", "); + pair.Value.ToString(sb); + sb.Append('}'); + } + sb.Append('}'); + } + else if (self is IEnumerable) + { + sb.Append("{ "); + foreach (var elm in (self as IEnumerable)) + { + elm.ToString(sb, first_child); + first_child = false; + } + sb.Append('}'); + } + else if (self is TBase) + { + sb.Append((self as TBase).ToString()); + } + else + { + sb.Append(self != null? self.ToString() : ""); + } + } + } + + +} diff --git a/lib/csharp/thrift_netstd/Thrift/Protocol/Utilities/TBase64Utils.cs b/lib/csharp/thrift_netstd/Thrift/Protocol/Utilities/TBase64Utils.cs new file mode 100644 index 000000000..90b8f8867 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Protocol/Utilities/TBase64Utils.cs @@ -0,0 +1,101 @@ +// 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. + +using System; + +namespace Thrift.Protocol.Utilities +{ + // ReSharper disable once InconsistentNaming + internal static class TBase64Utils + { + //TODO: Constants + //TODO: Check for args + //TODO: Unitests + + internal const string EncodeTable = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + private static readonly int[] DecodeTable = + { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 + }; + + internal static void Encode(byte[] src, int srcOff, int len, byte[] dst, int dstOff) + { + if (src == null) + { + throw new ArgumentNullException(nameof(src)); + } + + dst[dstOff] = (byte) EncodeTable[(src[srcOff] >> 2) & 0x3F]; + + if (len == 3) + { + dst[dstOff + 1] = (byte) EncodeTable[((src[srcOff] << 4) & 0x30) | ((src[srcOff + 1] >> 4) & 0x0F)]; + dst[dstOff + 2] = (byte) EncodeTable[((src[srcOff + 1] << 2) & 0x3C) | ((src[srcOff + 2] >> 6) & 0x03)]; + dst[dstOff + 3] = (byte) EncodeTable[src[srcOff + 2] & 0x3F]; + } + else if (len == 2) + { + dst[dstOff + 1] = (byte) EncodeTable[((src[srcOff] << 4) & 0x30) | ((src[srcOff + 1] >> 4) & 0x0F)]; + dst[dstOff + 2] = (byte) EncodeTable[(src[srcOff + 1] << 2) & 0x3C]; + } + else + { + // len == 1 + dst[dstOff + 1] = (byte) EncodeTable[(src[srcOff] << 4) & 0x30]; + } + } + + internal static void Decode(byte[] src, int srcOff, int len, byte[] dst, int dstOff) + { + if (src == null) + { + throw new ArgumentNullException(nameof(src)); + } + + dst[dstOff] = (byte) ((DecodeTable[src[srcOff] & 0x0FF] << 2) | (DecodeTable[src[srcOff + 1] & 0x0FF] >> 4)); + + if (len > 2) + { + dst[dstOff + 1] = + (byte) + (((DecodeTable[src[srcOff + 1] & 0x0FF] << 4) & 0xF0) | (DecodeTable[src[srcOff + 2] & 0x0FF] >> 2)); + if (len > 3) + { + dst[dstOff + 2] = + (byte) + (((DecodeTable[src[srcOff + 2] & 0x0FF] << 6) & 0xC0) | DecodeTable[src[srcOff + 3] & 0x0FF]); + } + } + } + } +} \ No newline at end of file diff --git a/lib/csharp/thrift_netstd/Thrift/Protocol/Utilities/TJsonProtocolConstants.cs b/lib/csharp/thrift_netstd/Thrift/Protocol/Utilities/TJsonProtocolConstants.cs new file mode 100644 index 000000000..6cc1302e9 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Protocol/Utilities/TJsonProtocolConstants.cs @@ -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. + +namespace Thrift.Protocol.Utilities +{ + // ReSharper disable once InconsistentNaming + public static class TJSONProtocolConstants + { + //TODO Check for performance for reusing ImmutableArray from System.Collections.Immutable (https://blogs.msdn.microsoft.com/dotnet/2013/06/24/please-welcome-immutablearrayt/) + // can be possible to get better performance and also better GC + + public static readonly byte[] Comma = {(byte) ','}; + public static readonly byte[] Colon = {(byte) ':'}; + public static readonly byte[] LeftBrace = {(byte) '{'}; + public static readonly byte[] RightBrace = {(byte) '}'}; + public static readonly byte[] LeftBracket = {(byte) '['}; + public static readonly byte[] RightBracket = {(byte) ']'}; + public static readonly byte[] Quote = {(byte) '"'}; + public static readonly byte[] Backslash = {(byte) '\\'}; + + public static readonly byte[] JsonCharTable = + { + 0, 0, 0, 0, 0, 0, 0, 0, (byte) 'b', (byte) 't', (byte) 'n', 0, (byte) 'f', (byte) 'r', 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, (byte) '"', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + + public static readonly char[] EscapeChars = "\"\\/bfnrt".ToCharArray(); + public static readonly byte[] EscapeCharValues = {(byte) '"', (byte) '\\', (byte) '/', (byte) '\b', (byte) '\f', (byte) '\n', (byte) '\r', (byte) '\t'}; + public static readonly byte[] EscSequences = {(byte) '\\', (byte) 'u', (byte) '0', (byte) '0'}; + + public static class TypeNames + { + public static readonly byte[] NameBool = { (byte)'t', (byte)'f' }; + public static readonly byte[] NameByte = { (byte)'i', (byte)'8' }; + public static readonly byte[] NameI16 = { (byte)'i', (byte)'1', (byte)'6' }; + public static readonly byte[] NameI32 = { (byte)'i', (byte)'3', (byte)'2' }; + public static readonly byte[] NameI64 = { (byte)'i', (byte)'6', (byte)'4' }; + public static readonly byte[] NameDouble = { (byte)'d', (byte)'b', (byte)'l' }; + public static readonly byte[] NameStruct = { (byte)'r', (byte)'e', (byte)'c' }; + public static readonly byte[] NameString = { (byte)'s', (byte)'t', (byte)'r' }; + public static readonly byte[] NameMap = { (byte)'m', (byte)'a', (byte)'p' }; + public static readonly byte[] NameList = { (byte)'l', (byte)'s', (byte)'t' }; + public static readonly byte[] NameSet = { (byte)'s', (byte)'e', (byte)'t' }; + } + } +} \ No newline at end of file diff --git a/lib/csharp/thrift_netstd/Thrift/Protocol/Utilities/TJsonProtocolHelper.cs b/lib/csharp/thrift_netstd/Thrift/Protocol/Utilities/TJsonProtocolHelper.cs new file mode 100644 index 000000000..ff49ebe24 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Protocol/Utilities/TJsonProtocolHelper.cs @@ -0,0 +1,176 @@ +// 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. + +using Thrift.Protocol.Entities; + +namespace Thrift.Protocol.Utilities +{ + // ReSharper disable once InconsistentNaming + public static class TJSONProtocolHelper + { + public static byte[] GetTypeNameForTypeId(TType typeId) + { + switch (typeId) + { + case TType.Bool: + return TJSONProtocolConstants.TypeNames.NameBool; + case TType.Byte: + return TJSONProtocolConstants.TypeNames.NameByte; + case TType.I16: + return TJSONProtocolConstants.TypeNames.NameI16; + case TType.I32: + return TJSONProtocolConstants.TypeNames.NameI32; + case TType.I64: + return TJSONProtocolConstants.TypeNames.NameI64; + case TType.Double: + return TJSONProtocolConstants.TypeNames.NameDouble; + case TType.String: + return TJSONProtocolConstants.TypeNames.NameString; + case TType.Struct: + return TJSONProtocolConstants.TypeNames.NameStruct; + case TType.Map: + return TJSONProtocolConstants.TypeNames.NameMap; + case TType.Set: + return TJSONProtocolConstants.TypeNames.NameSet; + case TType.List: + return TJSONProtocolConstants.TypeNames.NameList; + default: + throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, "Unrecognized exType"); + } + } + + public static TType GetTypeIdForTypeName(byte[] name) + { + var result = TType.Stop; + if (name.Length > 1) + { + switch (name[0]) + { + case (byte) 'd': + result = TType.Double; + break; + case (byte) 'i': + switch (name[1]) + { + case (byte) '8': + result = TType.Byte; + break; + case (byte) '1': + result = TType.I16; + break; + case (byte) '3': + result = TType.I32; + break; + case (byte) '6': + result = TType.I64; + break; + } + break; + case (byte) 'l': + result = TType.List; + break; + case (byte) 'm': + result = TType.Map; + break; + case (byte) 'r': + result = TType.Struct; + break; + case (byte) 's': + if (name[1] == (byte) 't') + { + result = TType.String; + } + else if (name[1] == (byte) 'e') + { + result = TType.Set; + } + break; + case (byte) 't': + result = TType.Bool; + break; + } + } + if (result == TType.Stop) + { + throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, "Unrecognized exType"); + } + return result; + } + + /// + /// Return true if the given byte could be a valid part of a JSON number. + /// + public static bool IsJsonNumeric(byte b) + { + switch (b) + { + case (byte)'+': + case (byte)'-': + case (byte)'.': + case (byte)'0': + case (byte)'1': + case (byte)'2': + case (byte)'3': + case (byte)'4': + case (byte)'5': + case (byte)'6': + case (byte)'7': + case (byte)'8': + case (byte)'9': + case (byte)'E': + case (byte)'e': + return true; + default: + return false; + } + } + + /// + /// Convert a byte containing a hex char ('0'-'9' or 'a'-'f') into its + /// corresponding hex value + /// + public static byte ToHexVal(byte ch) + { + if (ch >= '0' && ch <= '9') + { + return (byte)((char)ch - '0'); + } + + if (ch >= 'a' && ch <= 'f') + { + ch += 10; + return (byte)((char)ch - 'a'); + } + + throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected hex character"); + } + + /// + /// Convert a byte containing a hex value to its corresponding hex character + /// + public static byte ToHexChar(byte val) + { + val &= 0x0F; + if (val < 10) + { + return (byte)((char)val + '0'); + } + val -= 10; + return (byte)((char)val + 'a'); + } + } +} \ No newline at end of file diff --git a/lib/csharp/thrift_netstd/Thrift/Protocol/Utilities/TProtocolUtil.cs b/lib/csharp/thrift_netstd/Thrift/Protocol/Utilities/TProtocolUtil.cs new file mode 100644 index 000000000..832e46e6c --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Protocol/Utilities/TProtocolUtil.cs @@ -0,0 +1,107 @@ +// 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. + +using System.Threading; +using System.Threading.Tasks; +using Thrift.Protocol.Entities; + +namespace Thrift.Protocol.Utilities +{ + // ReSharper disable once InconsistentNaming + public static class TProtocolUtil + { + public static async Task SkipAsync(TProtocol protocol, TType type, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + protocol.IncrementRecursionDepth(); + try + { + switch (type) + { + case TType.Bool: + await protocol.ReadBoolAsync(cancellationToken); + break; + case TType.Byte: + await protocol.ReadByteAsync(cancellationToken); + break; + case TType.I16: + await protocol.ReadI16Async(cancellationToken); + break; + case TType.I32: + await protocol.ReadI32Async(cancellationToken); + break; + case TType.I64: + await protocol.ReadI64Async(cancellationToken); + break; + case TType.Double: + await protocol.ReadDoubleAsync(cancellationToken); + break; + case TType.String: + // Don't try to decode the string, just skip it. + await protocol.ReadBinaryAsync(cancellationToken); + break; + case TType.Struct: + await protocol.ReadStructBeginAsync(cancellationToken); + while (true) + { + var field = await protocol.ReadFieldBeginAsync(cancellationToken); + if (field.Type == TType.Stop) + { + break; + } + await SkipAsync(protocol, field.Type, cancellationToken); + await protocol.ReadFieldEndAsync(cancellationToken); + } + await protocol.ReadStructEndAsync(cancellationToken); + break; + case TType.Map: + var map = await protocol.ReadMapBeginAsync(cancellationToken); + for (var i = 0; i < map.Count; i++) + { + await SkipAsync(protocol, map.KeyType, cancellationToken); + await SkipAsync(protocol, map.ValueType, cancellationToken); + } + await protocol.ReadMapEndAsync(cancellationToken); + break; + case TType.Set: + var set = await protocol.ReadSetBeginAsync(cancellationToken); + for (var i = 0; i < set.Count; i++) + { + await SkipAsync(protocol, set.ElementType, cancellationToken); + } + await protocol.ReadSetEndAsync(cancellationToken); + break; + case TType.List: + var list = await protocol.ReadListBeginAsync(cancellationToken); + for (var i = 0; i < list.Count; i++) + { + await SkipAsync(protocol, list.ElementType, cancellationToken); + } + await protocol.ReadListEndAsync(cancellationToken); + break; + default: + throw new TProtocolException(TProtocolException.INVALID_DATA, "Unknown data type " + type.ToString("d")); + } + } + finally + { + protocol.DecrementRecursionDepth(); + } + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Server/TServer.cs b/lib/csharp/thrift_netstd/Thrift/Server/TServer.cs new file mode 100644 index 000000000..3d4f3fc20 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Server/TServer.cs @@ -0,0 +1,85 @@ +// 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. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Thrift.Protocol; +using Thrift.Transport; +using Thrift.Processor; + +namespace Thrift.Server +{ + // ReSharper disable once InconsistentNaming + public abstract class TServer + { + protected readonly ILogger Logger; + protected TProtocolFactory InputProtocolFactory; + protected TTransportFactory InputTransportFactory; + protected ITProcessorFactory ProcessorFactory; + protected TProtocolFactory OutputProtocolFactory; + protected TTransportFactory OutputTransportFactory; + + protected ITServerEventHandler ServerEventHandler; + protected TServerTransport ServerTransport; + + protected TServer(ITProcessorFactory processorFactory, TServerTransport serverTransport, + TTransportFactory inputTransportFactory, TTransportFactory outputTransportFactory, + TProtocolFactory inputProtocolFactory, TProtocolFactory outputProtocolFactory, + ILogger logger = null) + { + ProcessorFactory = processorFactory ?? throw new ArgumentNullException(nameof(processorFactory)); + ServerTransport = serverTransport; + InputTransportFactory = inputTransportFactory ?? new TTransportFactory(); + OutputTransportFactory = outputTransportFactory ?? new TTransportFactory(); + InputProtocolFactory = inputProtocolFactory ?? throw new ArgumentNullException(nameof(inputProtocolFactory)); + OutputProtocolFactory = outputProtocolFactory ?? throw new ArgumentNullException(nameof(outputProtocolFactory)); + Logger = logger; // null is absolutely legal + } + + public void SetEventHandler(ITServerEventHandler seh) + { + ServerEventHandler = seh; + } + + public ITServerEventHandler GetEventHandler() + { + return ServerEventHandler; + } + + // Log delegation? deprecated, use ILogger + protected void LogError( string msg) + { + if (Logger != null) + Logger.LogError("{Msg}",msg); // NOTE: Log message template, not string interpolation! + } + + public abstract void Stop(); + + public virtual void Start() + { + // do nothing + } + + public virtual Task ServeAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Server/TServerEventHandler.cs b/lib/csharp/thrift_netstd/Thrift/Server/TServerEventHandler.cs new file mode 100644 index 000000000..9226b42a1 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Server/TServerEventHandler.cs @@ -0,0 +1,62 @@ +// 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. + +using System.Threading; +using System.Threading.Tasks; +using Thrift.Protocol; +using Thrift.Transport; + +namespace Thrift.Server +{ + //TODO: replacement by event? + + /// + /// Interface implemented by server users to handle events from the server + /// + /// Replaced by ITServerEventHandler + // ReSharper disable once InconsistentNaming + #pragma warning disable IDE1006 + public interface TServerEventHandler : ITServerEventHandler { } + #pragma warning restore IDE1006 + + /// + /// Interface implemented by server users to handle events from the server + /// + public interface ITServerEventHandler + { + /// + /// Called before the server begins */ + /// + Task PreServeAsync(CancellationToken cancellationToken); + + /// + /// Called when a new client has connected and is about to being processing */ + /// + Task CreateContextAsync(TProtocol input, TProtocol output, CancellationToken cancellationToken); + + /// + /// Called when a client has finished request-handling to delete server context */ + /// + Task DeleteContextAsync(object serverContext, TProtocol input, TProtocol output, + CancellationToken cancellationToken); + + /// + /// Called when a client is about to call the processor */ + /// + Task ProcessContextAsync(object serverContext, TTransport transport, CancellationToken cancellationToken); + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Server/TSimpleAsyncServer.cs b/lib/csharp/thrift_netstd/Thrift/Server/TSimpleAsyncServer.cs new file mode 100644 index 000000000..f1f6277d5 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Server/TSimpleAsyncServer.cs @@ -0,0 +1,230 @@ +// 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. + +using System; +using System.Threading; +using Thrift.Protocol; +using Thrift.Transport; +using Thrift.Processor; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + + +namespace Thrift.Server +{ + + // ReSharper disable once InconsistentNaming + public class TSimpleAsyncServer : TServer + { + private volatile bool stop = false; + + private CancellationToken ServerCancellationToken; + + public TSimpleAsyncServer(ITProcessorFactory itProcessorFactory, + TServerTransport serverTransport, + TTransportFactory inputTransportFactory, + TTransportFactory outputTransportFactory, + TProtocolFactory inputProtocolFactory, + TProtocolFactory outputProtocolFactory, + ILogger logger) + : base(itProcessorFactory, + serverTransport, + inputTransportFactory, + outputTransportFactory, + inputProtocolFactory, + outputProtocolFactory, + logger) + { + } + + public TSimpleAsyncServer(ITProcessorFactory itProcessorFactory, + TServerTransport serverTransport, + TTransportFactory inputTransportFactory, + TTransportFactory outputTransportFactory, + TProtocolFactory inputProtocolFactory, + TProtocolFactory outputProtocolFactory, + ILoggerFactory loggerFactory) + : this(itProcessorFactory, + serverTransport, + inputTransportFactory, + outputTransportFactory, + inputProtocolFactory, + outputProtocolFactory, + loggerFactory.CreateLogger()) + { + } + + public TSimpleAsyncServer(ITAsyncProcessor processor, + TServerTransport serverTransport, + TProtocolFactory inputProtocolFactory, + TProtocolFactory outputProtocolFactory, + ILoggerFactory loggerFactory) + : this(new TSingletonProcessorFactory(processor), + serverTransport, + null, // defaults to TTransportFactory() + null, // defaults to TTransportFactory() + inputProtocolFactory, + outputProtocolFactory, + loggerFactory.CreateLogger(nameof(TSimpleAsyncServer))) + { + } + + public override async Task ServeAsync(CancellationToken cancellationToken) + { + ServerCancellationToken = cancellationToken; + try + { + try + { + ServerTransport.Listen(); + } + catch (TTransportException ttx) + { + LogError("Error, could not listen on ServerTransport: " + ttx); + return; + } + + //Fire the preServe server event when server is up but before any client connections + if (ServerEventHandler != null) + await ServerEventHandler.PreServeAsync(cancellationToken); + + while (!(stop || ServerCancellationToken.IsCancellationRequested)) + { + try + { + using (TTransport client = await ServerTransport.AcceptAsync(cancellationToken)) + { + await ExecuteAsync(client); + } + } + catch (TaskCanceledException) + { + stop = true; + } + catch (TTransportException ttx) + { + if (!stop || ttx.Type != TTransportException.ExceptionType.Interrupted) + { + LogError(ttx.ToString()); + } + + } + } + + if (stop) + { + try + { + ServerTransport.Close(); + } + catch (TTransportException ttx) + { + LogError("TServerTransport failed on close: " + ttx.Message); + } + stop = false; + } + + } + finally + { + ServerCancellationToken = default; + } + } + + /// + /// Loops on processing a client forever + /// client will be a TTransport instance + /// + /// + private async Task ExecuteAsync(TTransport client) + { + var cancellationToken = ServerCancellationToken; + + var processor = ProcessorFactory.GetAsyncProcessor(client, this); + + TTransport inputTransport = null; + TTransport outputTransport = null; + TProtocol inputProtocol = null; + TProtocol outputProtocol = null; + object connectionContext = null; + try + { + try + { + inputTransport = InputTransportFactory.GetTransport(client); + outputTransport = OutputTransportFactory.GetTransport(client); + inputProtocol = InputProtocolFactory.GetProtocol(inputTransport); + outputProtocol = OutputProtocolFactory.GetProtocol(outputTransport); + + //Recover event handler (if any) and fire createContext server event when a client connects + if (ServerEventHandler != null) + connectionContext = await ServerEventHandler.CreateContextAsync(inputProtocol, outputProtocol, cancellationToken); + + //Process client requests until client disconnects + while (!(stop || cancellationToken.IsCancellationRequested)) + { + if (!await inputTransport.PeekAsync(cancellationToken)) + break; + + //Fire processContext server event + //N.B. This is the pattern implemented in C++ and the event fires provisionally. + //That is to say it may be many minutes between the event firing and the client request + //actually arriving or the client may hang up without ever makeing a request. + if (ServerEventHandler != null) + await ServerEventHandler.ProcessContextAsync(connectionContext, inputTransport, cancellationToken); + + //Process client request (blocks until transport is readable) + if (!await processor.ProcessAsync(inputProtocol, outputProtocol, cancellationToken)) + break; + } + } + catch (TTransportException) + { + //Usually a client disconnect, expected + } + catch (Exception x) + { + //Unexpected + LogError("Error: " + x); + } + + //Fire deleteContext server event after client disconnects + if (ServerEventHandler != null) + await ServerEventHandler.DeleteContextAsync(connectionContext, inputProtocol, outputProtocol, cancellationToken); + + } + finally + { + //Close transports + inputTransport?.Close(); + outputTransport?.Close(); + + // disposable stuff should be disposed + inputProtocol?.Dispose(); + outputProtocol?.Dispose(); + inputTransport?.Dispose(); + outputTransport?.Dispose(); + } + } + + public override void Stop() + { + stop = true; + ServerTransport?.Close(); + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Server/TThreadPoolAsyncServer.cs b/lib/csharp/thrift_netstd/Thrift/Server/TThreadPoolAsyncServer.cs new file mode 100644 index 000000000..efa1698da --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Server/TThreadPoolAsyncServer.cs @@ -0,0 +1,299 @@ +/* + * 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. + */ + +using System; +using System.Threading; +using Thrift.Protocol; +using Thrift.Transport; +using Thrift.Processor; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + + +namespace Thrift.Server +{ + /// + /// Server that uses C# built-in ThreadPool to spawn threads when handling requests. + /// + public class TThreadPoolAsyncServer : TServer + { + private const int DEFAULT_MIN_THREADS = -1; // use .NET ThreadPool defaults + private const int DEFAULT_MAX_THREADS = -1; // use .NET ThreadPool defaults + private volatile bool stop = false; + + private CancellationToken ServerCancellationToken; + + public struct Configuration + { + public int MinWorkerThreads; + public int MaxWorkerThreads; + public int MinIOThreads; + public int MaxIOThreads; + + public Configuration(int min = DEFAULT_MIN_THREADS, int max = DEFAULT_MAX_THREADS) + { + MinWorkerThreads = min; + MaxWorkerThreads = max; + MinIOThreads = min; + MaxIOThreads = max; + } + + public Configuration(int minWork, int maxWork, int minIO, int maxIO) + { + MinWorkerThreads = minWork; + MaxWorkerThreads = maxWork; + MinIOThreads = minIO; + MaxIOThreads = maxIO; + } + } + + public TThreadPoolAsyncServer(ITAsyncProcessor processor, TServerTransport serverTransport, ILogger logger = null) + : this(new TSingletonProcessorFactory(processor), serverTransport, + null, null, // defaults to TTransportFactory() + new TBinaryProtocol.Factory(), new TBinaryProtocol.Factory(), + new Configuration(), logger) + { + } + + public TThreadPoolAsyncServer(ITAsyncProcessor processor, + TServerTransport serverTransport, + TTransportFactory transportFactory, + TProtocolFactory protocolFactory) + : this(new TSingletonProcessorFactory(processor), serverTransport, + transportFactory, transportFactory, + protocolFactory, protocolFactory, + new Configuration()) + { + } + + public TThreadPoolAsyncServer(ITProcessorFactory processorFactory, + TServerTransport serverTransport, + TTransportFactory transportFactory, + TProtocolFactory protocolFactory) + : this(processorFactory, serverTransport, + transportFactory, transportFactory, + protocolFactory, protocolFactory, + new Configuration()) + { + } + + public TThreadPoolAsyncServer(ITProcessorFactory processorFactory, + TServerTransport serverTransport, + TTransportFactory inputTransportFactory, + TTransportFactory outputTransportFactory, + TProtocolFactory inputProtocolFactory, + TProtocolFactory outputProtocolFactory, + int minThreadPoolThreads, int maxThreadPoolThreads, ILogger logger = null) + : this(processorFactory, serverTransport, inputTransportFactory, outputTransportFactory, + inputProtocolFactory, outputProtocolFactory, + new Configuration(minThreadPoolThreads, maxThreadPoolThreads), + logger) + { + } + + public TThreadPoolAsyncServer(ITProcessorFactory processorFactory, + TServerTransport serverTransport, + TTransportFactory inputTransportFactory, + TTransportFactory outputTransportFactory, + TProtocolFactory inputProtocolFactory, + TProtocolFactory outputProtocolFactory, + Configuration threadConfig, + ILogger logger = null) + : base(processorFactory, serverTransport, inputTransportFactory, outputTransportFactory, + inputProtocolFactory, outputProtocolFactory, logger) + { + lock (typeof(TThreadPoolAsyncServer)) + { + if ((threadConfig.MaxWorkerThreads > 0) || (threadConfig.MaxIOThreads > 0)) + { + ThreadPool.GetMaxThreads(out int work, out int comm); + if (threadConfig.MaxWorkerThreads > 0) + work = threadConfig.MaxWorkerThreads; + if (threadConfig.MaxIOThreads > 0) + comm = threadConfig.MaxIOThreads; + if (!ThreadPool.SetMaxThreads(work, comm)) + throw new Exception("Error: could not SetMaxThreads in ThreadPool"); + } + + if ((threadConfig.MinWorkerThreads > 0) || (threadConfig.MinIOThreads > 0)) + { + ThreadPool.GetMinThreads(out int work, out int comm); + if (threadConfig.MinWorkerThreads > 0) + work = threadConfig.MinWorkerThreads; + if (threadConfig.MinIOThreads > 0) + comm = threadConfig.MinIOThreads; + if (!ThreadPool.SetMinThreads(work, comm)) + throw new Exception("Error: could not SetMinThreads in ThreadPool"); + } + } + } + + + /// + /// Use new ThreadPool thread for each new client connection. + /// + public override async Task ServeAsync(CancellationToken cancellationToken) + { + ServerCancellationToken = cancellationToken; + try + { + try + { + ServerTransport.Listen(); + } + catch (TTransportException ttx) + { + LogError("Error, could not listen on ServerTransport: " + ttx); + return; + } + + //Fire the preServe server event when server is up but before any client connections + if (ServerEventHandler != null) + await ServerEventHandler.PreServeAsync(cancellationToken); + + while (!(stop || ServerCancellationToken.IsCancellationRequested)) + { + try + { + TTransport client = await ServerTransport.AcceptAsync(cancellationToken); + _ = Task.Run(async () => await ExecuteAsync(client), cancellationToken); // intentionally ignoring retval + } + catch (TaskCanceledException) + { + stop = true; + } + catch (TTransportException ttx) + { + if (!stop || ttx.Type != TTransportException.ExceptionType.Interrupted) + { + LogError(ttx.ToString()); + } + + } + } + + if (stop) + { + try + { + ServerTransport.Close(); + } + catch (TTransportException ttx) + { + LogError("TServerTransport failed on close: " + ttx.Message); + } + stop = false; + } + + } + finally + { + ServerCancellationToken = default; + } + } + + /// + /// Loops on processing a client forever + /// client will be a TTransport instance + /// + /// + private async Task ExecuteAsync(TTransport client) + { + var cancellationToken = ServerCancellationToken; + + using (client) + { + ITAsyncProcessor processor = ProcessorFactory.GetAsyncProcessor(client, this); + TTransport inputTransport = null; + TTransport outputTransport = null; + TProtocol inputProtocol = null; + TProtocol outputProtocol = null; + object connectionContext = null; + try + { + try + { + inputTransport = InputTransportFactory.GetTransport(client); + outputTransport = OutputTransportFactory.GetTransport(client); + inputProtocol = InputProtocolFactory.GetProtocol(inputTransport); + outputProtocol = OutputProtocolFactory.GetProtocol(outputTransport); + + //Recover event handler (if any) and fire createContext server event when a client connects + if (ServerEventHandler != null) + connectionContext = await ServerEventHandler.CreateContextAsync(inputProtocol, outputProtocol, cancellationToken); + + //Process client requests until client disconnects + while (!(stop || cancellationToken.IsCancellationRequested)) + { + if (!await inputTransport.PeekAsync(cancellationToken)) + break; + + //Fire processContext server event + //N.B. This is the pattern implemented in C++ and the event fires provisionally. + //That is to say it may be many minutes between the event firing and the client request + //actually arriving or the client may hang up without ever makeing a request. + if (ServerEventHandler != null) + await ServerEventHandler.ProcessContextAsync(connectionContext, inputTransport, cancellationToken); + + //Process client request (blocks until transport is readable) + if (!await processor.ProcessAsync(inputProtocol, outputProtocol, cancellationToken)) + break; + } + } + catch (TTransportException) + { + //Usually a client disconnect, expected + } + catch (Exception x) + { + //Unexpected + LogError("Error: " + x); + } + + //Fire deleteContext server event after client disconnects + if (ServerEventHandler != null) + await ServerEventHandler.DeleteContextAsync(connectionContext, inputProtocol, outputProtocol, cancellationToken); + + } + finally + { + //Close transports + inputTransport?.Close(); + outputTransport?.Close(); + + // disposable stuff should be disposed + inputProtocol?.Dispose(); + outputProtocol?.Dispose(); + inputTransport?.Dispose(); + outputTransport?.Dispose(); + } + } + } + + public override void Stop() + { + stop = true; + ServerTransport?.Close(); + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/TApplicationException.cs b/lib/csharp/thrift_netstd/Thrift/TApplicationException.cs new file mode 100644 index 000000000..514db9a08 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/TApplicationException.cs @@ -0,0 +1,147 @@ +// 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. + +using System.Threading; +using System.Threading.Tasks; +using Thrift.Protocol; +using Thrift.Protocol.Entities; +using Thrift.Protocol.Utilities; + +namespace Thrift +{ + // ReSharper disable once InconsistentNaming + public class TApplicationException : TException + { + public enum ExceptionType + { + Unknown, + UnknownMethod, + InvalidMessageType, + WrongMethodName, + BadSequenceId, + MissingResult, + InternalError, + ProtocolError, + InvalidTransform, + InvalidProtocol, + UnsupportedClientType + } + + private const int MessageTypeFieldId = 1; + private const int ExTypeFieldId = 2; + + public ExceptionType Type { get; private set; } + + public TApplicationException() + { + } + + public TApplicationException(ExceptionType type) + { + Type = type; + } + + public TApplicationException(ExceptionType type, string message) + : base(message, null) // TApplicationException is serializable, but we never serialize InnerException + { + Type = type; + } + + public static async ValueTask ReadAsync(TProtocol inputProtocol, CancellationToken cancellationToken) + { + string message = null; + var type = ExceptionType.Unknown; + + await inputProtocol.ReadStructBeginAsync(cancellationToken); + while (true) + { + var field = await inputProtocol.ReadFieldBeginAsync(cancellationToken); + if (field.Type == TType.Stop) + { + break; + } + + switch (field.ID) + { + case MessageTypeFieldId: + if (field.Type == TType.String) + { + message = await inputProtocol.ReadStringAsync(cancellationToken); + } + else + { + await TProtocolUtil.SkipAsync(inputProtocol, field.Type, cancellationToken); + } + break; + case ExTypeFieldId: + if (field.Type == TType.I32) + { + type = (ExceptionType) await inputProtocol.ReadI32Async(cancellationToken); + } + else + { + await TProtocolUtil.SkipAsync(inputProtocol, field.Type, cancellationToken); + } + break; + default: + await TProtocolUtil.SkipAsync(inputProtocol, field.Type, cancellationToken); + break; + } + + await inputProtocol.ReadFieldEndAsync(cancellationToken); + } + + await inputProtocol.ReadStructEndAsync(cancellationToken); + + return new TApplicationException(type, message); + } + + public async Task WriteAsync(TProtocol outputProtocol, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + const string messageTypeFieldName = "message"; + const string exTypeFieldName = "exType"; + const string structApplicationExceptionName = "TApplicationException"; + + var struc = new TStruct(structApplicationExceptionName); + var field = new TField(); + + await outputProtocol.WriteStructBeginAsync(struc, cancellationToken); + + if (!string.IsNullOrEmpty(Message)) + { + field.Name = messageTypeFieldName; + field.Type = TType.String; + field.ID = MessageTypeFieldId; + await outputProtocol.WriteFieldBeginAsync(field, cancellationToken); + await outputProtocol.WriteStringAsync(Message, cancellationToken); + await outputProtocol.WriteFieldEndAsync(cancellationToken); + } + + field.Name = exTypeFieldName; + field.Type = TType.I32; + field.ID = ExTypeFieldId; + + await outputProtocol.WriteFieldBeginAsync(field, cancellationToken); + await outputProtocol.WriteI32Async((int) Type, cancellationToken); + await outputProtocol.WriteFieldEndAsync(cancellationToken); + await outputProtocol.WriteFieldStopAsync(cancellationToken); + await outputProtocol.WriteStructEndAsync(cancellationToken); + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/TBaseClient.cs b/lib/csharp/thrift_netstd/Thrift/TBaseClient.cs new file mode 100644 index 000000000..aefb54dd5 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/TBaseClient.cs @@ -0,0 +1,91 @@ +// 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. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Thrift.Protocol; + +namespace Thrift +{ + // ReSharper disable once InconsistentNaming + /// + /// TBaseClient. + /// Base client for generated clients. + /// Do not change this class without checking generated code (namings, etc.) + /// + public abstract class TBaseClient + { + private readonly TProtocol _inputProtocol; + private readonly TProtocol _outputProtocol; + private bool _isDisposed; + private int _seqId; + public readonly Guid ClientId = Guid.NewGuid(); + + protected TBaseClient(TProtocol inputProtocol, TProtocol outputProtocol) + { + _inputProtocol = inputProtocol ?? throw new ArgumentNullException(nameof(inputProtocol)); + _outputProtocol = outputProtocol ?? throw new ArgumentNullException(nameof(outputProtocol)); + } + + public TProtocol InputProtocol => _inputProtocol; + + public TProtocol OutputProtocol => _outputProtocol; + + public int SeqId + { + get { return ++_seqId; } + } + + public virtual async Task OpenTransportAsync() + { + await OpenTransportAsync(CancellationToken.None); + } + + public virtual async Task OpenTransportAsync(CancellationToken cancellationToken) + { + if (!_inputProtocol.Transport.IsOpen) + { + await _inputProtocol.Transport.OpenAsync(cancellationToken); + } + + if (!_outputProtocol.Transport.IsOpen) + { + await _outputProtocol.Transport.OpenAsync(cancellationToken); + } + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (!_isDisposed) + { + if (disposing) + { + _inputProtocol?.Dispose(); + _outputProtocol?.Dispose(); + } + } + + _isDisposed = true; + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/TConfiguration.cs b/lib/csharp/thrift_netstd/Thrift/TConfiguration.cs new file mode 100644 index 000000000..eff78e96d --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/TConfiguration.cs @@ -0,0 +1,36 @@ +// 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. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Thrift +{ + public class TConfiguration + { + public const int DEFAULT_MAX_MESSAGE_SIZE = 100 * 1024 * 1024; + public const int DEFAULT_MAX_FRAME_SIZE = 16384000; // this value is used consistently across all Thrift libraries + public const int DEFAULT_RECURSION_DEPTH = 64; + + public int MaxMessageSize { get; set; } = DEFAULT_MAX_MESSAGE_SIZE; + public int MaxFrameSize { get; set; } = DEFAULT_MAX_FRAME_SIZE; + public int RecursionLimit { get; set; } = DEFAULT_RECURSION_DEPTH; + + // TODO(JensG): add connection and i/o timeouts + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/TException.cs b/lib/csharp/thrift_netstd/Thrift/TException.cs new file mode 100644 index 000000000..43e70549b --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/TException.cs @@ -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. + +using System; + +namespace Thrift +{ + // ReSharper disable once InconsistentNaming + public class TException : Exception + { + public TException() + { + } + + public TException(string message, Exception inner) + : base(message, inner) + { + } + } +} \ No newline at end of file diff --git a/lib/csharp/thrift_netstd/Thrift/Thrift.csproj b/lib/csharp/thrift_netstd/Thrift/Thrift.csproj new file mode 100644 index 000000000..e1c151e1b --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Thrift.csproj @@ -0,0 +1,80 @@ + + + + + netstandard2.1;netstandard2.0;net5.0 + Thrift + ApacheThrift + true + true + false + false + false + false + false + false + false + false + true + + + + true + true + thrift.snk + false + Apache Thrift 0.17.0 + 0.17.0.0 + false + http://thrift.apache.org/ + Apache Thrift Developers + false + Apache-2.0 + C# .NET Core bindings for the Apache Thrift RPC system + + Apache Thrift RPC + https://github.com/apache/thrift/blob/0.17.0/CHANGES.md + Copyright 2022 The Apache Software Foundation + + + + + + + + + + + + + + + + + + + + + + + $(IntermediateOutputPath)$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension) + + + diff --git a/lib/csharp/thrift_netstd/Thrift/Transport/Client/THttpTransport.cs b/lib/csharp/thrift_netstd/Thrift/Transport/Client/THttpTransport.cs new file mode 100644 index 000000000..60ed59c67 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Transport/Client/THttpTransport.cs @@ -0,0 +1,295 @@ +// 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. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; + + +namespace Thrift.Transport.Client +{ + // ReSharper disable once InconsistentNaming + public class THttpTransport : TEndpointTransport + { + private readonly X509Certificate[] _certificates; + private readonly Uri _uri; + + private int _connectTimeout = 30000; // Timeouts in milliseconds + private HttpClient _httpClient; + private Stream _inputStream; + private MemoryStream _outputStream = new MemoryStream(); + private bool _isDisposed; + + public THttpTransport(Uri uri, TConfiguration config, IDictionary customRequestHeaders = null, string userAgent = null) + : this(uri, config, Enumerable.Empty(), customRequestHeaders, userAgent) + { + } + + public THttpTransport(Uri uri, TConfiguration config, IEnumerable certificates, + IDictionary customRequestHeaders, string userAgent = null) + : base(config) + { + _uri = uri; + _certificates = (certificates ?? Enumerable.Empty()).ToArray(); + + if (!string.IsNullOrEmpty(userAgent)) + UserAgent = userAgent; + + // due to current bug with performance of Dispose in netcore https://github.com/dotnet/corefx/issues/8809 + // this can be switched to default way (create client->use->dispose per flush) later + _httpClient = CreateClient(customRequestHeaders); + ConfigureClient(_httpClient); + } + + /// + /// Constructor that takes a HttpClient instance to support using IHttpClientFactory. + /// + /// As the HttpMessageHandler of the client must be configured at the time of creation, it + /// is assumed that the consumer has already added any certificates and configured decompression methods. The + /// consumer can use the CreateHttpClientHandler method to get a handler with these set. + /// Client configured with the desired message handler, user agent, and URI if not + /// specified in the uri parameter. A default user agent will be used if not set. + /// Thrift configuration object + /// Optional URI to use for requests, if not specified the base address of httpClient + /// is used. + public THttpTransport(HttpClient httpClient, TConfiguration config, Uri uri = null) + : base(config) + { + _httpClient = httpClient; + + _uri = uri ?? httpClient.BaseAddress; + httpClient.BaseAddress = _uri; + + var userAgent = _httpClient.DefaultRequestHeaders.UserAgent.ToString(); + if (!string.IsNullOrEmpty(userAgent)) + UserAgent = userAgent; + + ConfigureClient(_httpClient); + } + + // According to RFC 2616 section 3.8, the "User-Agent" header may not carry a version number + public readonly string UserAgent = "Thrift netstd THttpClient"; + + public int ConnectTimeout + { + set + { + _connectTimeout = value; + if(_httpClient != null) + _httpClient.Timeout = TimeSpan.FromMilliseconds(_connectTimeout); + } + get + { + if (_httpClient == null) + return _connectTimeout; + return (int)_httpClient.Timeout.TotalMilliseconds; + } + } + + public override bool IsOpen => true; + + public HttpRequestHeaders RequestHeaders => _httpClient.DefaultRequestHeaders; + + public MediaTypeHeaderValue ContentType { get; set; } + + public override Task OpenAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override void Close() + { + if (_inputStream != null) + { + _inputStream.Dispose(); + _inputStream = null; + } + + if (_outputStream != null) + { + _outputStream.Dispose(); + _outputStream = null; + } + + if (_httpClient != null) + { + _httpClient.Dispose(); + _httpClient = null; + } + } + + public override async ValueTask ReadAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (_inputStream == null) + throw new TTransportException(TTransportException.ExceptionType.NotOpen, "No request has been sent"); + + CheckReadBytesAvailable(length); + + try + { +#if NETSTANDARD2_0 + var ret = await _inputStream.ReadAsync(buffer, offset, length, cancellationToken); +#else + var ret = await _inputStream.ReadAsync(new Memory(buffer, offset, length), cancellationToken); +#endif + if (ret == -1) + { + throw new TTransportException(TTransportException.ExceptionType.EndOfFile, "No more data available"); + } + + CountConsumedMessageBytes(ret); + return ret; + } + catch (IOException iox) + { + throw new TTransportException(TTransportException.ExceptionType.Unknown, iox.ToString()); + } + } + + public override async Task WriteAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + +#if NETSTANDARD2_0 + await _outputStream.WriteAsync(buffer, offset, length, cancellationToken); +#else + await _outputStream.WriteAsync(buffer.AsMemory(offset, length), cancellationToken); +#endif + } + + /// + /// Get a client handler configured with recommended properties to use with the HttpClient constructor + /// and an IHttpClientFactory. + /// + /// An optional array of client certificates to associate with the handler. + /// + /// A client handler with deflate and gZip compression-decompression algorithms and any client + /// certificates passed in via certificates. + /// + public virtual HttpClientHandler CreateHttpClientHandler(X509Certificate[] certificates = null) + { + var handler = new HttpClientHandler(); + if (certificates != null) + handler.ClientCertificates.AddRange(certificates); + handler.AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip; + return handler; + } + + private HttpClient CreateClient(IDictionary customRequestHeaders) + { + var handler = CreateHttpClientHandler(_certificates); + var httpClient = new HttpClient(handler); + + + if (customRequestHeaders != null) + { + foreach (var item in customRequestHeaders) + { + httpClient.DefaultRequestHeaders.Add(item.Key, item.Value); + } + } + + return httpClient; + } + + private void ConfigureClient(HttpClient httpClient) + { + if (_connectTimeout > 0) + { + httpClient.Timeout = TimeSpan.FromMilliseconds(_connectTimeout); + } + + httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-thrift")); + + // Clear any user agent values to avoid drift with the field value + httpClient.DefaultRequestHeaders.UserAgent.Clear(); + httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd(UserAgent); + + httpClient.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("deflate")); + httpClient.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip")); + } + + public override async Task FlushAsync(CancellationToken cancellationToken) + { + try + { + _outputStream.Seek(0, SeekOrigin.Begin); + + using (var contentStream = new StreamContent(_outputStream)) + { + contentStream.Headers.ContentType = ContentType ?? new MediaTypeHeaderValue(@"application/x-thrift"); + + var response = (await _httpClient.PostAsync(_uri, contentStream, cancellationToken)).EnsureSuccessStatusCode(); + + _inputStream?.Dispose(); +#if NETSTANDARD2_0 || NETSTANDARD2_1 + _inputStream = await response.Content.ReadAsStreamAsync(); +#else + _inputStream = await response.Content.ReadAsStreamAsync(cancellationToken); +#endif + if (_inputStream.CanSeek) + { + _inputStream.Seek(0, SeekOrigin.Begin); + } + } + } + catch (IOException iox) + { + throw new TTransportException(TTransportException.ExceptionType.Unknown, iox.ToString()); + } + catch (HttpRequestException wx) + { + throw new TTransportException(TTransportException.ExceptionType.Unknown, + "Couldn't connect to server: " + wx); + } + catch (Exception ex) + { + throw new TTransportException(TTransportException.ExceptionType.Unknown, ex.Message); + } + finally + { + _outputStream = new MemoryStream(); + ResetConsumedMessageSize(); + } + } + + + // IDisposable + protected override void Dispose(bool disposing) + { + if (!_isDisposed) + { + if (disposing) + { + _inputStream?.Dispose(); + _outputStream?.Dispose(); + _httpClient?.Dispose(); + } + } + _isDisposed = true; + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Transport/Client/TMemoryBufferTransport.cs b/lib/csharp/thrift_netstd/Thrift/Transport/Client/TMemoryBufferTransport.cs new file mode 100644 index 000000000..5773d30cb --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Transport/Client/TMemoryBufferTransport.cs @@ -0,0 +1,178 @@ +// 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. + +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + + +namespace Thrift.Transport.Client +{ + // ReSharper disable once InconsistentNaming + public class TMemoryBufferTransport : TEndpointTransport + { + private bool IsDisposed; + private byte[] Bytes; + private int _bytesUsed; + + public TMemoryBufferTransport(TConfiguration config, int initialCapacity = 2048) + : base(config) + { + Bytes = new byte[initialCapacity]; + } + + public TMemoryBufferTransport(byte[] buf, TConfiguration config) + :base(config) + { + Bytes = (byte[])buf.Clone(); + _bytesUsed = Bytes.Length; + UpdateKnownMessageSize(_bytesUsed); + } + + public int Position { get; set; } + + public int Capacity + { + get + { + Debug.Assert(_bytesUsed <= Bytes.Length); + return Bytes.Length; + } + set + { + Array.Resize(ref Bytes, value); + _bytesUsed = value; + } + } + + public int Length + { + get { + Debug.Assert(_bytesUsed <= Bytes.Length); + return _bytesUsed; + } + set { + if ((Bytes.Length < value) || (Bytes.Length > (10 * value))) + Array.Resize(ref Bytes, Math.Max(2048, (int)(value * 1.25))); + _bytesUsed = value; + } + } + + public void SetLength(int value) + { + Length = value; + Position = Math.Min(Position, value); + } + + public override bool IsOpen => true; + + public override Task OpenAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override void Close() + { + /* do nothing */ + } + + public void Seek(int delta, SeekOrigin origin) + { + int newPos; + switch (origin) + { + case SeekOrigin.Begin: + newPos = delta; + break; + case SeekOrigin.Current: + newPos = Position + delta; + break; + case SeekOrigin.End: + newPos = _bytesUsed + delta; + break; + default: + throw new ArgumentException("Unrecognized value",nameof(origin)); + } + + if ((0 > newPos) || (newPos > _bytesUsed)) + throw new ArgumentException("Cannot seek outside of the valid range",nameof(origin)); + Position = newPos; + + ResetConsumedMessageSize(); + CountConsumedMessageBytes(Position); + } + + public override ValueTask ReadAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken) + { + var count = Math.Min(Length - Position, length); + Buffer.BlockCopy(Bytes, Position, buffer, offset, count); + Position += count; + CountConsumedMessageBytes(count); + return new ValueTask(count); + } + + public override Task WriteAsync(byte[] buffer, CancellationToken cancellationToken) + { + return WriteAsync(buffer, 0, buffer.Length, cancellationToken); + } + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + var free = Length - Position; + Length = Length + count - free; + Buffer.BlockCopy(buffer, offset, Bytes, Position, count); + Position += count; + return Task.CompletedTask; + } + + public override Task FlushAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + ResetConsumedMessageSize(); + return Task.CompletedTask; + } + + public byte[] GetBuffer() + { + var retval = new byte[Length]; + Buffer.BlockCopy(Bytes, 0, retval, 0, Length); + return retval; + } + + internal bool TryGetBuffer(out ArraySegment bufSegment) + { + bufSegment = new ArraySegment(Bytes, 0, _bytesUsed); + return true; + } + + // IDisposable + protected override void Dispose(bool disposing) + { + if (!IsDisposed) + { + if (disposing) + { + // nothing to do + } + } + IsDisposed = true; + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Transport/Client/TNamedPipeTransport.cs b/lib/csharp/thrift_netstd/Thrift/Transport/Client/TNamedPipeTransport.cs new file mode 100644 index 000000000..c57db9d60 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Transport/Client/TNamedPipeTransport.cs @@ -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. + +using System; +using System.IO.Pipes; +using System.Security.Principal; +using System.Threading; +using System.Threading.Tasks; + +namespace Thrift.Transport.Client +{ + // ReSharper disable once InconsistentNaming + public class TNamedPipeTransport : TEndpointTransport + { + private NamedPipeClientStream PipeStream; + private readonly int ConnectTimeout; + private const int DEFAULT_CONNECT_TIMEOUT = 60 * 1000; // Timeout.Infinite is not a good default + + public TNamedPipeTransport(string pipe, TConfiguration config, int timeout = DEFAULT_CONNECT_TIMEOUT) + : this(".", pipe, config, timeout) + { + } + + public TNamedPipeTransport(string server, string pipe, TConfiguration config, int timeout = DEFAULT_CONNECT_TIMEOUT) + : base(config) + { + var serverName = string.IsNullOrWhiteSpace(server) ? server : "."; + ConnectTimeout = (timeout > 0) ? timeout : DEFAULT_CONNECT_TIMEOUT; + + PipeStream = new NamedPipeClientStream(serverName, pipe, PipeDirection.InOut, PipeOptions.None, TokenImpersonationLevel.Anonymous); + } + + public override bool IsOpen => PipeStream != null && PipeStream.IsConnected; + + public override async Task OpenAsync(CancellationToken cancellationToken) + { + if (IsOpen) + { + throw new TTransportException(TTransportException.ExceptionType.AlreadyOpen); + } + + await PipeStream.ConnectAsync( ConnectTimeout, cancellationToken); + ResetConsumedMessageSize(); + } + + public override void Close() + { + if (PipeStream != null) + { + PipeStream.Dispose(); + PipeStream = null; + } + } + + public override async ValueTask ReadAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken) + { + if (PipeStream == null) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen); + } + + CheckReadBytesAvailable(length); +#if NETSTANDARD2_0 + var numRead = await PipeStream.ReadAsync(buffer, offset, length, cancellationToken); +#else + var numRead = await PipeStream.ReadAsync(new Memory(buffer, offset, length), cancellationToken); +#endif + CountConsumedMessageBytes(numRead); + return numRead; + } + + public override async Task WriteAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken) + { + if (PipeStream == null) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen); + } + + // if necessary, send the data in chunks + // there's a system limit around 0x10000 bytes that we hit otherwise + // MSDN: "Pipe write operations across a network are limited to 65,535 bytes per write. For more information regarding pipes, see the Remarks section." + var nBytes = Math.Min(15 * 4096, length); // 16 would exceed the limit + while (nBytes > 0) + { +#if NETSTANDARD2_0 + await PipeStream.WriteAsync(buffer, offset, nBytes, cancellationToken); +#else + await PipeStream.WriteAsync(buffer.AsMemory(offset, nBytes), cancellationToken); +#endif + offset += nBytes; + length -= nBytes; + nBytes = Math.Min(nBytes, length); + } + } + + public override Task FlushAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + ResetConsumedMessageSize(); + return Task.CompletedTask; + } + + + protected override void Dispose(bool disposing) + { + if(disposing) + { + PipeStream?.Dispose(); + } + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Transport/Client/TSocketTransport.cs b/lib/csharp/thrift_netstd/Thrift/Transport/Client/TSocketTransport.cs new file mode 100644 index 000000000..5e3245b8c --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Transport/Client/TSocketTransport.cs @@ -0,0 +1,156 @@ +// 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. + +using System; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Thrift.Transport.Client +{ + // ReSharper disable once InconsistentNaming + public class TSocketTransport : TStreamTransport + { + private bool _isDisposed; + + + public TSocketTransport(TcpClient client, TConfiguration config) + : base(config) + { + TcpClient = client ?? throw new ArgumentNullException(nameof(client)); + SetInputOutputStream(); + } + + public TSocketTransport(IPAddress host, int port, TConfiguration config, int timeout = 0) + : base(config) + { + Host = host; + Port = port; + + TcpClient = new TcpClient(); + TcpClient.ReceiveTimeout = TcpClient.SendTimeout = timeout; + TcpClient.Client.NoDelay = true; + SetInputOutputStream(); + } + + public TSocketTransport(string host, int port, TConfiguration config, int timeout = 0) + : base(config) + { + try + { + var entry = Dns.GetHostEntry(host); + if (entry.AddressList.Length == 0) + throw new TTransportException(TTransportException.ExceptionType.Unknown, "unable to resolve host name"); + + Host = entry.AddressList[0]; + Port = port; + + TcpClient = new TcpClient(host, port); + TcpClient.ReceiveTimeout = TcpClient.SendTimeout = timeout; + TcpClient.Client.NoDelay = true; + SetInputOutputStream(); + } + catch (SocketException e) + { + throw new TTransportException(TTransportException.ExceptionType.Unknown, e.Message, e); + } + } + + private void SetInputOutputStream() + { + if (IsOpen) + { + InputStream = TcpClient.GetStream(); + OutputStream = TcpClient.GetStream(); + } + } + + public TcpClient TcpClient { get; private set; } + public IPAddress Host { get; } + public int Port { get; } + + public int Timeout + { + set + { + if (TcpClient != null) + { + TcpClient.ReceiveTimeout = TcpClient.SendTimeout = value; + } + } + } + + public override bool IsOpen + { + get + { + return (TcpClient != null) && TcpClient.Connected; + } + } + + public override async Task OpenAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (IsOpen) + { + throw new TTransportException(TTransportException.ExceptionType.AlreadyOpen, "Socket already connected"); + } + + if (Port <= 0) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot open without port"); + } + + if (TcpClient == null) + { + throw new InvalidOperationException("Invalid or not initialized tcp client"); + } + + await TcpClient.ConnectAsync(Host, Port); + SetInputOutputStream(); + } + + public override void Close() + { + base.Close(); + + if (TcpClient != null) + { + TcpClient.Dispose(); + TcpClient = null; + } + } + + // IDisposable + protected override void Dispose(bool disposing) + { + if (!_isDisposed) + { + if (disposing) + { + TcpClient?.Dispose(); + + base.Dispose(disposing); + } + } + _isDisposed = true; + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Transport/Client/TStreamTransport.cs b/lib/csharp/thrift_netstd/Thrift/Transport/Client/TStreamTransport.cs new file mode 100644 index 000000000..053a37ba1 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Transport/Client/TStreamTransport.cs @@ -0,0 +1,120 @@ +// 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. +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Thrift.Transport.Client +{ + // ReSharper disable once InconsistentNaming + public class TStreamTransport : TEndpointTransport + { + private bool _isDisposed; + + protected TStreamTransport(TConfiguration config) + :base(config) + { + } + + public TStreamTransport(Stream inputStream, Stream outputStream, TConfiguration config) + : base(config) + { + InputStream = inputStream; + OutputStream = outputStream; + } + + protected Stream OutputStream { get; set; } + + private Stream _InputStream = null; + protected Stream InputStream { + get => _InputStream; + set { + _InputStream = value; + ResetConsumedMessageSize(); + } + } + + public override bool IsOpen => true; + + public override Task OpenAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override void Close() + { + if (InputStream != null) + { + InputStream.Dispose(); + InputStream = null; + } + + if (OutputStream != null) + { + OutputStream.Dispose(); + OutputStream = null; + } + } + + public override async ValueTask ReadAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken) + { + if (InputStream == null) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen, + "Cannot read from null inputstream"); + } + + // The ReadAsync method should not be used since it does not check the ReceiveTimeout property. + return await Task.Run( () => InputStream.Read( buffer, offset, length ), cancellationToken ); + } + + public override async Task WriteAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken) + { + if (OutputStream == null) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen, + "Cannot write to null outputstream"); + } + + // The WriteAsync method should not be used since it does not check the SendTimeout property. + await Task.Run( () => OutputStream.Write( buffer, offset, length ), cancellationToken ); + } + + public override async Task FlushAsync(CancellationToken cancellationToken) + { + await OutputStream.FlushAsync(cancellationToken); + ResetConsumedMessageSize(); + } + + + // IDisposable + protected override void Dispose(bool disposing) + { + if (!_isDisposed) + { + if (disposing) + { + InputStream?.Dispose(); + OutputStream?.Dispose(); + } + } + _isDisposed = true; + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Transport/Client/TTlsSocketTransport.cs b/lib/csharp/thrift_netstd/Thrift/Transport/Client/TTlsSocketTransport.cs new file mode 100644 index 000000000..e3da6f4c2 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Transport/Client/TTlsSocketTransport.cs @@ -0,0 +1,273 @@ +// 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. + +using System; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; + +namespace Thrift.Transport.Client +{ + //TODO: check for correct work + + // ReSharper disable once InconsistentNaming + public class TTlsSocketTransport : TStreamTransport + { + private readonly X509Certificate2 _certificate; + private readonly RemoteCertificateValidationCallback _certValidator; + private readonly IPAddress _host; + private readonly bool _isServer; + private readonly LocalCertificateSelectionCallback _localCertificateSelectionCallback; + private readonly int _port; + private readonly SslProtocols _sslProtocols; + private readonly string _targetHost; + private TcpClient _client; + private SslStream _secureStream; + private int _timeout; + + public TTlsSocketTransport(TcpClient client, TConfiguration config, + X509Certificate2 certificate, bool isServer = false, + RemoteCertificateValidationCallback certValidator = null, + LocalCertificateSelectionCallback localCertificateSelectionCallback = null, + SslProtocols sslProtocols = SslProtocols.Tls12) + : base(config) + { + _client = client; + _certificate = certificate; + _certValidator = certValidator; + _localCertificateSelectionCallback = localCertificateSelectionCallback; + _sslProtocols = sslProtocols; + _isServer = isServer; + + if (isServer && certificate == null) + { + throw new ArgumentException("TTlsSocketTransport needs certificate to be used for server", + nameof(certificate)); + } + + if (IsOpen) + { + InputStream = client.GetStream(); + OutputStream = client.GetStream(); + } + } + + public TTlsSocketTransport(IPAddress host, int port, TConfiguration config, + string certificatePath, + RemoteCertificateValidationCallback certValidator = null, + LocalCertificateSelectionCallback localCertificateSelectionCallback = null, + SslProtocols sslProtocols = SslProtocols.Tls12) + : this(host, port, config, 0, + new X509Certificate2(certificatePath), + certValidator, + localCertificateSelectionCallback, + sslProtocols) + { + } + + public TTlsSocketTransport(IPAddress host, int port, TConfiguration config, + X509Certificate2 certificate = null, + RemoteCertificateValidationCallback certValidator = null, + LocalCertificateSelectionCallback localCertificateSelectionCallback = null, + SslProtocols sslProtocols = SslProtocols.Tls12) + : this(host, port, config, 0, + certificate, + certValidator, + localCertificateSelectionCallback, + sslProtocols) + { + } + + public TTlsSocketTransport(IPAddress host, int port, TConfiguration config, int timeout, + X509Certificate2 certificate, + RemoteCertificateValidationCallback certValidator = null, + LocalCertificateSelectionCallback localCertificateSelectionCallback = null, + SslProtocols sslProtocols = SslProtocols.Tls12) + : base(config) + { + _host = host; + _port = port; + _timeout = timeout; + _certificate = certificate; + _certValidator = certValidator; + _localCertificateSelectionCallback = localCertificateSelectionCallback; + _sslProtocols = sslProtocols; + + InitSocket(); + } + + public TTlsSocketTransport(string host, int port, TConfiguration config, int timeout, + X509Certificate2 certificate, + RemoteCertificateValidationCallback certValidator = null, + LocalCertificateSelectionCallback localCertificateSelectionCallback = null, + SslProtocols sslProtocols = SslProtocols.Tls12) + : base(config) + { + try + { + _targetHost = host; + + var entry = Dns.GetHostEntry(host); + if (entry.AddressList.Length == 0) + throw new TTransportException(TTransportException.ExceptionType.Unknown, "unable to resolve host name"); + + _host = entry.AddressList[0]; + _port = port; + _timeout = timeout; + _certificate = certificate; + _certValidator = certValidator; + _localCertificateSelectionCallback = localCertificateSelectionCallback; + _sslProtocols = sslProtocols; + + InitSocket(); + } + catch (SocketException e) + { + throw new TTransportException(TTransportException.ExceptionType.Unknown, e.Message, e); + } + } + + public int Timeout + { + set { _client.ReceiveTimeout = _client.SendTimeout = _timeout = value; } + } + + public TcpClient TcpClient => _client; + + public IPAddress Host => _host; + + public int Port => _port; + + public override bool IsOpen + { + get + { + if (_client == null) + { + return false; + } + + return _client.Connected; + } + } + + private void InitSocket() + { + _client = new TcpClient(); + _client.ReceiveTimeout = _client.SendTimeout = _timeout; + _client.Client.NoDelay = true; + } + + private bool DefaultCertificateValidator(object sender, X509Certificate certificate, X509Chain chain, + SslPolicyErrors sslValidationErrors) + { + return sslValidationErrors == SslPolicyErrors.None; + } + + public override async Task OpenAsync(CancellationToken cancellationToken) + { + if (IsOpen) + { + throw new TTransportException(TTransportException.ExceptionType.AlreadyOpen, "Socket already connected"); + } + + if (_host == null) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot open null host"); + } + + if (_port <= 0) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot open without port"); + } + + if (_client == null) + { + InitSocket(); + } + + if (_client != null) + { + await _client.ConnectAsync(_host, _port); + await SetupTlsAsync(); + } + } + + public async Task SetupTlsAsync() + { + var validator = _certValidator ?? DefaultCertificateValidator; + + if (_localCertificateSelectionCallback != null) + { + _secureStream = new SslStream(_client.GetStream(), false, validator, _localCertificateSelectionCallback); + } + else + { + _secureStream = new SslStream(_client.GetStream(), false, validator); + } + + try + { + if (_isServer) + { + // Server authentication + await + _secureStream.AuthenticateAsServerAsync(_certificate, _certValidator != null, _sslProtocols, + true); + } + else + { + // Client authentication + var certs = _certificate != null + ? new X509CertificateCollection {_certificate} + : new X509CertificateCollection(); + + var targetHost = _targetHost ?? _host.ToString(); + await _secureStream.AuthenticateAsClientAsync(targetHost, certs, _sslProtocols, true); + } + } + catch (Exception) + { + Close(); + throw; + } + + InputStream = _secureStream; + OutputStream = _secureStream; + } + + public override void Close() + { + base.Close(); + if (_client != null) + { + _client.Dispose(); + _client = null; + } + + if (_secureStream != null) + { + _secureStream.Dispose(); + _secureStream = null; + } + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Transport/Layered/TBufferedTransport.cs b/lib/csharp/thrift_netstd/Thrift/Transport/Layered/TBufferedTransport.cs new file mode 100644 index 000000000..271a9946c --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Transport/Layered/TBufferedTransport.cs @@ -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. + +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Thrift.Transport +{ + // ReSharper disable once InconsistentNaming + public class TBufferedTransport : TLayeredTransport + { + private readonly int DesiredBufferSize; + private readonly Client.TMemoryBufferTransport ReadBuffer; + private readonly Client.TMemoryBufferTransport WriteBuffer; + private bool IsDisposed; + + public class Factory : TTransportFactory + { + public override TTransport GetTransport(TTransport trans) + { + return new TBufferedTransport(trans); + } + } + + //TODO: should support only specified input transport? + public TBufferedTransport(TTransport transport, int bufSize = 1024) + : base(transport) + { + if (bufSize <= 0) + { + throw new ArgumentOutOfRangeException(nameof(bufSize), "Buffer size must be a positive number."); + } + + DesiredBufferSize = bufSize; + + WriteBuffer = new Client.TMemoryBufferTransport(InnerTransport.Configuration, bufSize); + ReadBuffer = new Client.TMemoryBufferTransport(InnerTransport.Configuration, bufSize); + + Debug.Assert(DesiredBufferSize == ReadBuffer.Capacity); + Debug.Assert(DesiredBufferSize == WriteBuffer.Capacity); + } + + public TTransport UnderlyingTransport + { + get + { + CheckNotDisposed(); + + return InnerTransport; + } + } + + public override bool IsOpen => !IsDisposed && InnerTransport.IsOpen; + + public override async Task OpenAsync(CancellationToken cancellationToken) + { + CheckNotDisposed(); + + await InnerTransport.OpenAsync(cancellationToken); + } + + public override void Close() + { + CheckNotDisposed(); + + InnerTransport.Close(); + } + + public override async ValueTask ReadAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken) + { + CheckNotDisposed(); + ValidateBufferArgs(buffer, offset, length); + + if (!IsOpen) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen); + } + + + // do we have something buffered? + var count = ReadBuffer.Length - ReadBuffer.Position; + if (count > 0) + { + return await ReadBuffer.ReadAsync(buffer, offset, length, cancellationToken); + } + + // does the request even fit into the buffer? + // Note we test for >= instead of > to avoid nonsense buffering + if (length >= ReadBuffer.Capacity) + { + return await InnerTransport.ReadAsync(buffer, offset, length, cancellationToken); + } + + // buffer a new chunk of bytes from the underlying transport + ReadBuffer.Length = ReadBuffer.Capacity; + ReadBuffer.TryGetBuffer(out ArraySegment bufSegment); + ReadBuffer.Length = await InnerTransport.ReadAsync(bufSegment.Array, 0, bufSegment.Count, cancellationToken); + ReadBuffer.Position = 0; + + // deliver the bytes + return await ReadBuffer.ReadAsync(buffer, offset, length, cancellationToken); + } + + + public override async Task WriteAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken) + { + CheckNotDisposed(); + ValidateBufferArgs(buffer, offset, length); + + if (!IsOpen) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen); + } + + // enough space left in buffer? + var free = WriteBuffer.Capacity - WriteBuffer.Length; + if (length > free) + { + WriteBuffer.TryGetBuffer(out ArraySegment bufSegment); + await InnerTransport.WriteAsync(bufSegment.Array, 0, bufSegment.Count, cancellationToken); + WriteBuffer.SetLength(0); + } + + // do the data even fit into the buffer? + // Note we test for < instead of <= to avoid nonsense buffering + if (length < WriteBuffer.Capacity) + { + await WriteBuffer.WriteAsync(buffer, offset, length, cancellationToken); + return; + } + + // write thru + await InnerTransport.WriteAsync(buffer, offset, length, cancellationToken); + } + + public override async Task FlushAsync(CancellationToken cancellationToken) + { + CheckNotDisposed(); + + if (!IsOpen) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen); + } + + if (WriteBuffer.Length > 0) + { + WriteBuffer.TryGetBuffer(out ArraySegment bufSegment); + await InnerTransport.WriteAsync(bufSegment.Array, 0, bufSegment.Count, cancellationToken); + WriteBuffer.SetLength(0); + } + + await InnerTransport.FlushAsync(cancellationToken); + } + + public override void CheckReadBytesAvailable(long numBytes) + { + var buffered = ReadBuffer.Length - ReadBuffer.Position; + if (buffered < numBytes) + { + numBytes -= buffered; + InnerTransport.CheckReadBytesAvailable(numBytes); + } + } + + + private void CheckNotDisposed() + { + if (IsDisposed) + { + throw new ObjectDisposedException(nameof(InnerTransport)); + } + } + + // IDisposable + protected override void Dispose(bool disposing) + { + if (!IsDisposed) + { + if (disposing) + { + ReadBuffer?.Dispose(); + WriteBuffer?.Dispose(); + InnerTransport?.Dispose(); + } + } + IsDisposed = true; + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Transport/Layered/TFramedTransport.cs b/lib/csharp/thrift_netstd/Thrift/Transport/Layered/TFramedTransport.cs new file mode 100644 index 000000000..722df7672 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Transport/Layered/TFramedTransport.cs @@ -0,0 +1,190 @@ +// 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. + +using System; +using System.Buffers.Binary; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Thrift.Transport +{ + // ReSharper disable once InconsistentNaming + public class TFramedTransport : TLayeredTransport + { + private const int HeaderSize = 4; + private readonly byte[] HeaderBuf = new byte[HeaderSize]; + private readonly Client.TMemoryBufferTransport ReadBuffer; + private readonly Client.TMemoryBufferTransport WriteBuffer; + + private bool IsDisposed; + + public class Factory : TTransportFactory + { + public override TTransport GetTransport(TTransport trans) + { + return new TFramedTransport(trans); + } + } + + public TFramedTransport(TTransport transport) + : base(transport) + { + ReadBuffer = new Client.TMemoryBufferTransport(Configuration); + WriteBuffer = new Client.TMemoryBufferTransport(Configuration); + InitWriteBuffer(); + } + + public override bool IsOpen => !IsDisposed && InnerTransport.IsOpen; + + public override async Task OpenAsync(CancellationToken cancellationToken) + { + CheckNotDisposed(); + + await InnerTransport.OpenAsync(cancellationToken); + } + + public override void Close() + { + CheckNotDisposed(); + + InnerTransport.Close(); + } + + public override async ValueTask ReadAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken) + { + CheckNotDisposed(); + ValidateBufferArgs(buffer, offset, length); + + if (!IsOpen) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen); + } + + // Read another frame of data if we run out of bytes + if (ReadBuffer.Position >= ReadBuffer.Length) + { + await ReadFrameAsync(cancellationToken); + } + + return await ReadBuffer.ReadAsync(buffer, offset, length, cancellationToken); + } + + private async ValueTask ReadFrameAsync(CancellationToken cancellationToken) + { + UpdateKnownMessageSize(-1); + await InnerTransport.ReadAllAsync(HeaderBuf, 0, HeaderSize, cancellationToken); + int size = BinaryPrimitives.ReadInt32BigEndian(HeaderBuf); + + if ((0 > size) || (size > Configuration.MaxFrameSize)) // size must be in the range 0 to allowed max + throw new TTransportException(TTransportException.ExceptionType.Unknown, $"Maximum frame size exceeded ({size} bytes)"); + UpdateKnownMessageSize(size + HeaderSize); + + ReadBuffer.SetLength(size); + ReadBuffer.Seek(0, SeekOrigin.Begin); + + ReadBuffer.TryGetBuffer(out ArraySegment bufSegment); + await InnerTransport.ReadAllAsync(bufSegment.Array, 0, size, cancellationToken); + } + + public override async Task WriteAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken) + { + CheckNotDisposed(); + ValidateBufferArgs(buffer, offset, length); + + if (!IsOpen) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen); + } + + if (WriteBuffer.Length > (int.MaxValue - length)) + { + await FlushAsync(cancellationToken); + } + + await WriteBuffer.WriteAsync(buffer, offset, length, cancellationToken); + } + + public override async Task FlushAsync(CancellationToken cancellationToken) + { + CheckNotDisposed(); + + if (!IsOpen) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen); + } + + WriteBuffer.TryGetBuffer(out ArraySegment bufSegment); + + int dataLen = bufSegment.Count - HeaderSize; + if (dataLen < 0) + { + throw new InvalidOperationException(); // logic error actually + } + + // Inject message header into the reserved buffer space + BinaryPrimitives.WriteInt32BigEndian(bufSegment.Array, dataLen); + + // Send the entire message at once + await InnerTransport.WriteAsync(bufSegment.Array, 0, bufSegment.Count, cancellationToken); + + InitWriteBuffer(); + + await InnerTransport.FlushAsync(cancellationToken); + } + + private void InitWriteBuffer() + { + // Reserve space for message header to be put right before sending it out + WriteBuffer.SetLength(HeaderSize); + WriteBuffer.Seek(0, SeekOrigin.End); + } + + public override void CheckReadBytesAvailable(long numBytes) + { + var buffered = ReadBuffer.Length - ReadBuffer.Position; + if (buffered < numBytes) + { + numBytes -= buffered; + InnerTransport.CheckReadBytesAvailable(numBytes); + } + } + + private void CheckNotDisposed() + { + if (IsDisposed) + { + throw new ObjectDisposedException(this.GetType().Name); + } + } + + // IDisposable + protected override void Dispose(bool disposing) + { + if (!IsDisposed) + { + if (disposing) + { + ReadBuffer?.Dispose(); + WriteBuffer?.Dispose(); + InnerTransport?.Dispose(); + } + } + IsDisposed = true; + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Transport/Layered/TLayeredTransport.cs b/lib/csharp/thrift_netstd/Thrift/Transport/Layered/TLayeredTransport.cs new file mode 100644 index 000000000..4d39b392e --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Transport/Layered/TLayeredTransport.cs @@ -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. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Thrift.Transport +{ + public abstract class TLayeredTransport : TTransport + { + public readonly TTransport InnerTransport; + + public override TConfiguration Configuration { get => InnerTransport.Configuration; } + + public TLayeredTransport(TTransport transport) + { + InnerTransport = transport ?? throw new ArgumentNullException(nameof(transport)); + } + + public override void UpdateKnownMessageSize(long size) + { + InnerTransport.UpdateKnownMessageSize(size); + } + + public override void CheckReadBytesAvailable(long numBytes) + { + InnerTransport.CheckReadBytesAvailable(numBytes); + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Transport/Server/NullLogger.cs b/lib/csharp/thrift_netstd/Thrift/Transport/Server/NullLogger.cs new file mode 100644 index 000000000..1f1f542d5 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Transport/Server/NullLogger.cs @@ -0,0 +1,56 @@ +// 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. + +using Microsoft.Extensions.Logging; +using System; + + +namespace Thrift.Transport.Server +{ + // sometimes we just don't want to log anything + internal class NullLogger : IDisposable, ILogger, ILogger + { + internal class NullScope : IDisposable + { + public void Dispose() + { + // nothing to do + } + } + + public IDisposable BeginScope(TState state) + { + return new NullScope(); + } + + public void Dispose() + { + // nothing to do + } + + public bool IsEnabled(LogLevel logLevel) + { + return false; // no + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + // do nothing + } + } +} + diff --git a/lib/csharp/thrift_netstd/Thrift/Transport/Server/THttpServerTransport.cs b/lib/csharp/thrift_netstd/Thrift/Transport/Server/THttpServerTransport.cs new file mode 100644 index 000000000..05efba21b --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Transport/Server/THttpServerTransport.cs @@ -0,0 +1,138 @@ +// 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. + +using System; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Thrift.Processor; +using Thrift.Protocol; +using Thrift.Transport.Client; + +namespace Thrift.Transport.Server +{ + // ReSharper disable once InconsistentNaming + public class THttpServerTransport + { + protected const string ContentType = "application/x-thrift"; + /* never used + private readonly ILogger _logger; + private readonly RequestDelegate _next; + */ + protected Encoding Encoding = Encoding.UTF8; + + protected TProtocolFactory InputProtocolFactory; + protected TProtocolFactory OutputProtocolFactory; + + protected TTransportFactory InputTransportFactory; + protected TTransportFactory OutputTransportFactory; + + protected ITAsyncProcessor Processor; + protected TConfiguration Configuration; + + public THttpServerTransport( + ITAsyncProcessor processor, + TConfiguration config, + RequestDelegate next = null, + ILoggerFactory loggerFactory = null) + : this(processor, config, new TBinaryProtocol.Factory(), null, next, loggerFactory) + { + } + + public THttpServerTransport( + ITAsyncProcessor processor, + TConfiguration config, + TProtocolFactory protocolFactory, + TTransportFactory transFactory = null, + RequestDelegate next = null, + ILoggerFactory loggerFactory = null) + : this(processor, config, protocolFactory, protocolFactory, transFactory, transFactory, next, loggerFactory) + { + } + + public THttpServerTransport( + ITAsyncProcessor processor, + TConfiguration config, + TProtocolFactory inputProtocolFactory, + TProtocolFactory outputProtocolFactory, + TTransportFactory inputTransFactory = null, + TTransportFactory outputTransFactory = null, + RequestDelegate next = null, + ILoggerFactory loggerFactory = null) + { + // loggerFactory == null is not illegal anymore + + Processor = processor ?? throw new ArgumentNullException(nameof(processor)); + Configuration = config; // may be null + + InputProtocolFactory = inputProtocolFactory ?? throw new ArgumentNullException(nameof(inputProtocolFactory)); + OutputProtocolFactory = outputProtocolFactory ?? throw new ArgumentNullException(nameof(outputProtocolFactory)); + + InputTransportFactory = inputTransFactory; + OutputTransportFactory = outputTransFactory; + + // never used + _ = next; + _ = loggerFactory; + /* never used + _next = next; + _logger = (loggerFactory != null) ? loggerFactory.CreateLogger() : new NullLogger(); + */ + } + + public async Task Invoke(HttpContext context) + { + await ProcessRequestAsync(context, context.RequestAborted); //TODO: check for correct logic + } + + public async Task ProcessRequestAsync(HttpContext context, CancellationToken cancellationToken) + { + var transport = new TStreamTransport(context.Request.Body, context.Response.Body, Configuration); + try + { + var intrans = (InputTransportFactory != null) ? InputTransportFactory.GetTransport(transport) : transport; + var outtrans = (OutputTransportFactory != null) ? OutputTransportFactory.GetTransport(transport) : transport; + + var input = InputProtocolFactory.GetProtocol(intrans); + var output = OutputProtocolFactory.GetProtocol(outtrans); + + context.Response.ContentType = ContentType; + while (await Processor.ProcessAsync(input, output, cancellationToken)) + { + if (!context.Response.HasStarted) // oneway method called + await context.Response.Body.FlushAsync(cancellationToken); + } + } + catch (TTransportException) + { + if (!context.Response.HasStarted) // if something goes bust, let the client know + context.Response.StatusCode = 500; // internal server error + } + catch (TProtocolException) + { + if (!context.Response.HasStarted) // if something goes bust, let the client know + context.Response.StatusCode = 400; // bad request, e.g. required field missing + } + finally + { + transport.Close(); + } + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Transport/Server/TNamedPipeServerTransport.cs b/lib/csharp/thrift_netstd/Thrift/Transport/Server/TNamedPipeServerTransport.cs new file mode 100644 index 000000000..e985a298a --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Transport/Server/TNamedPipeServerTransport.cs @@ -0,0 +1,360 @@ +// 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. + +using Microsoft.Win32.SafeHandles; +using System; +using System.IO.Pipes; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using System.ComponentModel; +using System.Security.AccessControl; +using System.Security.Principal; + +namespace Thrift.Transport.Server +{ + [Flags] + public enum NamedPipeClientFlags { + None = 0x00, + OnlyLocalClients = 0x01 + }; + + // ReSharper disable once InconsistentNaming + public class TNamedPipeServerTransport : TServerTransport + { + /// + /// This is the address of the Pipe on the localhost. + /// + private readonly string _pipeAddress; + private bool _asyncMode = true; + private volatile bool _isPending = true; + private NamedPipeServerStream _stream = null; + private readonly bool _onlyLocalClients = false; // compatibility default + + public TNamedPipeServerTransport(string pipeAddress, TConfiguration config, NamedPipeClientFlags flags) + : base(config) + { + _pipeAddress = pipeAddress; + _onlyLocalClients = flags.HasFlag(NamedPipeClientFlags.OnlyLocalClients); + } + + [Obsolete("This CTOR is deprecated, please use the other one instead.")] + public TNamedPipeServerTransport(string pipeAddress, TConfiguration config) + : base(config) + { + _pipeAddress = pipeAddress; + _onlyLocalClients = false; + } + + public override bool IsOpen() { + return true; + } + + public override void Listen() + { + // nothing to do here + } + + public override void Close() + { + if (_stream != null) + { + try + { + if (_stream.IsConnected) + _stream.Disconnect(); + _stream.Dispose(); + } + finally + { + _stream = null; + _isPending = false; + } + } + } + + public override bool IsClientPending() + { + return _isPending; + } + + private void EnsurePipeInstance() + { + if (_stream == null) + { + const PipeDirection direction = PipeDirection.InOut; + const int maxconn = NamedPipeServerStream.MaxAllowedServerInstances; + const PipeTransmissionMode mode = PipeTransmissionMode.Byte; + const int inbuf = 4096; + const int outbuf = 4096; + var options = _asyncMode ? PipeOptions.Asynchronous : PipeOptions.None; + + + // TODO: "CreatePipeNative" ist only a workaround, and there are have basically two possible outcomes: + // - once NamedPipeServerStream() gets a CTOR that supports pipesec, remove CreatePipeNative() + // - if 31190 gets resolved before, use _stream.SetAccessControl(pipesec) instead of CreatePipeNative() + // EITHER WAY, + // - if CreatePipeNative() finally gets removed, also remove "allow unsafe code" from the project settings + + try + { + var handle = CreatePipeNative(_pipeAddress, inbuf, outbuf, _onlyLocalClients); + if ((handle != null) && (!handle.IsInvalid)) + { + _stream = new NamedPipeServerStream(PipeDirection.InOut, _asyncMode, false, handle); + handle = null; // we don't own it any longer + } + else + { + handle?.Dispose(); + _stream = new NamedPipeServerStream(_pipeAddress, direction, maxconn, mode, options, inbuf, outbuf/*, pipesec*/); + } + } + catch (NotImplementedException) // Mono still does not support async, fallback to sync + { + if (_asyncMode) + { + options &= (~PipeOptions.Asynchronous); + _stream = new NamedPipeServerStream(_pipeAddress, direction, maxconn, mode, options, inbuf, outbuf); + _asyncMode = false; + } + else + { + throw; + } + } + } + } + + + #region CreatePipeNative workaround + + + [StructLayout(LayoutKind.Sequential)] + internal class SECURITY_ATTRIBUTES + { + internal int nLength = 0; + internal IntPtr lpSecurityDescriptor = IntPtr.Zero; + internal int bInheritHandle = 0; + } + + + private const string Kernel32 = "kernel32.dll"; + + [DllImport(Kernel32, SetLastError = true, CharSet = CharSet.Unicode)] + internal static extern IntPtr CreateNamedPipe( + string lpName, uint dwOpenMode, uint dwPipeMode, + uint nMaxInstances, uint nOutBufferSize, uint nInBufferSize, uint nDefaultTimeOut, + SECURITY_ATTRIBUTES pipeSecurityDescriptor + ); + + + + // Workaround: create the pipe via API call + // we have to do it this way, since NamedPipeServerStream() for netstd still lacks a few CTORs and/or arguments + // and _stream.SetAccessControl(pipesec); only keeps throwing ACCESS_DENIED errors at us + // References: + // - https://github.com/dotnet/corefx/issues/30170 (closed, continued in 31190) + // - https://github.com/dotnet/corefx/issues/31190 System.IO.Pipes.AccessControl package does not work + // - https://github.com/dotnet/corefx/issues/24040 NamedPipeServerStream: Provide support for WRITE_DAC + // - https://github.com/dotnet/corefx/issues/34400 Have a mechanism for lower privileged user to connect to a privileged user's pipe + private static SafePipeHandle CreatePipeNative(string name, int inbuf, int outbuf, bool OnlyLocalClients) + { + if (! RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return null; // Windows only + + var pinningHandle = new GCHandle(); + try + { + // owner gets full access, everyone else read/write + var pipesec = new PipeSecurity(); + using (var currentIdentity = WindowsIdentity.GetCurrent()) + { + var sidOwner = currentIdentity.Owner; + var sidWorld = new SecurityIdentifier(WellKnownSidType.WorldSid, null); + + pipesec.SetOwner(sidOwner); + pipesec.AddAccessRule(new PipeAccessRule(sidOwner, PipeAccessRights.FullControl, AccessControlType.Allow)); + pipesec.AddAccessRule(new PipeAccessRule(sidWorld, PipeAccessRights.ReadWrite, AccessControlType.Allow)); + } + + // create a security descriptor and assign it to the security attribs + var secAttrs = new SECURITY_ATTRIBUTES(); + byte[] sdBytes = pipesec.GetSecurityDescriptorBinaryForm(); + pinningHandle = GCHandle.Alloc(sdBytes, GCHandleType.Pinned); + unsafe { + fixed (byte* pSD = sdBytes) { + secAttrs.lpSecurityDescriptor = (IntPtr)pSD; + } + } + + // a bunch of constants we will need shortly + const uint PIPE_ACCESS_DUPLEX = 0x00000003; + const uint FILE_FLAG_OVERLAPPED = 0x40000000; + const uint WRITE_DAC = 0x00040000; + const uint PIPE_TYPE_BYTE = 0x00000000; + const uint PIPE_READMODE_BYTE = 0x00000000; + const uint PIPE_UNLIMITED_INSTANCES = 255; + const uint PIPE_ACCEPT_REMOTE_CLIENTS = 0x00000000; // Connections from remote clients can be accepted and checked against the security descriptor for the pipe. + const uint PIPE_REJECT_REMOTE_CLIENTS = 0x00000008; // Connections from remote clients are automatically rejected. + + // any extra flags we want to add + uint dwPipeModeXtra + = (OnlyLocalClients ? PIPE_REJECT_REMOTE_CLIENTS : PIPE_ACCEPT_REMOTE_CLIENTS) + ; + + // create the pipe via API call + var rawHandle = CreateNamedPipe( + @"\\.\pipe\" + name, + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | WRITE_DAC, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | dwPipeModeXtra, + PIPE_UNLIMITED_INSTANCES, (uint)inbuf, (uint)outbuf, + 5 * 1000, + secAttrs + ); + + // make a SafePipeHandle() from it + var handle = new SafePipeHandle(rawHandle, true); + if (handle.IsInvalid) + throw new Win32Exception(Marshal.GetLastWin32Error()); + + // return it (to be packaged) + return handle; + } + finally + { + if (pinningHandle.IsAllocated) + pinningHandle.Free(); + } + } + + #endregion + + protected override async ValueTask AcceptImplementationAsync(CancellationToken cancellationToken) + { + try + { + EnsurePipeInstance(); + + await _stream.WaitForConnectionAsync(cancellationToken); + + var trans = new ServerTransport(_stream, Configuration); + _stream = null; // pass ownership to ServerTransport + + //_isPending = false; + + return trans; + } + catch (TTransportException) + { + Close(); + throw; + } + catch (TaskCanceledException) + { + Close(); + throw; // let it bubble up + } + catch (Exception e) + { + Close(); + throw new TTransportException(TTransportException.ExceptionType.NotOpen, e.Message); + } + } + + private class ServerTransport : TEndpointTransport + { + private readonly NamedPipeServerStream PipeStream; + + public ServerTransport(NamedPipeServerStream stream, TConfiguration config) + : base(config) + { + PipeStream = stream; + } + + public override bool IsOpen => PipeStream != null && PipeStream.IsConnected; + + public override Task OpenAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } + + public override void Close() + { + PipeStream?.Dispose(); + } + + public override async ValueTask ReadAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken) + { + if (PipeStream == null) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen); + } + + CheckReadBytesAvailable(length); +#if NETSTANDARD2_0 + var numBytes = await PipeStream.ReadAsync(buffer, offset, length, cancellationToken); +#else + var numBytes = await PipeStream.ReadAsync(buffer.AsMemory(offset, length), cancellationToken); +#endif + CountConsumedMessageBytes(numBytes); + return numBytes; + } + + public override async Task WriteAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken) + { + if (PipeStream == null) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen); + } + + // if necessary, send the data in chunks + // there's a system limit around 0x10000 bytes that we hit otherwise + // MSDN: "Pipe write operations across a network are limited to 65,535 bytes per write. For more information regarding pipes, see the Remarks section." + var nBytes = Math.Min(15 * 4096, length); // 16 would exceed the limit + while (nBytes > 0) + { +#if NET5_0_OR_GREATER + await PipeStream.WriteAsync(buffer.AsMemory(offset, nBytes), cancellationToken); +#else + await PipeStream.WriteAsync(buffer, offset, nBytes, cancellationToken); +#endif + offset += nBytes; + length -= nBytes; + nBytes = Math.Min(nBytes, length); + } + } + + public override Task FlushAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + ResetConsumedMessageSize(); + return Task.CompletedTask; + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + PipeStream?.Dispose(); + } + } + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Transport/Server/TServerSocketTransport.cs b/lib/csharp/thrift_netstd/Thrift/Transport/Server/TServerSocketTransport.cs new file mode 100644 index 000000000..3a4fed468 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Transport/Server/TServerSocketTransport.cs @@ -0,0 +1,166 @@ +// 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. + +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; +using Thrift.Transport.Client; + +namespace Thrift.Transport.Server +{ + + // ReSharper disable once InconsistentNaming + public class TServerSocketTransport : TServerTransport + { + private readonly int _clientTimeout; + private TcpListener _server; + + public TServerSocketTransport(TcpListener listener, TConfiguration config, int clientTimeout = 0) + : base(config) + { + _server = listener; + _clientTimeout = clientTimeout; + } + + public TServerSocketTransport(int port, TConfiguration config, int clientTimeout = 0) + : this(null, config, clientTimeout) + { + try + { + // Make server socket + _server = new TcpListener(IPAddress.Any, port); + _server.Server.NoDelay = true; + } + catch (Exception) + { + _server = null; + throw new TTransportException("Could not create ServerSocket on port " + port + "."); + } + } + + public override bool IsOpen() + { + return (_server != null) + && (_server.Server != null) + && _server.Server.IsBound; + } + + public int GetPort() + { + if ((_server != null) && (_server.Server != null) && (_server.Server.LocalEndPoint != null)) + { + if (_server.Server.LocalEndPoint is IPEndPoint server) + { + return server.Port; + } + else + { + throw new TTransportException("ServerSocket is not a network socket"); + } + } + else + { + throw new TTransportException("ServerSocket is not open"); + } + } + + public override void Listen() + { + // Make sure not to block on accept + if (_server != null) + { + try + { + _server.Start(); + } + catch (SocketException sx) + { + throw new TTransportException("Could not accept on listening socket: " + sx.Message); + } + } + } + + public override bool IsClientPending() + { + return _server.Pending(); + } + + protected override async ValueTask AcceptImplementationAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (_server == null) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen, "No underlying server socket."); + } + + try + { + TTransport tSocketTransport = null; + #if NET6_0_OR_GREATER + var tcpClient = await _server.AcceptTcpClientAsync(cancellationToken); + #else + var tcpClient = await _server.AcceptTcpClientAsync(); + #endif + try + { + tSocketTransport = new TSocketTransport(tcpClient, Configuration) + { + Timeout = _clientTimeout + }; + + return tSocketTransport; + } + catch (Exception) + { + if (tSocketTransport != null) + { + tSocketTransport.Dispose(); + } + else // Otherwise, clean it up ourselves. + { + ((IDisposable)tcpClient).Dispose(); + } + + throw; + } + } + catch (Exception ex) + { + throw new TTransportException(ex.ToString()); + } + } + + public override void Close() + { + if (_server != null) + { + try + { + _server.Stop(); + } + catch (Exception ex) + { + throw new TTransportException("WARNING: Could not close server socket: " + ex); + } + _server = null; + } + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Transport/Server/TServerTransport.cs b/lib/csharp/thrift_netstd/Thrift/Transport/Server/TServerTransport.cs new file mode 100644 index 000000000..5366114b0 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Transport/Server/TServerTransport.cs @@ -0,0 +1,53 @@ +// 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. + +using System.Threading; +using System.Threading.Tasks; + +namespace Thrift.Transport +{ + // ReSharper disable once InconsistentNaming + public abstract class TServerTransport + { + public readonly TConfiguration Configuration; + + public TServerTransport(TConfiguration config) + { + Configuration = config ?? new TConfiguration(); + } + + public abstract bool IsOpen(); + + public abstract void Listen(); + public abstract void Close(); + public abstract bool IsClientPending(); + + protected abstract ValueTask AcceptImplementationAsync(CancellationToken cancellationToken = default); + + public async ValueTask AcceptAsync(CancellationToken cancellationToken = default) + { + var transport = await AcceptImplementationAsync(cancellationToken); + + if (transport == null) + { + throw new TTransportException($"{nameof(AcceptImplementationAsync)} should not return null"); + } + + return transport; + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Transport/Server/TTlsServerSocketTransport.cs b/lib/csharp/thrift_netstd/Thrift/Transport/Server/TTlsServerSocketTransport.cs new file mode 100644 index 000000000..77abcaeb5 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Transport/Server/TTlsServerSocketTransport.cs @@ -0,0 +1,178 @@ +// 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. + +using System; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; +using Thrift.Transport.Client; + +namespace Thrift.Transport.Server +{ + // ReSharper disable once InconsistentNaming + public class TTlsServerSocketTransport : TServerTransport + { + private readonly RemoteCertificateValidationCallback _clientCertValidator; + private readonly int _clientTimeout = 0; + private readonly LocalCertificateSelectionCallback _localCertificateSelectionCallback; + private readonly X509Certificate2 _serverCertificate; + private readonly SslProtocols _sslProtocols; + private TcpListener _server; + + public TTlsServerSocketTransport( + TcpListener listener, + TConfiguration config, + X509Certificate2 certificate, + RemoteCertificateValidationCallback clientCertValidator = null, + LocalCertificateSelectionCallback localCertificateSelectionCallback = null, + SslProtocols sslProtocols = SslProtocols.Tls12) + : base(config) + { + if (!certificate.HasPrivateKey) + { + throw new TTransportException(TTransportException.ExceptionType.Unknown, + "Your server-certificate needs to have a private key"); + } + + _serverCertificate = certificate; + _clientCertValidator = clientCertValidator; + _localCertificateSelectionCallback = localCertificateSelectionCallback; + _sslProtocols = sslProtocols; + _server = listener; + } + + public TTlsServerSocketTransport( + int port, + TConfiguration config, + X509Certificate2 certificate, + RemoteCertificateValidationCallback clientCertValidator = null, + LocalCertificateSelectionCallback localCertificateSelectionCallback = null, + SslProtocols sslProtocols = SslProtocols.Tls12) + : this(null, config, certificate, clientCertValidator, localCertificateSelectionCallback, sslProtocols) + { + try + { + // Create server socket + _server = new TcpListener(IPAddress.Any, port); + _server.Server.NoDelay = true; + } + catch (Exception) + { + _server = null; + throw new TTransportException($"Could not create ServerSocket on port {port}."); + } + } + + public override bool IsOpen() + { + return (_server != null) + && (_server.Server != null) + && _server.Server.IsBound; + } + + public int GetPort() + { + if ((_server != null) && (_server.Server != null) && (_server.Server.LocalEndPoint != null)) + { + if (_server.Server.LocalEndPoint is IPEndPoint server) + { + return server.Port; + } + else + { + throw new TTransportException("ServerSocket is not a network socket"); + } + } + else + { + throw new TTransportException("ServerSocket is not open"); + } + } + + public override void Listen() + { + // Make sure accept is not blocking + if (_server != null) + { + try + { + _server.Start(); + } + catch (SocketException sx) + { + throw new TTransportException($"Could not accept on listening socket: {sx.Message}"); + } + } + } + + public override bool IsClientPending() + { + return _server.Pending(); + } + + protected override async ValueTask AcceptImplementationAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (_server == null) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen, "No underlying server socket."); + } + + try + { + var client = await _server.AcceptTcpClientAsync(); + client.SendTimeout = client.ReceiveTimeout = _clientTimeout; + + //wrap the client in an SSL Socket passing in the SSL cert + var tTlsSocket = new TTlsSocketTransport( + client, Configuration, + _serverCertificate, true, _clientCertValidator, + _localCertificateSelectionCallback, _sslProtocols); + + await tTlsSocket.SetupTlsAsync(); + + return tTlsSocket; + } + catch (Exception ex) + { + throw new TTransportException(ex.ToString()); + } + } + + public override void Close() + { + if (_server != null) + { + try + { + _server.Stop(); + } + catch (Exception ex) + { + throw new TTransportException($"WARNING: Could not close server socket: {ex}"); + } + + _server = null; + } + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Transport/TEndpointTransport.cs b/lib/csharp/thrift_netstd/Thrift/Transport/TEndpointTransport.cs new file mode 100644 index 000000000..51a2a17cb --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Transport/TEndpointTransport.cs @@ -0,0 +1,104 @@ +// 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. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace Thrift.Transport +{ + + abstract public class TEndpointTransport : TTransport + { + protected long MaxMessageSize { get => Configuration.MaxMessageSize; } + protected long KnownMessageSize { get; private set; } + protected long RemainingMessageSize { get; private set; } + + private readonly TConfiguration _configuration; + public override TConfiguration Configuration { get => _configuration; } + + public TEndpointTransport( TConfiguration config) + { + _configuration = config ?? new TConfiguration(); + Debug.Assert(Configuration != null); + + ResetConsumedMessageSize(); + } + + /// + /// Resets RemainingMessageSize to the configured maximum + /// + protected void ResetConsumedMessageSize(long newSize = -1) + { + // full reset + if (newSize < 0) + { + KnownMessageSize = MaxMessageSize; + RemainingMessageSize = MaxMessageSize; + return; + } + + // update only: message size can shrink, but not grow + Debug.Assert(KnownMessageSize <= MaxMessageSize); + if (newSize > KnownMessageSize) + throw new TTransportException(TTransportException.ExceptionType.EndOfFile, "MaxMessageSize reached"); + + KnownMessageSize = newSize; + RemainingMessageSize = newSize; + } + + /// + /// Updates RemainingMessageSize to reflect then known real message size (e.g. framed transport). + /// Will throw if we already consumed too many bytes or if the new size is larger than allowed. + /// + /// + public override void UpdateKnownMessageSize(long size) + { + var consumed = KnownMessageSize - RemainingMessageSize; + ResetConsumedMessageSize(size); + CountConsumedMessageBytes(consumed); + } + + /// + /// Throws if there are not enough bytes in the input stream to satisfy a read of numBytes bytes of data + /// + /// + public override void CheckReadBytesAvailable(long numBytes) + { + if (RemainingMessageSize < numBytes) + throw new TTransportException(TTransportException.ExceptionType.EndOfFile, "MaxMessageSize reached"); + } + + /// + /// Consumes numBytes from the RemainingMessageSize. + /// + /// + protected void CountConsumedMessageBytes(long numBytes) + { + if (RemainingMessageSize >= numBytes) + { + RemainingMessageSize -= numBytes; + } + else + { + RemainingMessageSize = 0; + throw new TTransportException(TTransportException.ExceptionType.EndOfFile, "MaxMessageSize reached"); + } + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Transport/TTransport.cs b/lib/csharp/thrift_netstd/Thrift/Transport/TTransport.cs new file mode 100644 index 000000000..3f4245aa2 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Transport/TTransport.cs @@ -0,0 +1,169 @@ +// 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. + +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Thrift.Transport +{ + //TODO: think about client info + // ReSharper disable once InconsistentNaming + public abstract class TTransport : IDisposable + { + //TODO: think how to avoid peek byte + private readonly byte[] _peekBuffer = new byte[1]; + private bool _hasPeekByte; + + public abstract bool IsOpen { get; } + public abstract TConfiguration Configuration { get; } + public abstract void UpdateKnownMessageSize(long size); + public abstract void CheckReadBytesAvailable(long numBytes); + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public async ValueTask PeekAsync(CancellationToken cancellationToken) + { + //If we already have a byte read but not consumed, do nothing. + if (_hasPeekByte) + { + return true; + } + + //If transport closed we can't peek. + if (!IsOpen) + { + return false; + } + + //Try to read one byte. If succeeds we will need to store it for the next read. + try + { + var bytes = await ReadAsync(_peekBuffer, 0, 1, cancellationToken); + if (bytes == 0) + { + return false; + } + } + catch (IOException) + { + return false; + } + + _hasPeekByte = true; + return true; + } + + + public abstract Task OpenAsync(CancellationToken cancellationToken = default); + + public abstract void Close(); + + protected static void ValidateBufferArgs(byte[] buffer, int offset, int length) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + +#if DEBUG // let it fail with OutOfRange in RELEASE mode + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset), "Buffer offset must be >= 0"); + } + + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length), "Buffer length must be >= 0"); + } + + if (offset + length > buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(buffer), "Not enough data"); + } +#endif + } + + + public abstract ValueTask ReadAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken); + + public virtual async ValueTask ReadAllAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + ValidateBufferArgs(buffer, offset, length); + if (length <= 0) + return 0; + + // If we previously peeked a byte, we need to use that first. + var totalBytes = 0; + if (_hasPeekByte) + { + buffer[offset++] = _peekBuffer[0]; + _hasPeekByte = false; + if (1 == length) + { + return 1; // we're done + } + ++totalBytes; + } + + var remaining = length - totalBytes; + Debug.Assert(remaining > 0); // any other possible cases should have been handled already + while (true) + { + var numBytes = await ReadAsync(buffer, offset, remaining, cancellationToken); + totalBytes += numBytes; + if (totalBytes >= length) + { + return totalBytes; // we're done + } + + if (numBytes <= 0) + { + throw new TTransportException(TTransportException.ExceptionType.EndOfFile, + "Cannot read, Remote side has closed"); + } + + remaining -= numBytes; + offset += numBytes; + } + } + + public virtual async Task WriteAsync(byte[] buffer, CancellationToken cancellationToken) + { + await WriteAsync(buffer, 0, buffer.Length, CancellationToken.None); + } + + public virtual async Task WriteAsync(byte[] buffer, int offset, int length) + { + await WriteAsync(buffer, offset, length, CancellationToken.None); + } + + public abstract Task WriteAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken); + + + public abstract Task FlushAsync(CancellationToken cancellationToken); + + protected abstract void Dispose(bool disposing); + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/Transport/TTransportException.cs b/lib/csharp/thrift_netstd/Thrift/Transport/TTransportException.cs new file mode 100644 index 000000000..760a178e6 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Transport/TTransportException.cs @@ -0,0 +1,60 @@ +// 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. + +using System; + +namespace Thrift.Transport +{ + // ReSharper disable once InconsistentNaming + public class TTransportException : TException + { + public enum ExceptionType + { + Unknown, + NotOpen, + AlreadyOpen, + TimedOut, + EndOfFile, + Interrupted + } + + public ExceptionType ExType { get; private set; } + + public TTransportException() + { + } + + public TTransportException(ExceptionType exType, Exception inner = null) + : base(string.Empty, inner) + { + ExType = exType; + } + + public TTransportException(ExceptionType exType, string message, Exception inner = null) + : base(message, inner) + { + ExType = exType; + } + + public TTransportException(string message, Exception inner = null) + : base(message, inner) + { + } + + public ExceptionType Type => ExType; + } +} \ No newline at end of file diff --git a/lib/csharp/thrift_netstd/Thrift/Transport/TTransportFactory.cs b/lib/csharp/thrift_netstd/Thrift/Transport/TTransportFactory.cs new file mode 100644 index 000000000..5f604f163 --- /dev/null +++ b/lib/csharp/thrift_netstd/Thrift/Transport/TTransportFactory.cs @@ -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. + +namespace Thrift.Transport +{ + /// + /// From Mark Slee & Aditya Agarwal of Facebook: + /// Factory class used to create wrapped instance of Transports. + /// This is used primarily in servers, which get Transports from + /// a ServerTransport and then may want to mutate them (i.e. create + /// a BufferedTransport from the underlying base transport) + /// + // ReSharper disable once InconsistentNaming + public class TTransportFactory + { + public virtual TTransport GetTransport(TTransport trans) + { + return trans; + } + } +} diff --git a/lib/csharp/thrift_netstd/Thrift/thrift.snk b/lib/csharp/thrift_netstd/Thrift/thrift.snk new file mode 100644 index 000000000..97bc5812b Binary files /dev/null and b/lib/csharp/thrift_netstd/Thrift/thrift.snk differ diff --git a/lib/csharp/thrift_netstd/build.cmd b/lib/csharp/thrift_netstd/build.cmd new file mode 100644 index 000000000..863c4b45e --- /dev/null +++ b/lib/csharp/thrift_netstd/build.cmd @@ -0,0 +1,27 @@ +@echo off +rem /* +rem * Licensed to the Apache Software Foundation (ASF) under one +rem * or more contributor license agreements. See the NOTICE file +rem * distributed with this work for additional information +rem * regarding copyright ownership. The ASF licenses this file +rem * to you under the Apache License, Version 2.0 (the +rem * "License"); you may not use this file except in compliance +rem * with the License. You may obtain a copy of the License at +rem * +rem * http://www.apache.org/licenses/LICENSE-2.0 +rem * +rem * Unless required by applicable law or agreed to in writing, +rem * software distributed under the License is distributed on an +rem * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +rem * KIND, either express or implied. See the License for the +rem * specific language governing permissions and limitations +rem * under the License. +rem */ + +setlocal + +thrift -version +dotnet --info +dotnet build + +:eof diff --git a/lib/csharp/thrift_netstd/build.sh b/lib/csharp/thrift_netstd/build.sh new file mode 100644 index 000000000..ae18bce9b --- /dev/null +++ b/lib/csharp/thrift_netstd/build.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +# +# 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. +# + +#exit if any command fails +#set -e + +thrift --version +dotnet --info +dotnet build + +#revision=${TRAVIS_JOB_ID:=1} +#revision=$(printf "%04d" $revision) + +#dotnet pack ./src/PROJECT_NAME -c Release -o ./artifacts --version-suffix=$revision diff --git a/lib/csharp/thrift_netstd/runtests.cmd b/lib/csharp/thrift_netstd/runtests.cmd new file mode 100644 index 000000000..5114bc594 --- /dev/null +++ b/lib/csharp/thrift_netstd/runtests.cmd @@ -0,0 +1,28 @@ +@echo off +rem /* +rem * Licensed to the Apache Software Foundation (ASF) under one +rem * or more contributor license agreements. See the NOTICE file +rem * distributed with this work for additional information +rem * regarding copyright ownership. The ASF licenses this file +rem * to you under the Apache License, Version 2.0 (the +rem * "License"); you may not use this file except in compliance +rem * with the License. You may obtain a copy of the License at +rem * +rem * http://www.apache.org/licenses/LICENSE-2.0 +rem * +rem * Unless required by applicable law or agreed to in writing, +rem * software distributed under the License is distributed on an +rem * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +rem * KIND, either express or implied. See the License for the +rem * specific language governing permissions and limitations +rem * under the License. +rem */ +setlocal + +thrift -version +dotnet --info + +dotnet test Tests\Thrift.IntegrationTests\Thrift.IntegrationTests.csproj +dotnet test Tests\Thrift.Tests\Thrift.Tests.csproj + +:eof diff --git a/lib/csharp/thrift_netstd/runtests.sh b/lib/csharp/thrift_netstd/runtests.sh new file mode 100644 index 000000000..a26cc36ac --- /dev/null +++ b/lib/csharp/thrift_netstd/runtests.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +# +# 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 -version +dotnet --info + +dotnet test Tests\Thrift.IntegrationTests\Thrift.IntegrationTests.csproj +dotnet test Tests\Thrift.Tests\Thrift.Tests.csproj \ No newline at end of file diff --git a/plugins/csharp/CMakeLists.txt b/plugins/csharp/CMakeLists.txt new file mode 100644 index 000000000..8b802f94e --- /dev/null +++ b/plugins/csharp/CMakeLists.txt @@ -0,0 +1,4 @@ +add_subdirectory(parser) +add_subdirectory(service) + +install_webplugin(webgui) \ No newline at end of file diff --git a/plugins/csharp/migrations/20220518134047_Initial.Designer.cs b/plugins/csharp/migrations/20220518134047_Initial.Designer.cs new file mode 100644 index 000000000..0a7707b34 --- /dev/null +++ b/plugins/csharp/migrations/20220518134047_Initial.Designer.cs @@ -0,0 +1,545 @@ +// +using System; +using CSharpParser.model; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace CSharpParser.Migrations +{ + [DbContext(typeof(CsharpDbContext))] + [Migration("20220518134047_Initial")] + partial class Initial + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.10") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + modelBuilder.Entity("CSharpParser.model.CsharpAstNode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)"); + + b.Property("Accessibility") + .HasColumnType("integer"); + + b.Property("AstSymbolType") + .HasColumnType("integer"); + + b.Property("AstType") + .HasColumnType("integer"); + + b.Property("AstValue") + .HasColumnType("text"); + + b.Property("EntityHash") + .HasColumnType("bigint"); + + b.Property("Location_range_end_column") + .HasColumnType("bigint"); + + b.Property("Location_range_end_line") + .HasColumnType("bigint"); + + b.Property("Location_range_start_column") + .HasColumnType("bigint"); + + b.Property("Location_range_start_line") + .HasColumnType("bigint"); + + b.Property("Path") + .HasColumnType("text"); + + b.Property("RawKind") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("CsharpAstNodes"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpClass", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AstNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("ClassType") + .HasColumnType("integer"); + + b.Property("CsharpNamespaceId") + .HasColumnType("bigint"); + + b.Property("DocumentationCommentXML") + .HasColumnType("text"); + + b.Property("EntityHash") + .HasColumnType("bigint"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("ParentNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("QualifiedName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("AstNodeId"); + + b.HasIndex("CsharpNamespaceId"); + + b.HasIndex("ParentNodeId"); + + b.ToTable("CsharpClasses"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpEnum", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AstNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("CsharpNamespaceId") + .HasColumnType("bigint"); + + b.Property("DocumentationCommentXML") + .HasColumnType("text"); + + b.Property("EntityHash") + .HasColumnType("bigint"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("ParentNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("QualifiedName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("AstNodeId"); + + b.HasIndex("CsharpNamespaceId"); + + b.HasIndex("ParentNodeId"); + + b.ToTable("CsharpEnums"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpEnumMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AstNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("CsharpEnumId") + .HasColumnType("bigint"); + + b.Property("DocumentationCommentXML") + .HasColumnType("text"); + + b.Property("EntityHash") + .HasColumnType("bigint"); + + b.Property("EqualsValue") + .HasColumnType("integer"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("ParentNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("QualifiedName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("AstNodeId"); + + b.HasIndex("CsharpEnumId"); + + b.HasIndex("ParentNodeId"); + + b.ToTable("CsharpEnumMembers"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpEtcEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AstNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("DeclaratorNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("DocumentationCommentXML") + .HasColumnType("text"); + + b.Property("EntityHash") + .HasColumnType("bigint"); + + b.Property("EtcEntityType") + .HasColumnType("integer"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("ParentNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("QualifiedName") + .HasColumnType("text"); + + b.Property("QualifiedType") + .HasColumnType("text"); + + b.Property("TypeHash") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("AstNodeId"); + + b.HasIndex("ParentNodeId"); + + b.ToTable("CsharpEtcEntitys"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpMethod", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AstNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("DocumentationCommentXML") + .HasColumnType("text"); + + b.Property("EntityHash") + .HasColumnType("bigint"); + + b.Property("MethodType") + .HasColumnType("integer"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("ParentNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("QualifiedName") + .HasColumnType("text"); + + b.Property("QualifiedType") + .HasColumnType("text"); + + b.Property("TypeHash") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("AstNodeId"); + + b.HasIndex("ParentNodeId"); + + b.ToTable("CsharpMethods"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpNamespace", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AstNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("DocumentationCommentXML") + .HasColumnType("text"); + + b.Property("EntityHash") + .HasColumnType("bigint"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("ParentNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("QualifiedName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("AstNodeId"); + + b.HasIndex("ParentNodeId"); + + b.ToTable("CsharpNamespaces"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpStruct", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AstNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("CsharpNamespaceId") + .HasColumnType("bigint"); + + b.Property("DocumentationCommentXML") + .HasColumnType("text"); + + b.Property("EntityHash") + .HasColumnType("bigint"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("ParentNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("QualifiedName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("AstNodeId"); + + b.HasIndex("CsharpNamespaceId"); + + b.HasIndex("ParentNodeId"); + + b.ToTable("CsharpStructs"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AstNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("DocumentationCommentXML") + .HasColumnType("text"); + + b.Property("EntityHash") + .HasColumnType("bigint"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("ParentNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("QualifiedName") + .HasColumnType("text"); + + b.Property("QualifiedType") + .HasColumnType("text"); + + b.Property("TypeHash") + .HasColumnType("bigint"); + + b.Property("VariableType") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("AstNodeId"); + + b.HasIndex("ParentNodeId"); + + b.ToTable("CsharpVariables"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpClass", b => + { + b.HasOne("CSharpParser.model.CsharpAstNode", "AstNode") + .WithMany() + .HasForeignKey("AstNodeId"); + + b.HasOne("CSharpParser.model.CsharpNamespace", "CsharpNamespace") + .WithMany() + .HasForeignKey("CsharpNamespaceId"); + + b.HasOne("CSharpParser.model.CsharpAstNode", "ParentNode") + .WithMany() + .HasForeignKey("ParentNodeId"); + + b.Navigation("AstNode"); + + b.Navigation("CsharpNamespace"); + + b.Navigation("ParentNode"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpEnum", b => + { + b.HasOne("CSharpParser.model.CsharpAstNode", "AstNode") + .WithMany() + .HasForeignKey("AstNodeId"); + + b.HasOne("CSharpParser.model.CsharpNamespace", "CsharpNamespace") + .WithMany() + .HasForeignKey("CsharpNamespaceId"); + + b.HasOne("CSharpParser.model.CsharpAstNode", "ParentNode") + .WithMany() + .HasForeignKey("ParentNodeId"); + + b.Navigation("AstNode"); + + b.Navigation("CsharpNamespace"); + + b.Navigation("ParentNode"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpEnumMember", b => + { + b.HasOne("CSharpParser.model.CsharpAstNode", "AstNode") + .WithMany() + .HasForeignKey("AstNodeId"); + + b.HasOne("CSharpParser.model.CsharpEnum", null) + .WithMany("CsharpEnumMembers") + .HasForeignKey("CsharpEnumId"); + + b.HasOne("CSharpParser.model.CsharpAstNode", "ParentNode") + .WithMany() + .HasForeignKey("ParentNodeId"); + + b.Navigation("AstNode"); + + b.Navigation("ParentNode"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpEtcEntity", b => + { + b.HasOne("CSharpParser.model.CsharpAstNode", "AstNode") + .WithMany() + .HasForeignKey("AstNodeId"); + + b.HasOne("CSharpParser.model.CsharpAstNode", "ParentNode") + .WithMany() + .HasForeignKey("ParentNodeId"); + + b.Navigation("AstNode"); + + b.Navigation("ParentNode"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpMethod", b => + { + b.HasOne("CSharpParser.model.CsharpAstNode", "AstNode") + .WithMany() + .HasForeignKey("AstNodeId"); + + b.HasOne("CSharpParser.model.CsharpAstNode", "ParentNode") + .WithMany() + .HasForeignKey("ParentNodeId"); + + b.Navigation("AstNode"); + + b.Navigation("ParentNode"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpNamespace", b => + { + b.HasOne("CSharpParser.model.CsharpAstNode", "AstNode") + .WithMany() + .HasForeignKey("AstNodeId"); + + b.HasOne("CSharpParser.model.CsharpAstNode", "ParentNode") + .WithMany() + .HasForeignKey("ParentNodeId"); + + b.Navigation("AstNode"); + + b.Navigation("ParentNode"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpStruct", b => + { + b.HasOne("CSharpParser.model.CsharpAstNode", "AstNode") + .WithMany() + .HasForeignKey("AstNodeId"); + + b.HasOne("CSharpParser.model.CsharpNamespace", "CsharpNamespace") + .WithMany() + .HasForeignKey("CsharpNamespaceId"); + + b.HasOne("CSharpParser.model.CsharpAstNode", "ParentNode") + .WithMany() + .HasForeignKey("ParentNodeId"); + + b.Navigation("AstNode"); + + b.Navigation("CsharpNamespace"); + + b.Navigation("ParentNode"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpVariable", b => + { + b.HasOne("CSharpParser.model.CsharpAstNode", "AstNode") + .WithMany() + .HasForeignKey("AstNodeId"); + + b.HasOne("CSharpParser.model.CsharpAstNode", "ParentNode") + .WithMany() + .HasForeignKey("ParentNodeId"); + + b.Navigation("AstNode"); + + b.Navigation("ParentNode"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpEnum", b => + { + b.Navigation("CsharpEnumMembers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/plugins/csharp/migrations/20220518134047_Initial.cs b/plugins/csharp/migrations/20220518134047_Initial.cs new file mode 100644 index 000000000..e643a7a6a --- /dev/null +++ b/plugins/csharp/migrations/20220518134047_Initial.cs @@ -0,0 +1,443 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace CSharpParser.Migrations +{ + public partial class Initial : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "CsharpAstNodes", + columns: table => new + { + Id = table.Column(type: "numeric(20,0)", nullable: false), + AstValue = table.Column(type: "text", nullable: true), + AstSymbolType = table.Column(type: "integer", nullable: false), + AstType = table.Column(type: "integer", nullable: false), + Accessibility = table.Column(type: "integer", nullable: false), + Location_range_start_line = table.Column(type: "bigint", nullable: false), + Location_range_start_column = table.Column(type: "bigint", nullable: false), + Location_range_end_line = table.Column(type: "bigint", nullable: false), + Location_range_end_column = table.Column(type: "bigint", nullable: false), + Path = table.Column(type: "text", nullable: true), + EntityHash = table.Column(type: "bigint", nullable: false), + RawKind = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CsharpAstNodes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CsharpEtcEntitys", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + EtcEntityType = table.Column(type: "integer", nullable: false), + DeclaratorNodeId = table.Column(type: "numeric(20,0)", nullable: false), + AstNodeId = table.Column(type: "numeric(20,0)", nullable: true), + ParentNodeId = table.Column(type: "numeric(20,0)", nullable: true), + EntityHash = table.Column(type: "bigint", nullable: false), + Name = table.Column(type: "text", nullable: true), + QualifiedName = table.Column(type: "text", nullable: true), + DocumentationCommentXML = table.Column(type: "text", nullable: true), + TypeHash = table.Column(type: "bigint", nullable: false), + QualifiedType = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_CsharpEtcEntitys", x => x.Id); + table.ForeignKey( + name: "FK_CsharpEtcEntitys_CsharpAstNodes_AstNodeId", + column: x => x.AstNodeId, + principalTable: "CsharpAstNodes", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_CsharpEtcEntitys_CsharpAstNodes_ParentNodeId", + column: x => x.ParentNodeId, + principalTable: "CsharpAstNodes", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "CsharpMethods", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + MethodType = table.Column(type: "integer", nullable: false), + AstNodeId = table.Column(type: "numeric(20,0)", nullable: true), + ParentNodeId = table.Column(type: "numeric(20,0)", nullable: true), + EntityHash = table.Column(type: "bigint", nullable: false), + Name = table.Column(type: "text", nullable: true), + QualifiedName = table.Column(type: "text", nullable: true), + DocumentationCommentXML = table.Column(type: "text", nullable: true), + TypeHash = table.Column(type: "bigint", nullable: false), + QualifiedType = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_CsharpMethods", x => x.Id); + table.ForeignKey( + name: "FK_CsharpMethods_CsharpAstNodes_AstNodeId", + column: x => x.AstNodeId, + principalTable: "CsharpAstNodes", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_CsharpMethods_CsharpAstNodes_ParentNodeId", + column: x => x.ParentNodeId, + principalTable: "CsharpAstNodes", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "CsharpNamespaces", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + AstNodeId = table.Column(type: "numeric(20,0)", nullable: true), + ParentNodeId = table.Column(type: "numeric(20,0)", nullable: true), + EntityHash = table.Column(type: "bigint", nullable: false), + Name = table.Column(type: "text", nullable: true), + QualifiedName = table.Column(type: "text", nullable: true), + DocumentationCommentXML = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_CsharpNamespaces", x => x.Id); + table.ForeignKey( + name: "FK_CsharpNamespaces_CsharpAstNodes_AstNodeId", + column: x => x.AstNodeId, + principalTable: "CsharpAstNodes", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_CsharpNamespaces_CsharpAstNodes_ParentNodeId", + column: x => x.ParentNodeId, + principalTable: "CsharpAstNodes", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "CsharpVariables", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + VariableType = table.Column(type: "integer", nullable: false), + AstNodeId = table.Column(type: "numeric(20,0)", nullable: true), + ParentNodeId = table.Column(type: "numeric(20,0)", nullable: true), + EntityHash = table.Column(type: "bigint", nullable: false), + Name = table.Column(type: "text", nullable: true), + QualifiedName = table.Column(type: "text", nullable: true), + DocumentationCommentXML = table.Column(type: "text", nullable: true), + TypeHash = table.Column(type: "bigint", nullable: false), + QualifiedType = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_CsharpVariables", x => x.Id); + table.ForeignKey( + name: "FK_CsharpVariables_CsharpAstNodes_AstNodeId", + column: x => x.AstNodeId, + principalTable: "CsharpAstNodes", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_CsharpVariables_CsharpAstNodes_ParentNodeId", + column: x => x.ParentNodeId, + principalTable: "CsharpAstNodes", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "CsharpClasses", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ClassType = table.Column(type: "integer", nullable: false), + CsharpNamespaceId = table.Column(type: "bigint", nullable: true), + AstNodeId = table.Column(type: "numeric(20,0)", nullable: true), + ParentNodeId = table.Column(type: "numeric(20,0)", nullable: true), + EntityHash = table.Column(type: "bigint", nullable: false), + Name = table.Column(type: "text", nullable: true), + QualifiedName = table.Column(type: "text", nullable: true), + DocumentationCommentXML = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_CsharpClasses", x => x.Id); + table.ForeignKey( + name: "FK_CsharpClasses_CsharpAstNodes_AstNodeId", + column: x => x.AstNodeId, + principalTable: "CsharpAstNodes", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_CsharpClasses_CsharpAstNodes_ParentNodeId", + column: x => x.ParentNodeId, + principalTable: "CsharpAstNodes", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_CsharpClasses_CsharpNamespaces_CsharpNamespaceId", + column: x => x.CsharpNamespaceId, + principalTable: "CsharpNamespaces", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "CsharpEnums", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + CsharpNamespaceId = table.Column(type: "bigint", nullable: true), + AstNodeId = table.Column(type: "numeric(20,0)", nullable: true), + ParentNodeId = table.Column(type: "numeric(20,0)", nullable: true), + EntityHash = table.Column(type: "bigint", nullable: false), + Name = table.Column(type: "text", nullable: true), + QualifiedName = table.Column(type: "text", nullable: true), + DocumentationCommentXML = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_CsharpEnums", x => x.Id); + table.ForeignKey( + name: "FK_CsharpEnums_CsharpAstNodes_AstNodeId", + column: x => x.AstNodeId, + principalTable: "CsharpAstNodes", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_CsharpEnums_CsharpAstNodes_ParentNodeId", + column: x => x.ParentNodeId, + principalTable: "CsharpAstNodes", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_CsharpEnums_CsharpNamespaces_CsharpNamespaceId", + column: x => x.CsharpNamespaceId, + principalTable: "CsharpNamespaces", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "CsharpStructs", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + CsharpNamespaceId = table.Column(type: "bigint", nullable: true), + AstNodeId = table.Column(type: "numeric(20,0)", nullable: true), + ParentNodeId = table.Column(type: "numeric(20,0)", nullable: true), + EntityHash = table.Column(type: "bigint", nullable: false), + Name = table.Column(type: "text", nullable: true), + QualifiedName = table.Column(type: "text", nullable: true), + DocumentationCommentXML = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_CsharpStructs", x => x.Id); + table.ForeignKey( + name: "FK_CsharpStructs_CsharpAstNodes_AstNodeId", + column: x => x.AstNodeId, + principalTable: "CsharpAstNodes", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_CsharpStructs_CsharpAstNodes_ParentNodeId", + column: x => x.ParentNodeId, + principalTable: "CsharpAstNodes", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_CsharpStructs_CsharpNamespaces_CsharpNamespaceId", + column: x => x.CsharpNamespaceId, + principalTable: "CsharpNamespaces", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "CsharpEnumMembers", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + EqualsValue = table.Column(type: "integer", nullable: false), + CsharpEnumId = table.Column(type: "bigint", nullable: true), + AstNodeId = table.Column(type: "numeric(20,0)", nullable: true), + ParentNodeId = table.Column(type: "numeric(20,0)", nullable: true), + EntityHash = table.Column(type: "bigint", nullable: false), + Name = table.Column(type: "text", nullable: true), + QualifiedName = table.Column(type: "text", nullable: true), + DocumentationCommentXML = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_CsharpEnumMembers", x => x.Id); + table.ForeignKey( + name: "FK_CsharpEnumMembers_CsharpAstNodes_AstNodeId", + column: x => x.AstNodeId, + principalTable: "CsharpAstNodes", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_CsharpEnumMembers_CsharpAstNodes_ParentNodeId", + column: x => x.ParentNodeId, + principalTable: "CsharpAstNodes", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_CsharpEnumMembers_CsharpEnums_CsharpEnumId", + column: x => x.CsharpEnumId, + principalTable: "CsharpEnums", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_CsharpClasses_AstNodeId", + table: "CsharpClasses", + column: "AstNodeId"); + + migrationBuilder.CreateIndex( + name: "IX_CsharpClasses_CsharpNamespaceId", + table: "CsharpClasses", + column: "CsharpNamespaceId"); + + migrationBuilder.CreateIndex( + name: "IX_CsharpClasses_ParentNodeId", + table: "CsharpClasses", + column: "ParentNodeId"); + + migrationBuilder.CreateIndex( + name: "IX_CsharpEnumMembers_AstNodeId", + table: "CsharpEnumMembers", + column: "AstNodeId"); + + migrationBuilder.CreateIndex( + name: "IX_CsharpEnumMembers_CsharpEnumId", + table: "CsharpEnumMembers", + column: "CsharpEnumId"); + + migrationBuilder.CreateIndex( + name: "IX_CsharpEnumMembers_ParentNodeId", + table: "CsharpEnumMembers", + column: "ParentNodeId"); + + migrationBuilder.CreateIndex( + name: "IX_CsharpEnums_AstNodeId", + table: "CsharpEnums", + column: "AstNodeId"); + + migrationBuilder.CreateIndex( + name: "IX_CsharpEnums_CsharpNamespaceId", + table: "CsharpEnums", + column: "CsharpNamespaceId"); + + migrationBuilder.CreateIndex( + name: "IX_CsharpEnums_ParentNodeId", + table: "CsharpEnums", + column: "ParentNodeId"); + + migrationBuilder.CreateIndex( + name: "IX_CsharpEtcEntitys_AstNodeId", + table: "CsharpEtcEntitys", + column: "AstNodeId"); + + migrationBuilder.CreateIndex( + name: "IX_CsharpEtcEntitys_ParentNodeId", + table: "CsharpEtcEntitys", + column: "ParentNodeId"); + + migrationBuilder.CreateIndex( + name: "IX_CsharpMethods_AstNodeId", + table: "CsharpMethods", + column: "AstNodeId"); + + migrationBuilder.CreateIndex( + name: "IX_CsharpMethods_ParentNodeId", + table: "CsharpMethods", + column: "ParentNodeId"); + + migrationBuilder.CreateIndex( + name: "IX_CsharpNamespaces_AstNodeId", + table: "CsharpNamespaces", + column: "AstNodeId"); + + migrationBuilder.CreateIndex( + name: "IX_CsharpNamespaces_ParentNodeId", + table: "CsharpNamespaces", + column: "ParentNodeId"); + + migrationBuilder.CreateIndex( + name: "IX_CsharpStructs_AstNodeId", + table: "CsharpStructs", + column: "AstNodeId"); + + migrationBuilder.CreateIndex( + name: "IX_CsharpStructs_CsharpNamespaceId", + table: "CsharpStructs", + column: "CsharpNamespaceId"); + + migrationBuilder.CreateIndex( + name: "IX_CsharpStructs_ParentNodeId", + table: "CsharpStructs", + column: "ParentNodeId"); + + migrationBuilder.CreateIndex( + name: "IX_CsharpVariables_AstNodeId", + table: "CsharpVariables", + column: "AstNodeId"); + + migrationBuilder.CreateIndex( + name: "IX_CsharpVariables_ParentNodeId", + table: "CsharpVariables", + column: "ParentNodeId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "CsharpClasses"); + + migrationBuilder.DropTable( + name: "CsharpEnumMembers"); + + migrationBuilder.DropTable( + name: "CsharpEtcEntitys"); + + migrationBuilder.DropTable( + name: "CsharpMethods"); + + migrationBuilder.DropTable( + name: "CsharpStructs"); + + migrationBuilder.DropTable( + name: "CsharpVariables"); + + migrationBuilder.DropTable( + name: "CsharpEnums"); + + migrationBuilder.DropTable( + name: "CsharpNamespaces"); + + migrationBuilder.DropTable( + name: "CsharpAstNodes"); + } + } +} diff --git a/plugins/csharp/migrations/CsharpDbContextModelSnapshot.cs b/plugins/csharp/migrations/CsharpDbContextModelSnapshot.cs new file mode 100644 index 000000000..20ebbc516 --- /dev/null +++ b/plugins/csharp/migrations/CsharpDbContextModelSnapshot.cs @@ -0,0 +1,543 @@ +// +using System; +using CSharpParser.model; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace CSharpParser.Migrations +{ + [DbContext(typeof(CsharpDbContext))] + partial class CsharpDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.10") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + modelBuilder.Entity("CSharpParser.model.CsharpAstNode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)"); + + b.Property("Accessibility") + .HasColumnType("integer"); + + b.Property("AstSymbolType") + .HasColumnType("integer"); + + b.Property("AstType") + .HasColumnType("integer"); + + b.Property("AstValue") + .HasColumnType("text"); + + b.Property("EntityHash") + .HasColumnType("bigint"); + + b.Property("Location_range_end_column") + .HasColumnType("bigint"); + + b.Property("Location_range_end_line") + .HasColumnType("bigint"); + + b.Property("Location_range_start_column") + .HasColumnType("bigint"); + + b.Property("Location_range_start_line") + .HasColumnType("bigint"); + + b.Property("Path") + .HasColumnType("text"); + + b.Property("RawKind") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("CsharpAstNodes"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpClass", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AstNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("ClassType") + .HasColumnType("integer"); + + b.Property("CsharpNamespaceId") + .HasColumnType("bigint"); + + b.Property("DocumentationCommentXML") + .HasColumnType("text"); + + b.Property("EntityHash") + .HasColumnType("bigint"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("ParentNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("QualifiedName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("AstNodeId"); + + b.HasIndex("CsharpNamespaceId"); + + b.HasIndex("ParentNodeId"); + + b.ToTable("CsharpClasses"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpEnum", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AstNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("CsharpNamespaceId") + .HasColumnType("bigint"); + + b.Property("DocumentationCommentXML") + .HasColumnType("text"); + + b.Property("EntityHash") + .HasColumnType("bigint"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("ParentNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("QualifiedName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("AstNodeId"); + + b.HasIndex("CsharpNamespaceId"); + + b.HasIndex("ParentNodeId"); + + b.ToTable("CsharpEnums"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpEnumMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AstNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("CsharpEnumId") + .HasColumnType("bigint"); + + b.Property("DocumentationCommentXML") + .HasColumnType("text"); + + b.Property("EntityHash") + .HasColumnType("bigint"); + + b.Property("EqualsValue") + .HasColumnType("integer"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("ParentNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("QualifiedName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("AstNodeId"); + + b.HasIndex("CsharpEnumId"); + + b.HasIndex("ParentNodeId"); + + b.ToTable("CsharpEnumMembers"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpEtcEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AstNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("DeclaratorNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("DocumentationCommentXML") + .HasColumnType("text"); + + b.Property("EntityHash") + .HasColumnType("bigint"); + + b.Property("EtcEntityType") + .HasColumnType("integer"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("ParentNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("QualifiedName") + .HasColumnType("text"); + + b.Property("QualifiedType") + .HasColumnType("text"); + + b.Property("TypeHash") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("AstNodeId"); + + b.HasIndex("ParentNodeId"); + + b.ToTable("CsharpEtcEntitys"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpMethod", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AstNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("DocumentationCommentXML") + .HasColumnType("text"); + + b.Property("EntityHash") + .HasColumnType("bigint"); + + b.Property("MethodType") + .HasColumnType("integer"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("ParentNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("QualifiedName") + .HasColumnType("text"); + + b.Property("QualifiedType") + .HasColumnType("text"); + + b.Property("TypeHash") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("AstNodeId"); + + b.HasIndex("ParentNodeId"); + + b.ToTable("CsharpMethods"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpNamespace", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AstNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("DocumentationCommentXML") + .HasColumnType("text"); + + b.Property("EntityHash") + .HasColumnType("bigint"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("ParentNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("QualifiedName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("AstNodeId"); + + b.HasIndex("ParentNodeId"); + + b.ToTable("CsharpNamespaces"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpStruct", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AstNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("CsharpNamespaceId") + .HasColumnType("bigint"); + + b.Property("DocumentationCommentXML") + .HasColumnType("text"); + + b.Property("EntityHash") + .HasColumnType("bigint"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("ParentNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("QualifiedName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("AstNodeId"); + + b.HasIndex("CsharpNamespaceId"); + + b.HasIndex("ParentNodeId"); + + b.ToTable("CsharpStructs"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AstNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("DocumentationCommentXML") + .HasColumnType("text"); + + b.Property("EntityHash") + .HasColumnType("bigint"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("ParentNodeId") + .HasColumnType("numeric(20,0)"); + + b.Property("QualifiedName") + .HasColumnType("text"); + + b.Property("QualifiedType") + .HasColumnType("text"); + + b.Property("TypeHash") + .HasColumnType("bigint"); + + b.Property("VariableType") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("AstNodeId"); + + b.HasIndex("ParentNodeId"); + + b.ToTable("CsharpVariables"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpClass", b => + { + b.HasOne("CSharpParser.model.CsharpAstNode", "AstNode") + .WithMany() + .HasForeignKey("AstNodeId"); + + b.HasOne("CSharpParser.model.CsharpNamespace", "CsharpNamespace") + .WithMany() + .HasForeignKey("CsharpNamespaceId"); + + b.HasOne("CSharpParser.model.CsharpAstNode", "ParentNode") + .WithMany() + .HasForeignKey("ParentNodeId"); + + b.Navigation("AstNode"); + + b.Navigation("CsharpNamespace"); + + b.Navigation("ParentNode"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpEnum", b => + { + b.HasOne("CSharpParser.model.CsharpAstNode", "AstNode") + .WithMany() + .HasForeignKey("AstNodeId"); + + b.HasOne("CSharpParser.model.CsharpNamespace", "CsharpNamespace") + .WithMany() + .HasForeignKey("CsharpNamespaceId"); + + b.HasOne("CSharpParser.model.CsharpAstNode", "ParentNode") + .WithMany() + .HasForeignKey("ParentNodeId"); + + b.Navigation("AstNode"); + + b.Navigation("CsharpNamespace"); + + b.Navigation("ParentNode"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpEnumMember", b => + { + b.HasOne("CSharpParser.model.CsharpAstNode", "AstNode") + .WithMany() + .HasForeignKey("AstNodeId"); + + b.HasOne("CSharpParser.model.CsharpEnum", null) + .WithMany("CsharpEnumMembers") + .HasForeignKey("CsharpEnumId"); + + b.HasOne("CSharpParser.model.CsharpAstNode", "ParentNode") + .WithMany() + .HasForeignKey("ParentNodeId"); + + b.Navigation("AstNode"); + + b.Navigation("ParentNode"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpEtcEntity", b => + { + b.HasOne("CSharpParser.model.CsharpAstNode", "AstNode") + .WithMany() + .HasForeignKey("AstNodeId"); + + b.HasOne("CSharpParser.model.CsharpAstNode", "ParentNode") + .WithMany() + .HasForeignKey("ParentNodeId"); + + b.Navigation("AstNode"); + + b.Navigation("ParentNode"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpMethod", b => + { + b.HasOne("CSharpParser.model.CsharpAstNode", "AstNode") + .WithMany() + .HasForeignKey("AstNodeId"); + + b.HasOne("CSharpParser.model.CsharpAstNode", "ParentNode") + .WithMany() + .HasForeignKey("ParentNodeId"); + + b.Navigation("AstNode"); + + b.Navigation("ParentNode"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpNamespace", b => + { + b.HasOne("CSharpParser.model.CsharpAstNode", "AstNode") + .WithMany() + .HasForeignKey("AstNodeId"); + + b.HasOne("CSharpParser.model.CsharpAstNode", "ParentNode") + .WithMany() + .HasForeignKey("ParentNodeId"); + + b.Navigation("AstNode"); + + b.Navigation("ParentNode"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpStruct", b => + { + b.HasOne("CSharpParser.model.CsharpAstNode", "AstNode") + .WithMany() + .HasForeignKey("AstNodeId"); + + b.HasOne("CSharpParser.model.CsharpNamespace", "CsharpNamespace") + .WithMany() + .HasForeignKey("CsharpNamespaceId"); + + b.HasOne("CSharpParser.model.CsharpAstNode", "ParentNode") + .WithMany() + .HasForeignKey("ParentNodeId"); + + b.Navigation("AstNode"); + + b.Navigation("CsharpNamespace"); + + b.Navigation("ParentNode"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpVariable", b => + { + b.HasOne("CSharpParser.model.CsharpAstNode", "AstNode") + .WithMany() + .HasForeignKey("AstNodeId"); + + b.HasOne("CSharpParser.model.CsharpAstNode", "ParentNode") + .WithMany() + .HasForeignKey("ParentNodeId"); + + b.Navigation("AstNode"); + + b.Navigation("ParentNode"); + }); + + modelBuilder.Entity("CSharpParser.model.CsharpEnum", b => + { + b.Navigation("CsharpEnumMembers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/plugins/csharp/model/CsharpAstNode.cs b/plugins/csharp/model/CsharpAstNode.cs new file mode 100644 index 000000000..182a2160e --- /dev/null +++ b/plugins/csharp/model/CsharpAstNode.cs @@ -0,0 +1,68 @@ +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace CSharpParser.model +{ + enum AstSymbolTypeEnum + { + Variable, + Method, + Class, + Struct, + Namespace, + Enum, + EnumMember, + EtcEntity + + } + + enum AstTypeEnum + { + Declaration, + Definition, + Usage, + Read, + Write, + Expression + } + + class CsharpAstNode + { + public ulong Id { get; set; } + public string AstValue { get; set; } + public AstSymbolTypeEnum AstSymbolType { get; set; } + public AstTypeEnum AstType { get; set; } + public Accessibility Accessibility { get; set; } + public long Location_range_start_line { get; set; } + public long Location_range_start_column { get; set; } + public long Location_range_end_line { get; set; } + public long Location_range_end_column { get; set; } + public string Path { get; set; } + public long EntityHash { get; set; } + public SyntaxKind RawKind { get; set; } //SyntaxKind Enum + public void SetLocation(FileLinePositionSpan f) + { + Location_range_start_line = f.StartLinePosition.Line+1; + Location_range_start_column = f.StartLinePosition.Character+1; + Location_range_end_line = f.EndLinePosition.Line+1; + Location_range_end_column = f.EndLinePosition.Character+1; + Path = f.Path; + } + public bool isRangeSmaller(CsharpAstNode other){ + if (Location_range_start_line == other.Location_range_start_line){ + if (Location_range_end_line == other.Location_range_end_line){ + return Location_range_end_column - Location_range_start_column < + other.Location_range_end_column - other.Location_range_start_column; + } + return Location_range_end_line < other.Location_range_end_line; + } else if (Location_range_end_line - Location_range_start_line == + other.Location_range_end_line - other.Location_range_start_line){ + return Location_range_end_column - Location_range_start_column < + other.Location_range_end_column - other.Location_range_start_column; + } + return Location_range_end_line - Location_range_start_line < + other.Location_range_end_line - other.Location_range_start_line; + } + } +} diff --git a/plugins/csharp/model/CsharpClass.cs b/plugins/csharp/model/CsharpClass.cs new file mode 100644 index 000000000..fa6642923 --- /dev/null +++ b/plugins/csharp/model/CsharpClass.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; + +namespace CSharpParser.model +{ + enum ClassTypeEnum + { + Class, + Interface, + Record + } + class CsharpClass : CsharpEntity + { + public ClassTypeEnum ClassType { get; set; } + public CsharpNamespace CsharpNamespace { get; set; } + } + //[Table("csharp_structs")] + class CsharpStruct : CsharpEntity + { + public CsharpNamespace CsharpNamespace { get; set; } + } +} diff --git a/plugins/csharp/model/CsharpDbContext.cs b/plugins/csharp/model/CsharpDbContext.cs new file mode 100644 index 000000000..eaef779d1 --- /dev/null +++ b/plugins/csharp/model/CsharpDbContext.cs @@ -0,0 +1,23 @@ +using System.Text; +using Microsoft.EntityFrameworkCore; + +namespace CSharpParser.model +{ + class CsharpDbContext : DbContext + { + public CsharpDbContext(DbContextOptions options) : base(options) { } + + public DbSet CsharpAstNodes { get; set; } + public DbSet CsharpNamespaces { get; set; } + public DbSet CsharpClasses { get; set; } + public DbSet CsharpMethods { get; set; } + public DbSet CsharpVariables { get; set; } + public DbSet CsharpStructs { get; set; } + public DbSet CsharpEnums { get; set; } + public DbSet CsharpEnumMembers { get; set; } + public DbSet CsharpEtcEntitys { get; set; } + + + } + +} diff --git a/plugins/csharp/model/CsharpEntity.cs b/plugins/csharp/model/CsharpEntity.cs new file mode 100644 index 000000000..c7c85b7c7 --- /dev/null +++ b/plugins/csharp/model/CsharpEntity.cs @@ -0,0 +1,33 @@ +using System; + +namespace CSharpParser.model +{ + class CsharpEntity + { + public long Id { get; set; } + public CsharpAstNode AstNode { get; set; } + public CsharpAstNode ParentNode{ get; set; } + public long EntityHash { get; set; } + public String Name { get; set; } = " "; + public String QualifiedName { get; set; } = " "; + public string DocumentationCommentXML { get; set; } = " "; + } + + class CsharpTypedEntity : CsharpEntity + { + public long TypeHash { get; set; } + public String QualifiedType { get; set; } = " "; + } + + enum EtcEntityTypeEnum + { + Event, + Invocation, + ForeachExpr + } + class CsharpEtcEntity : CsharpTypedEntity + { + public EtcEntityTypeEnum EtcEntityType { get; set; } + public ulong DeclaratorNodeId { get; set; } + } +} diff --git a/plugins/csharp/model/CsharpEnum.cs b/plugins/csharp/model/CsharpEnum.cs new file mode 100644 index 000000000..5597d05d8 --- /dev/null +++ b/plugins/csharp/model/CsharpEnum.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; + +namespace CSharpParser.model +{ + //[Table("csharp_enums")] + class CsharpEnum : CsharpEntity + { + public CsharpNamespace CsharpNamespace { get; set; } + public HashSet CsharpEnumMembers { get; set; } = new HashSet(); + + public void AddMember(CsharpEnumMember member) + { + CsharpEnumMembers.Add(member); + } + } +} diff --git a/plugins/csharp/model/CsharpEnumMember.cs b/plugins/csharp/model/CsharpEnumMember.cs new file mode 100644 index 000000000..835147ea1 --- /dev/null +++ b/plugins/csharp/model/CsharpEnumMember.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace CSharpParser.model +{ + //[Table("csharp_enum_members")] + class CsharpEnumMember : CsharpEntity + { + public int EqualsValue { get; set; } + } +} diff --git a/plugins/csharp/model/CsharpMethod.cs b/plugins/csharp/model/CsharpMethod.cs new file mode 100644 index 000000000..087053af3 --- /dev/null +++ b/plugins/csharp/model/CsharpMethod.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace CSharpParser.model +{ + enum MethodTypeEnum + { + Delegate, + Accessor, + Constructor, + Destuctor, + Method, + Operator + } + + class CsharpMethod : CsharpTypedEntity + { + public MethodTypeEnum MethodType { get; set; } + } + +} diff --git a/plugins/csharp/model/CsharpNamespace.cs b/plugins/csharp/model/CsharpNamespace.cs new file mode 100644 index 000000000..8b4cdc4fd --- /dev/null +++ b/plugins/csharp/model/CsharpNamespace.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; + +namespace CSharpParser.model +{ + //[Table("csharp_namespaces")] + class CsharpNamespace : CsharpEntity + { + + } +} diff --git a/plugins/csharp/model/CsharpVariable.cs b/plugins/csharp/model/CsharpVariable.cs new file mode 100644 index 000000000..9b3a7b1e3 --- /dev/null +++ b/plugins/csharp/model/CsharpVariable.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace CSharpParser.model +{ + enum VariableTypeEnum + { + Property, + LINQ, + Parameter, + Variable + } + class CsharpVariable : CsharpTypedEntity + { + public VariableTypeEnum VariableType { get; set; } + } +} diff --git a/plugins/csharp/parser/CMakeLists.txt b/plugins/csharp/parser/CMakeLists.txt new file mode 100644 index 000000000..3ccd12593 --- /dev/null +++ b/plugins/csharp/parser/CMakeLists.txt @@ -0,0 +1,17 @@ +add_subdirectory(src_csharp) +include_directories( + include + ${PROJECT_SOURCE_DIR}/model/include + ${PROJECT_SOURCE_DIR}/util/include + ${PROJECT_SOURCE_DIR}/parser/include + ${CMAKE_BINARY_DIR}/model/include) + +add_library(csharpparser SHARED + src/csharpparser.cpp) + +target_link_libraries(csharpparser + model) + +target_compile_options(csharpparser PUBLIC -Wno-unknown-pragmas) + +install(TARGETS csharpparser DESTINATION ${INSTALL_PARSER_DIR}) diff --git a/plugins/csharp/parser/include/csharpparser/csharpparser.h b/plugins/csharp/parser/include/csharpparser/csharpparser.h new file mode 100644 index 000000000..6435dcf53 --- /dev/null +++ b/plugins/csharp/parser/include/csharpparser/csharpparser.h @@ -0,0 +1,43 @@ +#ifndef CC_PARSER_CSHARPPARSER_H +#define CC_PARSER_CSHARPPARSER_H + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace cc +{ +namespace parser +{ + +namespace fs = boost::filesystem; +namespace bp = boost::process; +namespace pt = boost::property_tree; + +class CsharpParser : public AbstractParser +{ +public: + CsharpParser(ParserContext& ctx_); + virtual ~CsharpParser(); + virtual bool parse() override; +private: + int _numCompileCommands; + int _threadNum; + + bool acceptProjectBuildPath(const std::string& buildDir_); + bool parseProjectBuildPath( + const std::vector& path_, + const std::string& buildPath_); + void addSource(const std::string& filepath_, bool error_); +}; + +} // parser +} // cc + +#endif // CC_PLUGINS_PARSER_DUMMYPARSER_H diff --git a/plugins/csharp/parser/src/csharpparser.cpp b/plugins/csharp/parser/src/csharpparser.cpp new file mode 100644 index 000000000..490c82f56 --- /dev/null +++ b/plugins/csharp/parser/src/csharpparser.cpp @@ -0,0 +1,194 @@ +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +namespace cc +{ +namespace parser +{ + +CsharpParser::CsharpParser(ParserContext& ctx_): AbstractParser(ctx_) +{ + _threadNum = _ctx.options["jobs"].as(); +} +/* +bool CsharpParser::acceptProjectBuildPath(const std::vector& path_) +{ + return path_.size() >= 2 && fs::is_directory(path_[0]) && fs::is_directory(path_[1]); +}*/ +bool CsharpParser::acceptProjectBuildPath(const std::string& buildPath_) +{ + return fs::is_directory(buildPath_); +} + +bool CsharpParser::parse() +{ + bool success = true; + + std::vector paths = _ctx.options["input"].as>(); + std::string buildPath = _ctx.options["build-dir"].as(); + + if (acceptProjectBuildPath(buildPath)) + { + LOG(debug) << "C# parser parse path: " << paths[0]; + LOG(debug) << "Parsed csharp project build path: " << buildPath; + success = success && parseProjectBuildPath(paths, buildPath); + } + else + { + LOG(error) << "Build path must be a directory!"; + success = false; + } + + return success; +} + +bool CsharpParser::parseProjectBuildPath( + const std::vector& paths_, + const std::string& buildPath_) +{ + namespace ch = std::chrono; + std::future log; + fs::path csharp_path = util::findCurrentExecutableDir() + "/../lib/csharp/"; + + /* + * Concatenate the command parameters to pass to the C# parser. + * 1) C# parser binary + * 2) Database connection string + * 3) Project build directory path + * 4) CC lib directory path + * 5) Thread number + * 6+) Source directories + */ + std::string command("./CSharpParser "); + command.append("'"); + command.append(_ctx.options["database"].as()); + command.append("' '"); + command.append(buildPath_); + command.append("' '"); + command.append(csharp_path.string()); + command.append("' "); + command.append(std::to_string(_ctx.options["jobs"].as())); + + for (auto p : paths_) + { + if (fs::is_directory(p)) + { + command.append(" '"); + command.append(p); + command.append("' "); + } + } + + LOG(debug) << "CSharpParser command: " << command; + + ch::steady_clock::time_point begin = ch::steady_clock::now(); + + int result = bp::system(command, bp::start_dir(csharp_path), bp::std_out > log); + + ch::steady_clock::time_point current = ch::steady_clock::now(); + float elapsed_time = ch::duration_cast(current - begin).count(); + LOG(debug) << "CSharp Parse time: " << elapsed_time << " ms"; + + std::string line; + std::stringstream log_str(log.get()); + //LOG(warning) << log_str.str(); + int countFull = 0, countPart = 0; + + while(std::getline(log_str, line, '\n')) + { + if (line[0] == '+' || line[0] == '-') + { + addSource(line.substr(1), line[0] == '-'); + if (line[0] == '+') + { + countFull++; + } + else + { + countPart++; + } + } + } + + ch::steady_clock::time_point after = ch::steady_clock::now(); + elapsed_time = + ch::duration_cast(after - current).count(); + + LOG(debug) << "C# source manage time: " << elapsed_time << " ms"; + LOG(info) << "Number of files fully parsed: " << countFull << + ", partially parsed: " << countPart << ", total: " << countFull+countPart; + + return result == 0; +} + +void CsharpParser::addSource(const std::string& filepath_, bool error_) +{ + util::OdbTransaction transaction(_ctx.db); + + model::BuildActionPtr buildAction(new model::BuildAction); + buildAction->command = " "; + buildAction->type = model::BuildAction::Compile; + + model::BuildSource buildSource; + buildSource.file = _ctx.srcMgr.getFile(filepath_); + buildSource.file->parseStatus = error_ + ? model::File::PSPartiallyParsed + : model::File::PSFullyParsed; + buildSource.file->type = "CS"; + buildSource.action = buildAction; + + _ctx.srcMgr.updateFile(*buildSource.file); + _ctx.srcMgr.persistFiles(); + + transaction([&, this] { + _ctx.db->persist(buildAction); + _ctx.db->persist(buildSource); + }); +} + + +CsharpParser::~CsharpParser() +{ +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreturn-type-c-linkage" +extern "C" +{ + boost::program_options::options_description getOptions() + { + boost::program_options::options_description description("C# Plugin"); + + description.add_options() + ("build-dir,b", po::value()->default_value("Build directory"), + "The build directory of the parsed project."); + + return description; + } + + std::shared_ptr make(ParserContext& ctx_) + { + return std::make_shared(ctx_); + } +} +#pragma clang diagnostic pop + +} // parser +} // cc diff --git a/plugins/csharp/parser/src_csharp/AstVisitor.cs b/plugins/csharp/parser/src_csharp/AstVisitor.cs new file mode 100644 index 000000000..722804bf9 --- /dev/null +++ b/plugins/csharp/parser/src_csharp/AstVisitor.cs @@ -0,0 +1,1007 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using static System.Console; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using CSharpParser.model; +using Microsoft.CodeAnalysis; + +namespace CSharpParser +{ + partial class AstVisitor : CSharpSyntaxWalker + { + private readonly CsharpDbContext DbContext; + private readonly SemanticModel Model; + private readonly SyntaxTree Tree; + + public bool FullyParsed = true; + + public AstVisitor(CsharpDbContext context, SemanticModel model, SyntaxTree tree) + { + this.DbContext = context; + this.Model = model; + this.Tree = tree; + } + + private ulong createIdentifier(CsharpAstNode astNode){ + string[] properties = + { + astNode.AstValue,":", + astNode.AstType.ToString(),":", + astNode.EntityHash.ToString(),":", + astNode.RawKind.ToString(),":", + astNode.Path,":", + astNode.Location_range_start_line.ToString(),":", + astNode.Location_range_start_column.ToString(),":", + astNode.Location_range_end_line.ToString(),":", + astNode.Location_range_end_column.ToString() + }; + + string res = string.Concat(properties); + + //WriteLine(res); + return fnvHash(res); + } + + private ulong fnvHash(string data_) + { + ulong hash = 14695981039346656037; + + int len = data_.Length; + for (int i = 0; i < len; ++i) + { + hash ^= data_[i]; + hash *= 1099511628211; + } + + return hash; + } + + private ulong getAstNodeId(SyntaxNode node){ + CsharpAstNode astNode = new CsharpAstNode + { + AstValue = node.ToString(), + RawKind = node.Kind(), + EntityHash = node.GetHashCode(), + AstType = AstTypeEnum.Declaration + }; + astNode.SetLocation(node.SyntaxTree.GetLineSpan(node.Span)); + var ret = createIdentifier(astNode); + return ret; + } + + private CsharpAstNode AstNode(SyntaxNode node, AstSymbolTypeEnum type, AstTypeEnum astType) + { + Accessibility acc = Accessibility.NotApplicable; + try + { + acc = Model.GetDeclaredSymbol(node).DeclaredAccessibility; + } + catch (Exception) + { + //WriteLine($"Can not get GetDeclaredSymbol of this node: {node}"); + } + CsharpAstNode astNode = new CsharpAstNode + { + AstValue = node.ToString(), + RawKind = node.Kind(), + EntityHash = node.GetHashCode(), + AstSymbolType = type, + AstType = astType, + Accessibility = acc + }; + astNode.SetLocation(Tree.GetLineSpan(node.Span)); + astNode.Id = createIdentifier(astNode); + + if (DbContext.CsharpAstNodes.Find(astNode.Id) == null) + { + DbContext.CsharpAstNodes.Add(astNode); + } + return astNode; + } + + private CsharpAstNode AstNode(SyntaxNode node, AstSymbolTypeEnum type) + { + return AstNode(node, type, AstTypeEnum.Declaration); + } + + public override void VisitUsingDirective(UsingDirectiveSyntax node) + { + base.VisitUsingDirective(node); + } + + public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node) + { + CsharpAstNode astNode = AstNode(node, AstSymbolTypeEnum.Namespace); + string qName = ""; + try + { + qName = Model.GetDeclaredSymbol(node).ToString(); + } + catch (Exception) + { + FullyParsed = false; + } + + CsharpNamespace csharpNamespace = new CsharpNamespace + { + AstNode = astNode, + Name = node.Name.ToString(), + QualifiedName = qName, + DocumentationCommentXML = Model.GetDeclaredSymbol(node).GetDocumentationCommentXml(), + EntityHash = astNode.EntityHash + }; + + var nameSpaces = DbContext.CsharpNamespaces.Where(n => n.Name == csharpNamespace.Name).ToList(); + if (nameSpaces.Count == 0) + { + DbContext.CsharpNamespaces.Add(csharpNamespace); + } + + base.VisitNamespaceDeclaration(node); + } + + public override void VisitInterfaceDeclaration(InterfaceDeclarationSyntax node) + { + CsharpAstNode astNode = AstNode(node, AstSymbolTypeEnum.Class); + base.VisitInterfaceDeclaration(node); + string qName = ""; + try + { + qName = Model.GetDeclaredSymbol(node).ToString(); + } + catch (Exception) + { + FullyParsed = false; + //WriteLine($"Can not get QualifiedName of this name: {node.Identifier}"); + } + + var nameSpaces = DbContext.CsharpNamespaces.Where(n => qName.Contains(n.Name)).ToList(); + CsharpNamespace csharpNamespace = null; + if (nameSpaces.Count != 0) + { + csharpNamespace = nameSpaces.First(); + } + + CsharpClass csharpClass = new CsharpClass + { + ClassType = ClassTypeEnum.Interface, + CsharpNamespace = csharpNamespace, + AstNode = astNode, + Name = node.Identifier.Text, + QualifiedName = qName, + DocumentationCommentXML = Model.GetDeclaredSymbol(node).GetDocumentationCommentXml(), + EntityHash = astNode.EntityHash + }; + + foreach (PropertyDeclarationSyntax propertyDeclaration in node.Members.OfType()) + { + VisitPropertyDecl(propertyDeclaration, astNode); + } + + foreach (MethodDeclarationSyntax methodDeclaration in node.Members.OfType()) + { + VisitMethodDecl(methodDeclaration, astNode); + } + + foreach (OperatorDeclarationSyntax operatorDeclaration in node.Members.OfType()) + { + VisitOperatorDecl(operatorDeclaration, astNode); + } + + DbContext.CsharpClasses.Add(csharpClass); + } + + public override void VisitStructDeclaration(StructDeclarationSyntax node) + { + CsharpAstNode astNode = AstNode(node, AstSymbolTypeEnum.Struct); + base.VisitStructDeclaration(node); + string qName = ""; + try + { + qName = Model.GetDeclaredSymbol(node).ToString(); + } + catch (Exception) + { + FullyParsed = false; + } + + var nameSpaces = DbContext.CsharpNamespaces.Where(n => qName.Contains(n.Name)).ToList(); + CsharpNamespace csharpNamespace = null; + if (nameSpaces.Count != 0) + { + csharpNamespace = nameSpaces.First(); + } + + CsharpStruct csharpStruct = new CsharpStruct + { + CsharpNamespace = csharpNamespace, + AstNode = astNode, + Name = node.Identifier.Text, + QualifiedName = qName, + DocumentationCommentXML = Model.GetDeclaredSymbol(node).GetDocumentationCommentXml(), + EntityHash = astNode.EntityHash + }; + + foreach (VariableDeclarationSyntax variableDeclaration in node.Members.OfType()) + { + VisitVariableDecl(variableDeclaration, astNode); + } + + foreach (PropertyDeclarationSyntax propertyDeclaration in node.Members.OfType()) + { + VisitPropertyDecl(propertyDeclaration, astNode); + VisitAccessors(propertyDeclaration.AccessorList, propertyDeclaration.Identifier.Text, astNode); + } + + foreach (MethodDeclarationSyntax methodDeclaration in node.Members.OfType()) + { + VisitMethodDecl(methodDeclaration, astNode); + } + + foreach (OperatorDeclarationSyntax operatorDeclaration in node.Members.OfType()) + { + VisitOperatorDecl(operatorDeclaration, astNode); + } + + foreach (DelegateDeclarationSyntax delegateDeclaration in node.Members.OfType()) + { + VisitDelegateDecl(delegateDeclaration, astNode); + } + + foreach (ConstructorDeclarationSyntax constructorDeclaration in node.Members.OfType()) + { + VisitConstructorDecl(constructorDeclaration, astNode); + } + + foreach (DestructorDeclarationSyntax destructorDeclaration in node.Members.OfType()) + { + VisitDestructorDecl(destructorDeclaration, astNode); + } + + foreach (EventDeclarationSyntax eventDeclaration in node.Members.OfType()) + { + CsharpAstNode astNode2 = AstNode(eventDeclaration, AstSymbolTypeEnum.EtcEntity); + string qName2 = ""; + try + { + qName2 = Model.GetDeclaredSymbol(node).ToString(); + } + catch (Exception) + { + //WriteLine($"Can not get QualifiedName of this name: {node.Identifier}"); + } + CsharpEtcEntity csharpEntity = new CsharpEtcEntity + { + AstNode = astNode2, + Name = eventDeclaration.Identifier.Text, + QualifiedName = qName, + DocumentationCommentXML = Model.GetDeclaredSymbol(eventDeclaration).GetDocumentationCommentXml(), + EntityHash = astNode.EntityHash, + ParentNode = astNode, + EtcEntityType = EtcEntityTypeEnum.Event + }; + DbContext.CsharpEtcEntitys.Add(csharpEntity); + } + + DbContext.CsharpStructs.Add(csharpStruct); + } + + public override void VisitClassDeclaration(ClassDeclarationSyntax node) + { + CsharpAstNode astNode = AstNode(node, AstSymbolTypeEnum.Class); + base.VisitClassDeclaration(node); + string qName = ""; + try + { + qName = Model.GetDeclaredSymbol(node).ToString(); + } + catch (Exception) + { + FullyParsed = false; + } + + var nameSpaces = DbContext.CsharpNamespaces.Where(n => qName.Contains(n.Name)).ToList(); + CsharpNamespace csharpNamespace = null; + if (nameSpaces.Count != 0) + { + csharpNamespace = nameSpaces.First(); + } + + CsharpClass csharpClass = new CsharpClass + { + CsharpNamespace = csharpNamespace, + AstNode = astNode, + Name = node.Identifier.Text, + QualifiedName = qName, + DocumentationCommentXML = Model.GetDeclaredSymbol(node).GetDocumentationCommentXml(), + EntityHash = astNode.EntityHash + }; + + foreach (var variableDeclaration in node.Members.OfType()) + { + VisitVariableDecl(variableDeclaration.Declaration, astNode); + } + + foreach (PropertyDeclarationSyntax propertyDeclaration in node.Members.OfType()) + { + VisitPropertyDecl(propertyDeclaration, astNode); + VisitAccessors(propertyDeclaration.AccessorList, propertyDeclaration.Identifier.Text, astNode); + } + + foreach (MethodDeclarationSyntax methodDeclaration in node.Members.OfType()) + { + VisitMethodDecl(methodDeclaration, astNode); + } + + foreach (OperatorDeclarationSyntax operatorDeclaration in node.Members.OfType()) + { + VisitOperatorDecl(operatorDeclaration, astNode); + } + + foreach (DelegateDeclarationSyntax delegateDeclaration in node.Members.OfType()) + { + VisitDelegateDecl(delegateDeclaration, astNode); + } + + foreach (ConstructorDeclarationSyntax constructorDeclaration in node.Members.OfType()) + { + VisitConstructorDecl(constructorDeclaration, astNode); + } + + foreach (DestructorDeclarationSyntax destructorDeclaration in node.Members.OfType()) + { + VisitDestructorDecl(destructorDeclaration,astNode); + } + + foreach (EventDeclarationSyntax eventDeclaration in node.Members.OfType()) + { + CsharpAstNode astNode2 = AstNode(eventDeclaration,AstSymbolTypeEnum.EtcEntity); + string qName2 = ""; + try + { + qName2 = Model.GetDeclaredSymbol(node).ToString(); + } + catch (Exception) + { + //WriteLine($"Can not get QualifiedName of this name: {node.Identifier}"); + } + CsharpEtcEntity csharpEntity = new CsharpEtcEntity + { + AstNode = astNode2, + EtcEntityType = EtcEntityTypeEnum.Event, + Name = eventDeclaration.Identifier.Text, + QualifiedName = qName, + DocumentationCommentXML = Model.GetDeclaredSymbol(eventDeclaration).GetDocumentationCommentXml(), + EntityHash = astNode.EntityHash, + ParentNode = astNode + }; + DbContext.CsharpEtcEntitys.Add(csharpEntity); + } + + DbContext.CsharpClasses.Add(csharpClass); + } + + public override void VisitRecordDeclaration(RecordDeclarationSyntax node) { + CsharpAstNode astNode = AstNode(node, AstSymbolTypeEnum.Class); + base.VisitRecordDeclaration(node); + string qName = ""; + try + { + qName = Model.GetDeclaredSymbol(node).ToString(); + } + catch (Exception) + { + FullyParsed = false; + } + + var nameSpaces = DbContext.CsharpNamespaces.Where(n => qName.Contains(n.Name)).ToList(); + CsharpNamespace csharpNamespace = null; + if (nameSpaces.Count == 1) + { + csharpNamespace = nameSpaces.First(); + } + + CsharpClass csharpRecord = new CsharpClass + { + ClassType = ClassTypeEnum.Record, + CsharpNamespace = csharpNamespace, + AstNode = astNode, + Name = node.Identifier.Text, + QualifiedName = qName, + DocumentationCommentXML = Model.GetDeclaredSymbol(node).GetDocumentationCommentXml(), + EntityHash = astNode.EntityHash + }; + + foreach (var variableDeclaration in node.Members.OfType()) + { + VisitVariableDecl(variableDeclaration.Declaration, astNode); + } + + foreach (PropertyDeclarationSyntax propertyDeclaration in node.Members.OfType()) + { + VisitPropertyDecl(propertyDeclaration, astNode); + VisitAccessors(propertyDeclaration.AccessorList, propertyDeclaration.Identifier.Text, astNode); + } + + foreach (MethodDeclarationSyntax methodDeclaration in node.Members.OfType()) + { + VisitMethodDecl(methodDeclaration, astNode); + } + + foreach (OperatorDeclarationSyntax operatorDeclaration in node.Members.OfType()) + { + VisitOperatorDecl(operatorDeclaration, astNode); + } + + foreach (DelegateDeclarationSyntax delegateDeclaration in node.Members.OfType()) + { + VisitDelegateDecl(delegateDeclaration, astNode); + } + + foreach (ConstructorDeclarationSyntax constructorDeclaration in node.Members.OfType()) + { + VisitConstructorDecl(constructorDeclaration, astNode); + } + + foreach (DestructorDeclarationSyntax destructorDeclaration in node.Members.OfType()) + { + VisitDestructorDecl(destructorDeclaration, astNode); + } + + foreach (EventDeclarationSyntax eventDeclaration in node.Members.OfType()) + { + CsharpAstNode astNode2 = AstNode(eventDeclaration, AstSymbolTypeEnum.EtcEntity); + string qName2 = ""; + try + { + qName2 = Model.GetDeclaredSymbol(node).ToString(); + } + catch (Exception) + { + //WriteLine($"Can not get QualifiedName of this name: {node.Identifier}"); + } + CsharpEtcEntity csharpEntity = new CsharpEtcEntity + { + AstNode = astNode2, + EtcEntityType = EtcEntityTypeEnum.Event, + Name = eventDeclaration.Identifier.Text, + QualifiedName = qName, + DocumentationCommentXML = Model.GetDeclaredSymbol(eventDeclaration).GetDocumentationCommentXml(), + EntityHash = astNode.EntityHash, + ParentNode = astNode + }; + DbContext.CsharpEtcEntitys.Add(csharpEntity); + } + + DbContext.CsharpClasses.Add(csharpRecord); + } + + private void VisitDelegateDecl(DelegateDeclarationSyntax node, CsharpAstNode parent) + { + CsharpAstNode astNode = AstNode(node,AstSymbolTypeEnum.Method); + string qName = ""; + try + { + qName = Model.GetDeclaredSymbol(node).ToString(); + } + catch (Exception) + { + FullyParsed = false; + } + + CsharpMethod method = new CsharpMethod + { + AstNode = astNode, + Name = node.Identifier.Text, + QualifiedName = qName, + DocumentationCommentXML = Model.GetDeclaredSymbol(node).GetDocumentationCommentXml(), + EntityHash = astNode.EntityHash, + ParentNode = parent, + MethodType = MethodTypeEnum.Delegate + }; + + if (node.ParameterList.Parameters.Count > 0) + { + VisitMethodParameters(node.ParameterList.Parameters, astNode); + } + + DbContext.CsharpMethods.Add(method); + } + + private void VisitDestructorDecl(DestructorDeclarationSyntax node, CsharpAstNode parent) + { + CsharpAstNode astNode = AstNode(node, AstSymbolTypeEnum.Method); + string qName = ""; + try + { + qName = Model.GetDeclaredSymbol(node).ToString(); + } + catch (Exception) + { + FullyParsed = false; + } + + CsharpMethod method = new CsharpMethod + { + AstNode = astNode, + Name = node.Identifier.Text, + QualifiedName = qName, + DocumentationCommentXML = Model.GetDeclaredSymbol(node).GetDocumentationCommentXml(), + EntityHash = astNode.EntityHash, + ParentNode = parent, + MethodType = MethodTypeEnum.Destuctor + }; + + if (node.ParameterList.Parameters.Count > 0) + { + VisitMethodParameters(node.ParameterList.Parameters,astNode); + } + + foreach (VariableDeclarationSyntax variableDeclaration in node.DescendantNodes().OfType()) + { + VisitVariableDecl(variableDeclaration, astNode); + } + + DbContext.CsharpMethods.Add(method); + } + + private void VisitConstructorDecl(ConstructorDeclarationSyntax node, CsharpAstNode parent) + { + CsharpAstNode astNode = AstNode(node, AstSymbolTypeEnum.Method); + string qName = ""; + try + { + qName = Model.GetDeclaredSymbol(node).ToString(); + } + catch (Exception) + { + FullyParsed = false; + } + + CsharpMethod method = new CsharpMethod + { + AstNode = astNode, + Name = node.Identifier.Text, + QualifiedName = qName, + DocumentationCommentXML = Model.GetDeclaredSymbol(node).GetDocumentationCommentXml(), + EntityHash = astNode.EntityHash, + ParentNode = parent, + MethodType = MethodTypeEnum.Constructor + }; + + if (node.ParameterList.Parameters.Count > 0) + { + VisitMethodParameters(node.ParameterList.Parameters,astNode); + } + + foreach (VariableDeclarationSyntax variableDeclaration in node.DescendantNodes().OfType()) + { + VisitVariableDecl(variableDeclaration, astNode); + } + + DbContext.CsharpMethods.Add(method); + } + + private void VisitMethodDecl(MethodDeclarationSyntax node, CsharpAstNode parent) + { + CsharpAstNode astNode = AstNode(node, AstSymbolTypeEnum.Method); + string qName = ""; + try + { + qName = Model.GetDeclaredSymbol(node).ToString(); + } + catch (Exception) + { + FullyParsed = false; + } + + string qType = ""; + try + { + qType = Model.GetSymbolInfo(node.ReturnType).Symbol.ToString(); + } + catch (Exception) + { + FullyParsed = false; + } + + CsharpMethod method = new CsharpMethod + { + AstNode = astNode, + Name = node.Identifier.Text, + QualifiedName = qName, + QualifiedType = qType, + DocumentationCommentXML = Model.GetDeclaredSymbol(node).GetDocumentationCommentXml(), + TypeHash = qType.GetHashCode(), + EntityHash = astNode.EntityHash, + ParentNode = parent, + MethodType = MethodTypeEnum.Method + }; + + if (node.ParameterList.Parameters.Count > 0) + { + VisitMethodParameters(node.ParameterList.Parameters,astNode); + } + + foreach (VariableDeclarationSyntax variableDeclaration in node.DescendantNodes().OfType()) + { + VisitVariableDecl(variableDeclaration, astNode); + } + + DbContext.CsharpMethods.Add(method); + } + + private void VisitOperatorDecl(OperatorDeclarationSyntax node, CsharpAstNode parent) + { + CsharpAstNode astNode = AstNode(node, AstSymbolTypeEnum.Method); + string qName = ""; + string Name = ""; + try + { + qName = Model.GetDeclaredSymbol(node).ToString(); + Name = Model.GetDeclaredSymbol(node).Name; + } + catch (Exception) + { + FullyParsed = false; + } + string qType = ""; + try + { + qType = Model.GetSymbolInfo(node.ReturnType).Symbol.ToString(); + } + catch (Exception) + { + FullyParsed = false; + } + + CsharpMethod csharpOperator = new CsharpMethod + { + AstNode = astNode, + Name = Name, + QualifiedName = qName, + QualifiedType = qType, + DocumentationCommentXML = Model.GetDeclaredSymbol(node).GetDocumentationCommentXml(), + TypeHash = qType.GetHashCode(), + EntityHash = astNode.EntityHash, + ParentNode = parent, + MethodType = MethodTypeEnum.Operator + }; + + if (node.ParameterList.Parameters.Count > 0) + { + VisitMethodParameters(node.ParameterList.Parameters,astNode); + } + + foreach (VariableDeclarationSyntax variableDeclaration in node.DescendantNodes().OfType()) + { + VisitVariableDecl(variableDeclaration, astNode); + } + + DbContext.CsharpMethods.Add(csharpOperator); + } + + private void VisitMethodParameters(SeparatedSyntaxList parameters, CsharpAstNode parent) + { + foreach (var param in parameters) + { + CsharpAstNode astNode = AstNode(param, AstSymbolTypeEnum.Variable); + string paramQType = ""; + try + { + paramQType = Model.GetSymbolInfo(param.Type).Symbol.ToString(); + } + catch (Exception) + { + FullyParsed = false; + } + CsharpVariable varibale = new CsharpVariable + { + AstNode = astNode, + Name = param.Identifier.Text, + QualifiedType = paramQType, + TypeHash = paramQType.GetHashCode(), + EntityHash = astNode.EntityHash, + ParentNode = parent, + VariableType = VariableTypeEnum.Parameter + }; + DbContext.CsharpVariables.Add(varibale); + } + } + + private void VisitVariableDecl(VariableDeclarationSyntax node, CsharpAstNode parent) + { + foreach (var variable in node.Variables) + { + CsharpAstNode astNode = AstNode(variable, AstSymbolTypeEnum.Variable); + string varQType = ""; + bool isLINQvar = node.DescendantNodes().OfType().Any(); + try + { + if (node.Type.ToString() == "var"){ + varQType = Model.GetOperation(variable.Initializer.Value).Type.ToString(); + } else { + varQType = Model.GetSymbolInfo(node.Type).Symbol.ToString(); + } + } + catch (Exception) + { + FullyParsed = false; + } + + foreach (var member in node.DescendantNodes().OfType()) + { + isLINQvar = isLINQvar || member.DescendantNodes().OfType() + .Where(memb => new string[]{"Where", "OfType", "Select", "SelectMany"} + .Contains(memb.Identifier.ValueText)).Any(); + } + + isLINQvar = isLINQvar && (varQType.Contains("IEnumerable") + || varQType.Contains("IOrderedEnumerable") + || varQType.Contains("IQueryable")); + + //if (isLINQvar) WriteLine($"LINQvar node: '{node}' QualifiedType: '{varQType}'"); + //if (varQType == "?") WriteLine($"LINQvar ? node: '{node}' QualifiedType: '{varQType}'"); + + CsharpVariable csharpVariable = new CsharpVariable + { + AstNode = astNode, + Name = variable.Identifier.Text, + QualifiedType = varQType, + TypeHash = varQType.GetHashCode(), + DocumentationCommentXML = Model.GetDeclaredSymbol(variable).GetDocumentationCommentXml(), + EntityHash = astNode.EntityHash, + VariableType = isLINQvar ? VariableTypeEnum.LINQ : VariableTypeEnum.Variable, + ParentNode = parent + }; + DbContext.CsharpVariables.Add(csharpVariable); + } + } + + private void VisitPropertyDecl(PropertyDeclarationSyntax node, CsharpAstNode parent) + { + CsharpAstNode astNode = AstNode(node, AstSymbolTypeEnum.Variable); + string varQType = ""; + try + { + varQType = Model.GetSymbolInfo(node.Type).Symbol.ToString(); + } + catch (Exception) + { + FullyParsed = false; + } + CsharpVariable variable = new CsharpVariable + { + AstNode = astNode, + Name = node.Identifier.Text, + QualifiedType = varQType, + TypeHash = varQType.GetHashCode(), + VariableType = VariableTypeEnum.Property, + DocumentationCommentXML = Model.GetDeclaredSymbol(node).GetDocumentationCommentXml(), + EntityHash = astNode.EntityHash, + ParentNode = parent + }; + DbContext.CsharpVariables.Add(variable); + } + + private void VisitAccessors(AccessorListSyntax node, String propertyName, CsharpAstNode parent) + { + HashSet methods = new HashSet(); + + if (node == null) return; + + foreach (AccessorDeclarationSyntax accessor in node.Accessors) + { + CsharpAstNode astNode = AstNode(accessor, AstSymbolTypeEnum.Method); + + String name = ""; + switch (accessor.Kind()) + { + case SyntaxKind.GetAccessorDeclaration: + name = ".Get"; + break; + case SyntaxKind.SetAccessorDeclaration: + name = ".Set"; + break; + case SyntaxKind.InitAccessorDeclaration: + name = ".Init"; + break; + case SyntaxKind.AddAccessorDeclaration: + name = ".Add"; + break; + case SyntaxKind.RemoveAccessorDeclaration: + name = ".Remove"; + break; + case SyntaxKind.UnknownAccessorDeclaration: + name = ".Unknown"; + break; + } + + CsharpMethod method = new CsharpMethod + { + AstNode = astNode, + Name = propertyName+name+"Accessor", + DocumentationCommentXML = Model.GetDeclaredSymbol(accessor).GetDocumentationCommentXml(), + EntityHash = astNode.EntityHash, + ParentNode = parent, + MethodType = MethodTypeEnum.Accessor + }; + + foreach (VariableDeclarationSyntax variableDeclaration in accessor.DescendantNodes().OfType()) + { + VisitVariableDecl(variableDeclaration, astNode); + } + + DbContext.CsharpMethods.Add(method); + } + } + + public override void VisitEnumDeclaration(EnumDeclarationSyntax node) + { + //WriteLine($"\n EnumDeclaration visited: {node.Identifier.Text}"); + CsharpAstNode astNode = AstNode(node, AstSymbolTypeEnum.Enum); + string qName = ""; + try + { + qName = Model.GetDeclaredSymbol(node).ToString(); + } + catch (Exception) + { + FullyParsed = false; + } + + var nameSpaces = DbContext.CsharpNamespaces.Where(n => qName.Contains(n.Name)).ToList(); + CsharpNamespace csharpNamespace = null; + if (nameSpaces.Count == 1) + { + csharpNamespace = nameSpaces.First(); + } + + CsharpEnum csharpEnum = new CsharpEnum + { + CsharpNamespace = csharpNamespace, + AstNode = astNode, + Name = node.Identifier.Text, + QualifiedName = qName, + DocumentationCommentXML = Model.GetDeclaredSymbol(node).GetDocumentationCommentXml(), + EntityHash = astNode.EntityHash + }; + + foreach (EnumMemberDeclarationSyntax enumMemberDeclarationSyntax in node.Members) + { + csharpEnum.AddMember(VisitEnumMemberDecl(enumMemberDeclarationSyntax, astNode)); + } + DbContext.CsharpEnums.Add(csharpEnum); + } + + private CsharpEnumMember VisitEnumMemberDecl(EnumMemberDeclarationSyntax node, CsharpAstNode parent) + { + CsharpAstNode astNode = AstNode(node, AstSymbolTypeEnum.EnumMember); + string qName = ""; + try + { + qName = Model.GetDeclaredSymbol(node).ToString(); + } + catch (Exception) + { + FullyParsed = false; + } + CsharpEnumMember csharpEnumMember = new CsharpEnumMember + { + AstNode = astNode, + Name = node.Identifier.Text, + QualifiedName = qName, + EntityHash = astNode.EntityHash, + ParentNode = parent + }; + if (node.EqualsValue != null) + { + try + { + csharpEnumMember.EqualsValue = int.Parse(node.EqualsValue.Value.ToString()); + } + catch (FormatException) + { + //WriteLine($"Unable to parse '{node.EqualsValue.Value}'"); + } + } + DbContext.CsharpEnumMembers.Add(csharpEnumMember); + return csharpEnumMember; + } + + public override void VisitInvocationExpression(InvocationExpressionSyntax node) + { + if (node.Expression.GetFirstToken().GetNextToken().ToString() == ".") //object + { + var symbol = Model.GetSymbolInfo(node.Expression.GetFirstToken().Parent).Symbol; + if (symbol != null) + { + foreach (var declaration in symbol.DeclaringSyntaxReferences) + { + if (declaration.GetSyntax().Kind() == SyntaxKind.VariableDeclarator) + { + var doc = ""; + try + { + doc = Model.GetDeclaredSymbol(declaration.GetSyntax()) + .GetDocumentationCommentXml(); + } + catch (Exception) + { + FullyParsed = false; + } + var info = Model.GetTypeInfo(node).ConvertedType; + var declaratorNodeId = getAstNodeId(declaration.GetSyntax()); + var astNode = AstNode(node, AstSymbolTypeEnum.EtcEntity, AstTypeEnum.Usage); + CsharpEtcEntity invoc = new CsharpEtcEntity + { + AstNode = astNode, + DocumentationCommentXML = doc, + EntityHash = astNode.EntityHash, + //ParentNode = DbContext.CsharpAstNodes.Find(astNode.Id), + EtcEntityType = EtcEntityTypeEnum.Invocation, + DeclaratorNodeId = declaratorNodeId, + Name = node.Expression.GetFirstToken().ToString(), + QualifiedType = info.Name + }; + DbContext.CsharpEtcEntitys.Add(invoc); + } + } + } + } + + base.VisitInvocationExpression(node); + } + + public override void VisitIdentifierName(IdentifierNameSyntax node) + { + + var symbol = Model.GetSymbolInfo(node).Symbol; + if (symbol != null && symbol.DeclaringSyntaxReferences.Count() == 1) + { + var declaration = symbol.DeclaringSyntaxReferences.First(); + + if (declaration.GetSyntax().Kind() != SyntaxKind.CompilationUnit ) + { + var doc = ""; + var type = ""; + + try + { + var info = Model.GetTypeInfo(node).ConvertedType; + if (info != null) type = info.Name; + } + catch (Exception) + { + FullyParsed = false; + } + var kind = declaration.GetSyntax().Kind() == SyntaxKind.ForEachStatement ? + EtcEntityTypeEnum.ForeachExpr : EtcEntityTypeEnum.Invocation; + if (node.Parent.Parent.Kind() != SyntaxKind.InvocationExpression) + { + var declaratorNodeId = getAstNodeId(declaration.GetSyntax()); + var astNode = AstNode(node, AstSymbolTypeEnum.EtcEntity, AstTypeEnum.Usage); + CsharpEtcEntity expr = new CsharpEtcEntity + { + AstNode = astNode, + DocumentationCommentXML = doc, + EntityHash = astNode.EntityHash, + EtcEntityType = kind, + DeclaratorNodeId = declaratorNodeId, + Name = node.ToString(), + QualifiedType = type + }; + DbContext.CsharpEtcEntitys.Add(expr); + } + + } + + } + + base.VisitIdentifierName(node); + + } + } +} diff --git a/plugins/csharp/parser/src_csharp/CMakeLists.txt b/plugins/csharp/parser/src_csharp/CMakeLists.txt new file mode 100644 index 000000000..5dae671b2 --- /dev/null +++ b/plugins/csharp/parser/src_csharp/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.5.1) +project(CodeCompassCSharp) + +add_custom_target(dotnetbuild ALL +COMMAND dotnet build -r linux-x64 -o ${CMAKE_CURRENT_BINARY_DIR}/csharp +WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" +) + +install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/csharp +DESTINATION ${INSTALL_LIB_DIR} +USE_SOURCE_PERMISSIONS) diff --git a/plugins/csharp/parser/src_csharp/CSharpParser.csproj b/plugins/csharp/parser/src_csharp/CSharpParser.csproj new file mode 100644 index 000000000..df518b334 --- /dev/null +++ b/plugins/csharp/parser/src_csharp/CSharpParser.csproj @@ -0,0 +1,27 @@ + + + + Exe + net6.0 + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + diff --git a/plugins/csharp/parser/src_csharp/Program.cs b/plugins/csharp/parser/src_csharp/Program.cs new file mode 100644 index 000000000..a9b2b23a5 --- /dev/null +++ b/plugins/csharp/parser/src_csharp/Program.cs @@ -0,0 +1,290 @@ +using static System.Console; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.EntityFrameworkCore; +using System; +using System.IO; +using System.Collections; +using System.Collections.Generic; +using System.Threading.Tasks; +using CSharpParser.model; + +namespace CSharpParser +{ + class Program + { + //private readonly CsharpDbContext _context; + private static List _rootDir; + private static string _buildDir = ""; + private static string _buildDirBase = ""; + private static string _connectionString = ""; + + static int Main(string[] args) + { + _rootDir = new List(); + int threadNum = 4; + + try + { + _connectionString = args[0].Replace("'", ""); + _buildDir = args[1].Replace("'", ""); + _buildDirBase = args[2].Replace("'", ""); + threadNum = int.Parse(args[3]); + + for (int i = 4; i < args.Length; ++i) + { + _rootDir.Add(args[i].Replace("'", "")); + } + } + catch (Exception e) + { + WriteLine("Error in parsing command!"); + return 1; + } + /*if (args.Length < 3) + { + WriteLine("Missing command-line arguments in CSharpParser!"); + return 1; + } + else if (args.Length == 3) + { + _connectionString = args[0].Replace("'", ""); + _rootDir = args[1].Replace("'", ""); + _buildDir = args[2].Replace("'", ""); + } + else if (args.Length == 4) + { + _connectionString = args[0].Replace("'", ""); + _rootDir = args[1].Replace("'", ""); + _buildDir = args[2].Replace("'", ""); + bool success = int.TryParse(args[3], out threadNum); + if (!success){ + WriteLine("Invalid threadnumber argument! Multithreaded parsing disabled!"); + } + } + else if (args.Length == 5) + { + _connectionString = args[0].Replace("'", ""); + _rootDir = args[1].Replace("'", ""); + _buildDir = args[2].Replace("'", ""); + _buildDirBase = args[3].Replace("'", ""); + bool success = int.TryParse(args[4], out threadNum); + if (!success) + { + WriteLine("Invalid threadnumber argument! Multithreaded parsing disabled!"); + } + } + else if (args.Length > 5) + { + WriteLine("Too many command-line arguments in CSharpParser!"); + return 1; + }*/ + + //Converting the connectionstring into entiy framwork style connectionstring + string csharpConnectionString = transformConnectionString(); + + var options = new DbContextOptionsBuilder() + .UseNpgsql(csharpConnectionString) + .Options; + + CsharpDbContext _context = new CsharpDbContext(options); + _context.Database.Migrate(); + + List allFiles = new List(); + foreach (var p in _rootDir) + { + Console.WriteLine(p); + allFiles.AddRange(GetSourceFilesFromDir(p, ".cs")); + } + + foreach (var f in allFiles) + { + WriteLine(f); + } + IEnumerable assemblies = GetSourceFilesFromDir(_buildDir, ".dll"); + IEnumerable assemblies_base = assemblies; + if (args.Length == 5) + assemblies_base = GetSourceFilesFromDir(_buildDirBase, ".dll"); + + List trees = new List(); + foreach (string file in allFiles) + { + string programText = File.ReadAllText(file); + SyntaxTree tree = CSharpSyntaxTree.ParseText(programText, null, file); + trees.Add(tree); + } + Write(trees.Count); + + CSharpCompilation compilation = CSharpCompilation.Create("CSharpCompilation") + .AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location)) + .AddSyntaxTrees(trees); + + foreach (string file in assemblies_base) + { + compilation = compilation.AddReferences(MetadataReference.CreateFromFile(file)); + } + foreach (string file in assemblies) + { + compilation = compilation.AddReferences(MetadataReference.CreateFromFile(file)); + } + + var runtask = ParalellRun(csharpConnectionString, threadNum, trees, compilation); + int ret = runtask.Result; + + return 0; + } + + private static async Task ParalellRun(string csharpConnectionString, int threadNum, + List trees, CSharpCompilation compilation) + { + var options = new DbContextOptionsBuilder() + .UseNpgsql(csharpConnectionString) + .Options; + CsharpDbContext dbContext = new CsharpDbContext(options); + + var contextList = new List(); + contextList.Add(dbContext); + for (int i = 1; i < threadNum; i++) + { + CsharpDbContext dbContextInstance = new CsharpDbContext(options); + contextList.Add(dbContextInstance); + } + + var ParsingTasks = new List>(); + int maxThread = threadNum < trees.Count() ? threadNum : trees.Count(); + WriteLine(threadNum); + for (int i = 0; i < maxThread; i++) + { + ParsingTasks.Add(ParseTree(contextList[i],trees[i],compilation,i)); + } + + int nextTreeIndex = maxThread; + while (ParsingTasks.Count > 0) + { + var finshedTask = await Task.WhenAny(ParsingTasks); + int nextContextIndex = await finshedTask; + + ParsingTasks.Remove(finshedTask); + if (nextTreeIndex < trees.Count) + { + ParsingTasks.Add(ParseTree(contextList[nextContextIndex], + trees[nextTreeIndex],compilation,nextContextIndex)); + ++nextTreeIndex; + } + } + + foreach (var ctx in contextList) + { + ctx.SaveChanges(); + } + + return 0; + } + + private static async Task ParseTree(CsharpDbContext context, + SyntaxTree tree, CSharpCompilation compilation, int index) + { + var ParsingTask = Task.Run(() => + { + WriteLine("ParallelRun " + tree.FilePath); + SemanticModel model = compilation.GetSemanticModel(tree); + var visitor = new AstVisitor(context, model, tree); + visitor.Visit(tree.GetCompilationUnitRoot()); + WriteLine((visitor.FullyParsed ? "+" : "-") + tree.FilePath); + return index; + }); + return await ParsingTask; + } + + public static IEnumerable GetSourceFilesFromDir(string root, string extension) + { + IEnumerable allFiles = new string[]{}; + // Data structure to hold names of subfolders. + ArrayList dirs = new ArrayList(); + + if (!System.IO.Directory.Exists(root)) + { + throw new ArgumentException(); + } + dirs.Add(root); + + while (dirs.Count > 0) + { + string currentDir = dirs[0].ToString(); + dirs.RemoveAt(0); + string[] subDirs; + try + { + subDirs = System.IO.Directory.GetDirectories(currentDir); + } + catch (UnauthorizedAccessException e) + { + WriteLine(e.Message); + continue; + } + catch (System.IO.DirectoryNotFoundException e) + { + WriteLine(e.Message); + continue; + } + + // Add the subdirectories for traversal. + dirs.AddRange(subDirs); + + string[] files = null; + try + { + files = System.IO.Directory.GetFiles(currentDir); + } + catch (UnauthorizedAccessException e) + { + Console.WriteLine(e.Message); + continue; + } + catch (System.IO.DirectoryNotFoundException e) + { + Console.WriteLine(e.Message); + continue; + } + + foreach (string file in files) + { + try + { + System.IO.FileInfo fi = new System.IO.FileInfo(file); + if (fi.Extension == extension) { + allFiles = allFiles.Append(file); + } + } + catch (System.IO.FileNotFoundException e) + { + // If file was deleted by a separate application + Console.WriteLine(e.Message); + } + } + } + + return allFiles; + } + + private static string transformConnectionString() + { + _connectionString = _connectionString.Substring(_connectionString.IndexOf(':')+1); + _connectionString = _connectionString.Replace("user", "username"); + string [] properties = _connectionString.Split(';'); + string csharpConnectionString = ""; + for (int i = 0; i < properties.Length; ++i) + { + csharpConnectionString += properties[i].Substring(0,1).ToUpper() + + properties[i].Substring(1); + if (i < properties.Length-1) + { + csharpConnectionString += ";"; + } + } + + return csharpConnectionString; + } + } +} diff --git a/plugins/csharp/service/CMakeLists.txt b/plugins/csharp/service/CMakeLists.txt new file mode 100644 index 000000000..cfc163255 --- /dev/null +++ b/plugins/csharp/service/CMakeLists.txt @@ -0,0 +1,57 @@ +include_directories( + include + ${PROJECT_SOURCE_DIR}/model/include + ${PROJECT_SOURCE_DIR}/util/include + ${PROJECT_SOURCE_DIR}/webserver/include + ${PROJECT_BINARY_DIR}/service/language/gen-cpp + ${PROJECT_BINARY_DIR}/service/project/gen-cpp + ${PROJECT_SOURCE_DIR}/service/project/include + ${CMAKE_CURRENT_BINARY_DIR}/gen-cpp) + +include_directories(SYSTEM + ${THRIFT_LIBTHRIFT_INCLUDE_DIRS}) + +# Generate thrift files +add_custom_command( + OUTPUT + ${CMAKE_CURRENT_BINARY_DIR}/gen-cpp/CsharpService.cpp + ${CMAKE_CURRENT_BINARY_DIR}/gen-cpp + ${CMAKE_CURRENT_BINARY_DIR}/gen-netstd + COMMAND + ${THRIFT_EXECUTABLE} --gen cpp --gen netstd + -o ${CMAKE_CURRENT_BINARY_DIR} + -I ${PROJECT_SOURCE_DIR}/service + ${CMAKE_CURRENT_SOURCE_DIR}/csharpservice.thrift + DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/csharpservice.thrift + COMMENT + "Generating Thrift for csharpservice.thrift") + +# Create cpp static library from thrift files +add_library(csharpservicethrift STATIC + ${CMAKE_CURRENT_BINARY_DIR}/gen-cpp/CsharpService.cpp) + +target_compile_options(csharpservicethrift PUBLIC -fPIC) + +add_dependencies(csharpservicethrift languagethrift commonthrift projectthrift) + +add_subdirectory(src_csharp) + +add_library(csharpservice SHARED + src/csharpservice.cpp + src/plugin.cpp) + +target_compile_options(csharpservice PUBLIC -Wno-unknown-pragmas) + +target_link_libraries(csharpservice + util + model + mongoose + csharpservicethrift + projectservice + projectthrift + languagethrift + gvc + ${THRIFT_LIBTHRIFT_LIBRARIES}) + +install(TARGETS csharpservice DESTINATION ${INSTALL_SERVICE_DIR}) \ No newline at end of file diff --git a/plugins/csharp/service/csharpservice.thrift b/plugins/csharp/service/csharpservice.thrift new file mode 100644 index 000000000..4d223da5e --- /dev/null +++ b/plugins/csharp/service/csharpservice.thrift @@ -0,0 +1,58 @@ +include "../../../service/language/language.thrift" +include "../../../service/project/common.thrift" + +namespace cpp cc.service.csharp +namespace netstd cc.service.csharp + +service CsharpService +{ + language.AstNodeInfo getAstNodeInfo(1:common.AstNodeId astNodeId) + throws (1:common.InvalidId ex) + + language.AstNodeInfo getAstNodeInfoByPosition(1:string path, 2:common.Position fpos) + throws (1:common.InvalidInput ex) + + map getProperties(1:common.AstNodeId astNodeIds) + throws (1:common.InvalidId ex) + + string getDocumentation(1:common.AstNodeId astNodeId) + throws (1:common.InvalidId ex) + + common.FileRange getFileRange(1:common.AstNodeId astNodeId) + throws (1:common.InvalidId ex) + + map getReferenceTypes(1:common.AstNodeId astNodeId) + throws (1:common.InvalidId ex) + + i32 getReferenceCount( + 1:common.AstNodeId astNodeId, + 2:i32 referenceId) + + list getReferences( + 1:common.AstNodeId astNodeId, + 2:i32 referenceId + 3:list tags) + throws (1:common.InvalidId ex) + + map getFileReferenceTypes() + throws (1:common.InvalidId ex) + + i32 getFileReferenceCount( + 1:string path, + 2:i32 referenceId) + + list getFileReferences( + 1:string path, + 2:i32 referenceId) + throws (1:common.InvalidId ex) + + map getDiagramTypes(1:common.AstNodeId astNodeId) + throws (1:common.InvalidId ex) + + string getDiagram(1:common.AstNodeId astNodeId, 2:i32 diagramId) + throws (1:common.InvalidId exId, 2:common.Timeout exLong) + + list getSyntaxHighlight( + 1:common.FileRange range, + 2:list content) +} \ No newline at end of file diff --git a/plugins/csharp/service/include/service/csharpservice.h b/plugins/csharp/service/include/service/csharpservice.h new file mode 100644 index 000000000..3660098c9 --- /dev/null +++ b/plugins/csharp/service/include/service/csharpservice.h @@ -0,0 +1,341 @@ +#ifndef CC_SERVICE_CSHARP_CSHARPSERVICE_H +#define CC_SERVICE_CSHARP_CSHARPSERVICE_H + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + +namespace cc +{ +namespace service +{ +namespace csharp +{ + +namespace language = cc::service::language; + +using TransportException = apache::thrift::transport::TTransportException; + +class CSharpQueryHandler : public CsharpServiceIf +{ +public: + CSharpQueryHandler() {} + + void getClientInterface(int timeoutInMs_) + { + using Transport = apache::thrift::transport::TTransport; + using BufferedTransport = apache::thrift::transport::TBufferedTransport; + using Socket = apache::thrift::transport::TSocket; + using Protocol = apache::thrift::protocol::TBinaryProtocol; + namespace ch = std::chrono; + + std::string host = "localhost"; + std::shared_ptr socket(new Socket(host, _thriftServerPort)); + std::shared_ptr transport(new BufferedTransport(socket)); + std::shared_ptr protocol(new Protocol(transport)); + + _service.reset(new CsharpServiceClient(protocol)); + + // Redirect Thrift output into std::stringstream + apache::thrift::GlobalOutput.setOutputFunction( + [](const char* x) + { + _thriftStream << x; + }); + + ch::steady_clock::time_point begin = ch::steady_clock::now(); + + while (!transport->isOpen()) + { + try + { + transport->open(); + } + catch (TransportException& ex) + { + ch::steady_clock::time_point current = ch::steady_clock::now(); + float elapsed_time = ch::duration_cast(current - begin).count(); + + if (elapsed_time > timeoutInMs_) + { + LOG(debug) << "Connection timeout, could not reach CSharp server on" + << host << ":" << _thriftServerPort; + apache::thrift::GlobalOutput.setOutputFunction( + apache::thrift::TOutput::errorTimeWrapper); + throw ex; + } + } + } + + apache::thrift::GlobalOutput.setOutputFunction( + apache::thrift::TOutput::errorTimeWrapper); + + LOG(info) << "C# server started!"; + } + + + void getAstNodeInfo( + language::AstNodeInfo& return_, + const core::AstNodeId& astNodeId_) override + { + _service -> getAstNodeInfo(return_, astNodeId_); + } + + void getAstNodeInfoByPosition( + language::AstNodeInfo& return_, + const std::string& path_, + const core::Position& fpos_) override + { + _service -> getAstNodeInfoByPosition(return_, path_, fpos_); + } + + void getFileRange( + core::FileRange& return_, + const core::AstNodeId& astNodeId_) override + { + _service -> getFileRange(return_, astNodeId_); + } + + void getProperties( + std::map& return_, + const core::AstNodeId& astNodeId_) override + { + _service -> getProperties(return_, astNodeId_); + } + + void getDocumentation( + std::string& return_, + const core::AstNodeId& astNodeId_) override + { + _service -> getDocumentation(return_, astNodeId_); + } + + void getReferenceTypes( + std::map& return_, + const core::AstNodeId& astNodeId_) override + { + _service -> getReferenceTypes(return_, astNodeId_); + } + + std::int32_t getReferenceCount( + const core::AstNodeId& astNodeId_, + const std::int32_t referenceId_) override + { + return _service -> getReferenceCount(astNodeId_, referenceId_); + } + + void getReferences( + std::vector& return_, + const core::AstNodeId& astNodeId_, + const std::int32_t referenceId_, + const std::vector& tags_) override + { + _service -> getReferences(return_, astNodeId_, referenceId_, tags_); + } + + void getFileReferenceTypes( + std::map& return_) override + { + _service -> getFileReferenceTypes(return_); + } + + std::int32_t getFileReferenceCount( + const core::FileId& fileId_, + const std::int32_t referenceId_) override + { + return _service -> getFileReferenceCount(fileId_, referenceId_); + } + + void getFileReferences( + std::vector& return_, + const core::FileId& fileId_, + const std::int32_t referenceId_) override + { + _service -> getFileReferences(return_, fileId_, referenceId_); + } + + void getDiagramTypes( + std::map& return_, + const core::AstNodeId& astNodeId_) override + { + _service -> getDiagramTypes(return_, astNodeId_); + } + + void getDiagram( + std::string& return_, + const core::AstNodeId& astNodeId_, + const std::int32_t diagramId_) override + { + _service -> getDiagram(return_, astNodeId_, diagramId_); + } + + void getSyntaxHighlight( + std::vector& return_, + const core::FileRange& range_, + const std::vector& content_) override + { + _service -> getSyntaxHighlight(return_, range_, content_); + } + + void setThriftServerPort(int port) + { + _thriftServerPort = port; + } + +private: + std::unique_ptr _service; + static std::stringstream _thriftStream; + static int _thriftServerPort; +}; +} + +namespace language +{ + +using TransportException = apache::thrift::transport::TTransportException; + +class CsharpServiceHandler : virtual public LanguageServiceIf +{ + friend class Diagram; + +public: + CsharpServiceHandler( + std::shared_ptr db_, + std::shared_ptr datadir_, + const cc::webserver::ServerContext& context_); + + std::string getDbString(); + + void getFileTypes(std::vector& return_) override; + + void getAstNodeInfo( + AstNodeInfo& return_, + const core::AstNodeId& astNodeId_) override; + + void getAstNodeInfoByPosition( + AstNodeInfo& return_, + const core::FilePosition& fpos_) override; + + void getSourceText( + std::string& return_, + const core::AstNodeId& astNodeId_) override; + + void getDocumentation( + std::string& return_, + const core::AstNodeId& astNodeId_) override; + + void getProperties( + std::map& return_, + const core::AstNodeId& astNodeId_) override; + + void getDiagramTypes( + std::map& return_, + const core::AstNodeId& astNodeId_) override; + + void getDiagram( + std::string& return_, + const core::AstNodeId& astNodeId_, + const std::int32_t diagramId_) override; + + void getDiagramLegend( + std::string& return_, + const std::int32_t diagramId_) override; + + void getFileDiagramTypes( + std::map& return_, + const core::FileId& fileId_) override; + + void getFileDiagram( + std::string& return_, + const core::FileId& fileId_, + const int32_t diagramId_) override; + + void getFileDiagramLegend( + std::string& return_, + const std::int32_t diagramId_) override; + + void getReferenceTypes( + std::map& return_, + const core::AstNodeId& astNodeId) override; + + void getReferences( + std::vector& return_, + const core::AstNodeId& astNodeId_, + const std::int32_t referenceId_, + const std::vector& tags_) override; + + std::int32_t getReferenceCount( + const core::AstNodeId& astNodeId_, + const std::int32_t referenceId_) override; + + void getReferencesInFile( + std::vector& return_, + const core::AstNodeId& astNodeId_, + const std::int32_t referenceId_, + const core::FileId& fileId_, + const std::vector& tags_) override; + + void getReferencesPage( + std::vector& return_, + const core::AstNodeId& astNodeId_, + const std::int32_t referenceId_, + const std::int32_t pageSize_, + const std::int32_t pageNo_) override; + + void getFileReferenceTypes( + std::map& return_, + const core::FileId& fileId_) override; + + void getFileReferences( + std::vector& return_, + const core::FileId& fileId_, + const std::int32_t referenceId_) override; + + std::int32_t getFileReferenceCount( + const core::FileId& fileId_, + const std::int32_t referenceId_) override; + + void getSyntaxHighlight( + std::vector& return_, + const core::FileRange& range_) override; + +private: + std::shared_ptr _db; + util::OdbTransaction _transaction; + + std::shared_ptr _datadir; + const cc::webserver::ServerContext& _context; + boost::process::child c; + + cc::service::csharp::CSharpQueryHandler _csharpQueryHandler; + static int _thriftServerPort; +}; + +} // language +} // service +} // cc + +#endif // CC_SERVICE_CSHARP_CSHARPSERVICE_H diff --git a/plugins/csharp/service/src/csharpservice.cpp b/plugins/csharp/service/src/csharpservice.cpp new file mode 100644 index 000000000..7d44b417d --- /dev/null +++ b/plugins/csharp/service/src/csharpservice.cpp @@ -0,0 +1,303 @@ +#include +#include +#include +#include +#include + +namespace cc +{ +namespace service +{ +namespace language +{ +typedef odb::query FileQuery; +namespace fs = boost::filesystem; +namespace bp = boost::process; +namespace pt = boost::property_tree; + +int CsharpServiceHandler::_thriftServerPort = 9091; + +CsharpServiceHandler::CsharpServiceHandler( + std::shared_ptr db_, + std::shared_ptr datadir_, + const cc::webserver::ServerContext& context_) + : _db(db_), + _transaction(db_), + _datadir(datadir_), + _context(context_) +{ + _csharpQueryHandler.setThriftServerPort(_thriftServerPort); + fs::path csharp_path = + fs::system_complete("../lib/serviceplugin/csharpservice/"); + + std::string command("./csharpservice "); + command.append(getDbString()); + command.append(" "); + command.append(std::to_string(_thriftServerPort)); + ++_thriftServerPort; + c = bp::child(bp::start_dir(csharp_path), command); + try + { + _csharpQueryHandler.getClientInterface(25000); + } + catch (TransportException& ex) + { + LOG(error) << "[csharpservice] Starting service failed!"; + } +} + +std::string CsharpServiceHandler::getDbString() +{ + pt::ptree _pt; + pt::read_json(*_datadir + "/project_info.json", _pt); + + return _pt.get("database"); +} + +void CsharpServiceHandler::getFileTypes(std::vector& return_) +{ + //LOG(info) << "CsharpServiceHandler getFileTypes"; + return_.push_back("CS"); + return_.push_back("Dir"); +} + +void CsharpServiceHandler::getAstNodeInfo( + AstNodeInfo& return_, + const core::AstNodeId& astNodeId_) +{ + _csharpQueryHandler.getAstNodeInfo(return_, astNodeId_); + model::FilePtr file = _transaction([&, this](){ + return _db->query_one( + FileQuery::path == return_.range.file); + }); + std::stringstream ss; + ss << file; + return_.range.file = ss.str(); + //LOG(info) << "csharpQuery.getAstNodeInfo: file = " << return_.range.file; +} + +void CsharpServiceHandler::getAstNodeInfoByPosition( + AstNodeInfo& return_, + const core::FilePosition& fpos_) +{ + model::FilePtr file = _transaction([&, this](){ + return _db->query_one( + FileQuery::id == std::stoull(fpos_.file)); + }); + _csharpQueryHandler.getAstNodeInfoByPosition(return_, file->path, fpos_.pos); +} + +void CsharpServiceHandler::getSourceText( + std::string& return_, + const core::AstNodeId& astNodeId_) +{ + LOG(info) << "getSourceText"; + core::FileRange fileRange; + + _csharpQueryHandler.getFileRange(fileRange, astNodeId_); + + return_ = _transaction([&, this](){ + model::FilePtr file = _db->query_one( + FileQuery::id == std::stoull(fileRange.file)); + + if (!file) { + return std::string(); + } + + return cc::util::textRange( + file->content.load()->content, + fileRange.range.startpos.line, + fileRange.range.startpos.column, + fileRange.range.endpos.line, + fileRange.range.endpos.column); + }); +} + +void CsharpServiceHandler::getProperties( + std::map& return_, + const core::AstNodeId& astNodeId_) +{ + //LOG(info) << "getProperties"; + _csharpQueryHandler.getProperties(return_, astNodeId_); +} + +void CsharpServiceHandler::getDocumentation( + std::string& return_, + const core::AstNodeId& astNodeId_) +{ + LOG(info) << "getDocumentation"; + _csharpQueryHandler.getDocumentation(return_, astNodeId_); +} + +void CsharpServiceHandler::getDiagramTypes( + std::map& return_, + const core::AstNodeId& astNodeId_) +{ + LOG(info) << "getDiagramTypes"; + //csharpQueryHandler.getDiagramTypes(return_, astNodeId_); +} + +void CsharpServiceHandler::getDiagram( + std::string& return_, + const core::AstNodeId& astNodeId_, + const std::int32_t diagramId_) +{ + LOG(info) << "getDiagram"; + //csharpQueryHandler.getDiagram(return_, astNodeId_, diagramId_); +} + +void CsharpServiceHandler::getDiagramLegend( + std::string& return_, + const std::int32_t diagramId_) +{ + LOG(info) << "getDiagramLegend"; +} + +void CsharpServiceHandler::getFileDiagramTypes( + std::map& return_, + const core::FileId& fileId_) +{ + LOG(info) << "getFileDiagramTypes"; +} + +void CsharpServiceHandler::getFileDiagram( + std::string& return_, + const core::FileId& fileId_, + const int32_t diagramId_) +{ + LOG(info) << "getFileDiagram"; +} + +void CsharpServiceHandler::getFileDiagramLegend( + std::string& return_, + const std::int32_t diagramId_) +{ + LOG(info) << "getFileDiagramLegend"; +} + +void CsharpServiceHandler::getReferenceTypes( + std::map& return_, + const core::AstNodeId& astNodeId_) +{ + //LOG(info) << "getReferenceTypes"; + _csharpQueryHandler.getReferenceTypes(return_, astNodeId_); +} + +std::int32_t CsharpServiceHandler::getReferenceCount( + const core::AstNodeId& astNodeId_, + const std::int32_t referenceId_) +{ + //LOG(info) << "getReferenceCount"; + return _csharpQueryHandler.getReferenceCount(astNodeId_, referenceId_); +} + +void CsharpServiceHandler::getReferences( + std::vector& return_, + const core::AstNodeId& astNodeId_, + const std::int32_t referenceId_, + const std::vector& tags_) +{ + //LOG(info) << "getReferences"; + _csharpQueryHandler.getReferences(return_, astNodeId_, referenceId_, tags_); + std::vector ret; + for (AstNodeInfo nodeinfo : return_) + { + model::FilePtr file = _transaction([&, this](){ + return _db->query_one( + FileQuery::path == nodeinfo.range.file); + }); + + std::stringstream ss; + ss << file->id; + nodeinfo.range.file = ss.str(); + ret.push_back(nodeinfo); + } + return_ = ret; +} + +void CsharpServiceHandler::getReferencesInFile( + std::vector& /* return_ */, + const core::AstNodeId& /* astNodeId_ */, + const std::int32_t /* referenceId_ */, + const core::FileId& /* fileId_ */, + const std::vector& /* tags_ */) +{ + //LOG(info) << "getReferencesInFile"; + // TODO +} + +void CsharpServiceHandler::getReferencesPage( + std::vector& /* return_ */, + const core::AstNodeId& /* astNodeId_ */, + const std::int32_t /* referenceId_ */, + const std::int32_t /* pageSize_ */, + const std::int32_t /* pageNo_ */) +{ + //LOG(info) << "getReferencesPage"; + // TODO +} + +void CsharpServiceHandler::getFileReferenceTypes( + std::map& return_, + const core::FileId& /* fileId_*/) +{ + //LOG(info) << "getFileReferenceTypes"; + _csharpQueryHandler.getFileReferenceTypes(return_); +} + +std::int32_t CsharpServiceHandler::getFileReferenceCount( + const core::FileId& fileId_, + const std::int32_t referenceId_) +{ + //LOG(info) << "getFileReferenceCount"; + model::FilePtr file = _transaction([&, this](){ + return _db->query_one( + FileQuery::id == std::stoull(fileId_)); + }); + return _csharpQueryHandler.getFileReferenceCount(file->path, referenceId_); +} + +void CsharpServiceHandler::getFileReferences( + std::vector& return_, + const core::FileId& fileId_, + const std::int32_t referenceId_) +{ + //LOG(info) << "getFileReferences"; + model::FilePtr file = _transaction([&, this](){ + return _db->query_one( + FileQuery::id == std::stoull(fileId_)); + }); + _csharpQueryHandler.getFileReferences(return_, file->path, referenceId_); +} + +void CsharpServiceHandler::getSyntaxHighlight( + std::vector& return_, + const core::FileRange& range_) +{ + LOG(info) << "getSyntaxHighlight"; + /* + std::vector content; + _transaction([&, this]() { + //--- Load the file content and break it into lines ---// + model::FilePtr file = _db->query_one( + FileQuery::id == std::stoull(range_.file)); + if (!file || !file->content.load()) + return; + std::istringstream s(file->content->content); + std::string line; + while (std::getline(s, line)) + content.push_back(line); + }); + csharpQueryHandler.getSyntaxHighlight(return_, range_, content); + */ +} + +} // language + +namespace csharp +{ +std::stringstream CSharpQueryHandler::_thriftStream; +int CSharpQueryHandler::_thriftServerPort; +} +} // service +} // cc diff --git a/plugins/csharp/service/src/plugin.cpp b/plugins/csharp/service/src/plugin.cpp new file mode 100644 index 000000000..1d2081114 --- /dev/null +++ b/plugins/csharp/service/src/plugin.cpp @@ -0,0 +1,26 @@ +#include + +#include + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreturn-type-c-linkage" +extern "C" +{ + boost::program_options::options_description getOptions() + { + boost::program_options::options_description description("C# Plugin"); + return description; + } + + void registerPlugin( + const cc::webserver::ServerContext& context_, + cc::webserver::PluginHandler* pluginHandler_) + { + cc::webserver::registerPluginSimple( + context_, + pluginHandler_, + CODECOMPASS_LANGUAGE_SERVICE_FACTORY_WITH_CFG(Csharp), + "CsharpService"); + } +} +#pragma clang diagnostic pop diff --git a/plugins/csharp/service/src_csharp/CMakeLists.txt b/plugins/csharp/service/src_csharp/CMakeLists.txt new file mode 100644 index 000000000..49aa13bf1 --- /dev/null +++ b/plugins/csharp/service/src_csharp/CMakeLists.txt @@ -0,0 +1,42 @@ +cmake_minimum_required(VERSION 3.5.1) +project(CodeCompassCSharp) + +add_custom_target(dotnetbuildservice + COMMAND ${THRIFT_EXECUTABLE} --gen netstd + -o ${CMAKE_CURRENT_BINARY_DIR} + -I ${PROJECT_SOURCE_DIR}/service + -r ${CMAKE_CURRENT_SOURCE_DIR}/../csharpservice.thrift + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" +) + +add_custom_target(dotnetaddclasslib + COMMAND dotnet new classlib -o ${CMAKE_CURRENT_BINARY_DIR}/gen-netstd/ --force + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" +) + +add_dependencies(dotnetaddclasslib dotnetbuildservice) + +add_custom_target(dotnetaddref + COMMAND dotnet add reference ${CMAKE_CURRENT_BINARY_DIR}/gen-netstd/gen-netstd.csproj + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" +) + +add_dependencies(dotnetaddref dotnetaddclasslib dotnetbuildservice) + +add_custom_target(dotnetaddref2 + COMMAND dotnet add reference ${CMAKE_SOURCE_DIR}/lib/csharp/thrift_netstd/Thrift/Thrift.csproj + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/gen-netstd/" +) + +add_dependencies(dotnetaddref2 dotnetaddref dotnetaddclasslib dotnetbuildservice) + +add_custom_target(dotnetbuildfiles ALL + COMMAND dotnet build -o ${CMAKE_CURRENT_BINARY_DIR}/csharpservice + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" +) + +add_dependencies(dotnetbuildfiles dotnetaddref2 dotnetaddref dotnetaddclasslib dotnetbuildservice) + +install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/csharpservice +DESTINATION ${INSTALL_SERVICE_DIR} +USE_SOURCE_PERMISSIONS) diff --git a/plugins/csharp/service/src_csharp/CSharpQueryHandler.cs b/plugins/csharp/service/src_csharp/CSharpQueryHandler.cs new file mode 100644 index 000000000..0a422de3e --- /dev/null +++ b/plugins/csharp/service/src_csharp/CSharpQueryHandler.cs @@ -0,0 +1,684 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Security; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using System.Diagnostics; +using language; +using cc.service.csharp; +using CSharpParser.model; + +public class CSharpQueryHandler : CsharpService.IAsync +{ + private CsharpDbContext dbContext; + public CSharpQueryHandler(string connenctionString) + { + // Converting the connectionstring into Entity Framework style connection string + connenctionString = connenctionString.Substring(connenctionString.IndexOf(':')+1); + connenctionString = connenctionString.Replace("user", "username"); + string[] properties = connenctionString.Split(';'); + string csharpConnenctionString = ""; + for (int i = 0; i < properties.Length; ++i) + { + csharpConnenctionString += properties[i].Substring(0,1).ToUpper() + + properties[i].Substring(1); + if (i < properties.Length-1) + { + csharpConnenctionString += ";"; + } + } + + var options = new DbContextOptionsBuilder() + .UseNpgsql(csharpConnenctionString) + .Options; + dbContext = new CsharpDbContext(options); + } + + private language.AstNodeInfo createAstNodeInfo(CsharpAstNode node) + { + language.AstNodeInfo ret = new language.AstNodeInfo(); + ret.Id = node.Id.ToString(); + ret.AstNodeValue = node.AstValue; + ret.AstNodeType = node.RawKind.ToString(); + ret.SymbolType = node.AstSymbolType.ToString(); + ret.Range = getFileRange(node); + + List tags = new List(); + tags.Add(node.Accessibility.ToString()); + + ret.Tags = tags; + + return ret; + } + + private List createAstNodeInfoList(List nodeList) + { + var ret = new List(); + foreach (var node in nodeList) + { + var astNodeInfo = createAstNodeInfo(node); + ret.Add(astNodeInfo); + } + + return ret; + } + + private FileRange getFileRange(CsharpAstNode node) + { + FileRange fileRange = new FileRange(); + Position startPosition = new Position + { + Line = (int)node.Location_range_start_line, + Column = (int)node.Location_range_start_column + }; + + Position endPosition = new Position + { + Line = (int)node.Location_range_end_line, + Column = (int)node.Location_range_end_column + }; + + Range range = new Range + { + Startpos = startPosition, + Endpos = endPosition + }; + + fileRange.File = node.Path; + fileRange.Range = range; + + return fileRange; + } + + private CsharpAstNode queryCsharpAstNode(string astNodeId) + { + CsharpAstNode ret; + try + { + ret = dbContext.CsharpAstNodes + .Where(a => a.Id.ToString()==astNodeId) + .First(); + } + catch (InvalidOperationException e) + { + System.Console.WriteLine($"[CSharpService error] There are no AstNode with this ID:{astNodeId}"); + ret = new CsharpAstNode(); + ret.Id = 0; + } + return ret; + } + + private List queryInvocations(CsharpAstNode astNode) + { + var ret = dbContext.CsharpEtcEntitys + .Where(e => e.DeclaratorNodeId == astNode.Id) + .Select(e => e.AstNode) + .ToList(); + return ret; + } + + private List queryDeclarators(CsharpAstNode astNode) + { + var ids = dbContext.CsharpEtcEntitys + .Where(e => e.AstNode.Id == astNode.Id) + .Select(e => e.DeclaratorNodeId.ToString()) + .ToList(); + + if (ids.Count == 0) + { + return new List(); + } + else + { + return ids.Select(id => queryCsharpAstNode(id)).ToList(); + } + } + + private List queryEvals(CsharpAstNode astNode) + { + var ret = + from invoc in dbContext.CsharpEtcEntitys + join variable in dbContext.CsharpVariables + on invoc.DeclaratorNodeId equals variable.AstNode.Id + where invoc.DeclaratorNodeId == astNode.Id + && variable.VariableType == VariableTypeEnum.LINQ + && (invoc.QualifiedType != "IEnumerable" + || invoc.EtcEntityType == EtcEntityTypeEnum.ForeachExpr) + select invoc.AstNode; + return ret.ToList(); + } + + private List queryParams(CsharpAstNode astNode) + { + var ret = dbContext.CsharpVariables + .Where(e => e.ParentNode.Id == astNode.Id + && e.VariableType == VariableTypeEnum.Parameter) + .Select(e => e.AstNode) + .ToList(); + return ret; + } + + private List queryLocals(CsharpAstNode astNode){ + var ret = dbContext.CsharpVariables + .Where(e => e.ParentNode.Id == astNode.Id + && e.VariableType == VariableTypeEnum.Variable) + .Select(e => e.AstNode) + .ToList(); + return ret; + } + + private List queryProperties(CsharpAstNode astNode) + { + var ret = dbContext.CsharpVariables + .Where(e => e.ParentNode.Id == astNode.Id + && e.VariableType == VariableTypeEnum.Property) + .Select(e => e.AstNode) + .ToList(); + return ret; + } + + private List queryCalls(CsharpAstNode astNode) + { + var ret = + from invoc in dbContext.CsharpEtcEntitys + join node in dbContext.CsharpAstNodes + on invoc.DeclaratorNodeId equals node.Id + where node.AstSymbolType == AstSymbolTypeEnum.Method + && invoc.AstNode.Path == astNode.Path + && invoc.AstNode.Location_range_start_line >= astNode.Location_range_start_line + && invoc.AstNode.Location_range_end_line <= astNode.Location_range_end_line + select invoc.AstNode; + return ret.ToList(); + } + + private List queryCallees(CsharpAstNode astNode) + { + var ret = + from invoc in dbContext.CsharpEtcEntitys + join node in dbContext.CsharpAstNodes + on invoc.DeclaratorNodeId equals node.Id + where node.AstSymbolType == AstSymbolTypeEnum.Method + && invoc.AstNode.Path == astNode.Path + && invoc.AstNode.Location_range_start_line >= astNode.Location_range_start_line + && invoc.AstNode.Location_range_end_line <= astNode.Location_range_end_line + select node; + return ret.Distinct().ToList(); + } + + private List queryCallers(CsharpAstNode astNode) + { + var invocations = dbContext.CsharpEtcEntitys + .Where(e => e.DeclaratorNodeId == astNode.Id) + .Select(e => e.AstNode); + var ret = + from invoc in invocations + join node in dbContext.CsharpAstNodes + on invoc.Path equals node.Path + where node.AstSymbolType == AstSymbolTypeEnum.Method + && invoc.Location_range_start_line >= node.Location_range_start_line + && invoc.Location_range_end_line <= node.Location_range_end_line + select node; + return ret.Distinct().ToList(); + } + + private List queryEnumConsts(CsharpAstNode astNode) + { + var ret = new List(); + if (astNode.AstSymbolType == AstSymbolTypeEnum.Enum) + { + ret = dbContext.CsharpEnumMembers + .Where(e => e.ParentNode.Id == astNode.Id) + .Select(e => e.AstNode) + .ToList(); + } + else if (astNode.AstSymbolType == AstSymbolTypeEnum.EnumMember) + { + var parent = dbContext.CsharpEnumMembers + .Where(e => e.AstNode.Id == astNode.Id) + .Select(e => e.ParentNode).FirstOrDefault(); + + ret = dbContext.CsharpEnumMembers + .Where(e => e.ParentNode.Id == parent.Id) + .Select(e => e.AstNode) + .ToList(); + } + + return ret; + } + + private List queryMethods(CsharpAstNode astNode) + { + var ret = dbContext.CsharpMethods + .Where(e => e.ParentNode.Id == astNode.Id) + .Select(e => e.AstNode) + .ToList(); + return ret; + } + + private List queryMethodType(CsharpAstNode astNode, MethodTypeEnum type) + { + var ret = dbContext.CsharpMethods + .Where(e => e.ParentNode.Id == astNode.Id + && e.MethodType == type) + .Select(e => e.AstNode) + .ToList(); + return ret; + } + + private List queryEvents(CsharpAstNode astNode) + { + var ret = dbContext.CsharpEtcEntitys + .Where(e => e.ParentNode.Id == astNode.Id + && e.EtcEntityType == EtcEntityTypeEnum.Event) + .Select(e => e.AstNode) + .ToList(); + return ret; + } + + + public async Task getAstNodeInfoAsync(string astNodeId, + CancellationToken cancellationToken = default(CancellationToken)) + { + System.Console.WriteLine("[CSharpService] getAstNodeInfoAsync"); + return await Task.FromResult(new language.AstNodeInfo()); + } + + public async Task getAstNodeInfoByPositionAsync(string path_, + Position pos_, + CancellationToken cancellationToken = default(CancellationToken)) + { + var nodes = dbContext.CsharpAstNodes + .Where(a => + a.Path == path_ && + ((a.Location_range_start_line == pos_.Line && + a.Location_range_start_column <= pos_.Column) || + a.Location_range_start_line < pos_.Line) && + ((a.Location_range_end_line == pos_.Line && + a.Location_range_end_column > pos_.Column) || + a.Location_range_end_line > pos_.Line)); + if (nodes.Count() == 0) + { + System.Console.WriteLine("[CSharpService error] There are no AstNode at this position!"); + return await Task.FromResult(new language.AstNodeInfo()); + } + + var minNode = nodes.FirstOrDefault(); + foreach (var node in nodes.ToList()) + { + if (node.isRangeSmaller(minNode)) + minNode = node; + } + + return await Task.FromResult(createAstNodeInfo(minNode)); + } + + public async Task> getPropertiesAsync(string astNodeIds, + CancellationToken cancellationToken = default(CancellationToken)) + { + Dictionary ret = new Dictionary(); + CsharpAstNode node = queryCsharpAstNode(astNodeIds); + ret.Add("AstNode Type", node.RawKind.ToString()); + ret.Add("Accessibility", node.Accessibility.ToString()); + switch(node.AstSymbolType){ + case AstSymbolTypeEnum.Variable: + var variable = dbContext.CsharpVariables + .Where(v => v.AstNode == node) + .FirstOrDefault(); + ret.Add("Name", variable.Name+" "); + ret.Add("Qualified Name", variable.QualifiedName+" "); + ret.Add("Documentation Comment", variable.DocumentationCommentXML+" "); + ret.Add("Qualified Type", variable.QualifiedType+" "); + ret.Add("Variable Type", variable.VariableType.ToString()); + break; + case AstSymbolTypeEnum.Method: + var method = dbContext.CsharpMethods + .Where(m => m.AstNode == node) + .FirstOrDefault(); + ret.Add("Name", method.Name+" "); + ret.Add("Qualified Name", method.QualifiedName+" "); + ret.Add("Documentation Comment", method.DocumentationCommentXML+" "); + ret.Add("Qualified Type", method.QualifiedType+" "); + ret.Add("Method Type", method.MethodType.ToString()); + break; + case AstSymbolTypeEnum.Class: + var Class = dbContext.CsharpClasses + .Where(m => m.AstNode == node) + .FirstOrDefault(); + ret.Add("Name", Class.Name+" "); + ret.Add("Qualified Name", Class.QualifiedName+" "); + ret.Add("Documentation Comment", Class.DocumentationCommentXML+" "); + if (Class.CsharpNamespace != null) ret.Add("Namespace", Class.CsharpNamespace.Name+" "); + ret.Add("Class Type", Class.ClassType.ToString()); + break; + case AstSymbolTypeEnum.Struct: + var Struct = dbContext.CsharpClasses + .Where(m => m.AstNode == node) + .FirstOrDefault(); + ret.Add("Name", Struct.Name+" "); + ret.Add("Qualified Name", Struct.QualifiedName+" "); + ret.Add("Documentation Comment", Struct.DocumentationCommentXML+" "); + if (Struct.CsharpNamespace != null) ret.Add("Namespace", Struct.CsharpNamespace.Name+" "); + break; + case AstSymbolTypeEnum.Namespace: + var Namespace = dbContext.CsharpNamespaces + .Where(m => m.AstNode == node) + .FirstOrDefault(); + ret.Add("Name", Namespace.Name+" "); + ret.Add("Qualified Name", Namespace.QualifiedName+" "); + ret.Add("Documentation Comment", Namespace.DocumentationCommentXML+" "); + break; + case AstSymbolTypeEnum.Enum: + var Enum = dbContext.CsharpEnums + .Where(m => m.AstNode == node) + .FirstOrDefault(); + ret.Add("Name", Enum.Name+" "); + ret.Add("Qualified Name", Enum.QualifiedName+" "); + ret.Add("Documentation Comment", Enum.DocumentationCommentXML+" "); + ret.Add("Namespace", Enum.CsharpNamespace.Name+" "); + break; + case AstSymbolTypeEnum.EnumMember: + var EnumMember = dbContext.CsharpEnumMembers + .Where(m => m.AstNode == node) + .FirstOrDefault(); + ret.Add("Name", EnumMember.Name+" "); + ret.Add("Qualified Name", EnumMember.QualifiedName+" "); + ret.Add("Documentation Comment", EnumMember.DocumentationCommentXML+" "); + ret.Add("Value", EnumMember.EqualsValue.ToString()); + break; + case AstSymbolTypeEnum.EtcEntity: + var EtcEntity = dbContext.CsharpEtcEntitys + .Where(m => m.AstNode == node) + .FirstOrDefault(); + ret.Add("Name", EtcEntity.Name+" "); + ret.Add("Qualified Name", EtcEntity.QualifiedName+" "); + ret.Add("Qualified Type", EtcEntity.QualifiedType+" "); + ret.Add("Documentation Comment", EtcEntity.DocumentationCommentXML+" "); + ret.Add("Etc Entity Type", EtcEntity.EtcEntityType.ToString()); + break; + default: + System.Console.WriteLine($"[CSharpService] {node.AstSymbolType} kind is unhandled"); + break; + } + return await Task.FromResult(ret); + } + + public async Task getDocumentationAsync(string astNodeId, + CancellationToken cancellationToken = default(CancellationToken)) + { + System.Console.WriteLine("[CSharpService] getDocumentationAsync"); + CsharpAstNode node = queryCsharpAstNode(astNodeId); + return await Task.FromResult("Documentation"); + } + + public async Task getFileRangeAsync(string astNodeId, + CancellationToken cancellationToken = default(CancellationToken)) + { + return await Task.FromResult(getFileRange(queryCsharpAstNode(astNodeId))); + } + + public async Task> getReferenceTypesAsync(string astNodeId, + CancellationToken cancellationToken = default(CancellationToken)) + { + var node = queryCsharpAstNode(astNodeId); + Dictionary ret = new Dictionary(); + ret.Add("Definition", (int)ReferenceType.DEFINITION); + ret.Add("Declaration", (int)ReferenceType.DECLARATION); + ret.Add("Usage", (int)ReferenceType.USAGE); + switch (node.AstSymbolType) + { + case AstSymbolTypeEnum.Variable: + var variable = dbContext.CsharpVariables + .Where(v => v.AstNode == node) + .FirstOrDefault(); + ret.Add("Reads", (int)ReferenceType.READ); + ret.Add("Writes", (int)ReferenceType.WRITE); + ret.Add("Type", (int)ReferenceType.TYPE); + if (variable.VariableType == VariableTypeEnum.LINQ) + { + ret.Add("LINQ evaluation", (int)ReferenceType.EVALUATION); + ret.Add("LINQ data modification", (int)ReferenceType.DATA_MODIFICATION); + } + break; + case AstSymbolTypeEnum.Method: + ret.Add("This calls", (int)ReferenceType.THIS_CALLS); + ret.Add("Callee", (int)ReferenceType.CALLEE); + ret.Add("Caller", (int)ReferenceType.CALLER); + ret.Add("Parameters", (int)ReferenceType.PARAMETER); + ret.Add("Local variables", (int)ReferenceType.LOCAL_VAR); + break; + case AstSymbolTypeEnum.Class: + ret.Add("Aliases", (int)ReferenceType.ALIAS); + ret.Add("Inherits from", (int)ReferenceType.INHERIT_FROM); + ret.Add("Inherited by", (int)ReferenceType.INHERIT_BY); + ret.Add("Data members", (int)ReferenceType.DATA_MEMBER); + ret.Add("Methods", (int)ReferenceType.METHOD); + ret.Add("Accesors", (int)ReferenceType.ACCESSOR); + ret.Add("Operators", (int)ReferenceType.OPERATOR); + ret.Add("Constructors", (int)ReferenceType.CONSTRUCTOR); + ret.Add("Delegates", (int)ReferenceType.DELEGATE); + ret.Add("Destructors", (int)ReferenceType.DESTRUCTOR); + ret.Add("Global variables", (int)ReferenceType.LOCAL_VAR); + break; + case AstSymbolTypeEnum.Struct: + ret.Add("Aliases", (int)ReferenceType.ALIAS); + ret.Add("Inherits from", (int)ReferenceType.INHERIT_FROM); + ret.Add("Inherited by", (int)ReferenceType.INHERIT_BY); + ret.Add("Data member", (int)ReferenceType.DATA_MEMBER); + ret.Add("Methods", (int)ReferenceType.METHOD); + ret.Add("Accesors", (int)ReferenceType.ACCESSOR); + ret.Add("Operators", (int)ReferenceType.OPERATOR); + ret.Add("Constructors", (int)ReferenceType.CONSTRUCTOR); + ret.Add("Delegates", (int)ReferenceType.DELEGATE); + ret.Add("Destructors", (int)ReferenceType.DESTRUCTOR); + ret.Add("Global variables", (int)ReferenceType.LOCAL_VAR); + break; + case AstSymbolTypeEnum.Namespace: + ret.Add("Aliases", (int)ReferenceType.ALIAS); + break; + case AstSymbolTypeEnum.Enum: + ret.Add("Enum constants", (int)ReferenceType.ENUM_CONSTANTS); + break; + case AstSymbolTypeEnum.EnumMember: + ret.Add("Enum constants", (int)ReferenceType.ENUM_CONSTANTS); + break; + case AstSymbolTypeEnum.EtcEntity: + ret.Add("Aliases", (int)ReferenceType.ALIAS); + ret.Add("Callee", (int)ReferenceType.CALLEE); + ret.Add("Caller", (int)ReferenceType.CALLER); + break; + default: + System.Console.WriteLine($"[CSharpService] {node.AstSymbolType} kind is unhandled"); + break; + } + + return await Task.FromResult(ret); + } + + public async Task getReferenceCountAsync(string astNodeId, int referenceId, + CancellationToken cancellationToken = default(CancellationToken)) + { + var node = queryCsharpAstNode(astNodeId); + int ret = 0; + switch ((ReferenceType)referenceId) + { + case ReferenceType.USAGE: + ret = queryInvocations(node).Count(); + break; + case ReferenceType.DEFINITION: + case ReferenceType.DECLARATION: + ret = queryDeclarators(node).Count(); + break; + case ReferenceType.EVALUATION: + ret = queryEvals(node).Count(); + break; + case ReferenceType.PARAMETER: + ret = queryParams(node).Count(); + break; + case ReferenceType.LOCAL_VAR: + ret = queryLocals(node).Count(); + break; + case ReferenceType.DATA_MEMBER: + ret = queryProperties(node).Count(); + break; + case ReferenceType.THIS_CALLS: + ret = queryCalls(node).Count(); + break; + case ReferenceType.CALLEE: + ret = queryCallees(node).Count(); + break; + case ReferenceType.CALLER: + ret = queryCallers(node).Count(); + break; + case ReferenceType.ENUM_CONSTANTS: + ret = queryEnumConsts(node).Count(); + break; + case ReferenceType.METHOD: + ret = queryMethods(node).Count(); + break; + case ReferenceType.CONSTRUCTOR: + ret = queryMethodType(node, MethodTypeEnum.Constructor).Count(); + break; + case ReferenceType.DESTRUCTOR: + ret = queryMethodType(node, MethodTypeEnum.Destuctor).Count(); + break; + case ReferenceType.OPERATOR: + ret = queryMethodType(node, MethodTypeEnum.Operator).Count(); + break; + case ReferenceType.ACCESSOR: + ret = queryMethodType(node, MethodTypeEnum.Accessor).Count(); + break; + case ReferenceType.DELEGATE: + ret = queryMethodType(node, MethodTypeEnum.Delegate).Count(); + break; + case ReferenceType.EVENT: + ret = queryEvents(node).Count(); + break; + default: + System.Console.WriteLine($"[CSharpService] {(ReferenceType)referenceId}"+ + " ReferenceType is unhandled"); + break; + } + return await Task.FromResult(ret); + } + + public async Task> getReferencesAsync(string astNodeId, + int referenceId, List tags, + CancellationToken cancellationToken = default(CancellationToken)) + { + var node = queryCsharpAstNode(astNodeId); + var ret = new List(); + switch ((ReferenceType)referenceId) + { + case ReferenceType.USAGE: + ret = createAstNodeInfoList(queryInvocations(node)); + break; + case ReferenceType.DEFINITION: + case ReferenceType.DECLARATION: + ret = createAstNodeInfoList(queryDeclarators(node)); + break; + case ReferenceType.EVALUATION: + ret = createAstNodeInfoList(queryEvals(node)); + break; + case ReferenceType.PARAMETER: + ret = createAstNodeInfoList(queryParams(node)); + break; + case ReferenceType.LOCAL_VAR: + ret = createAstNodeInfoList(queryLocals(node)); + break; + case ReferenceType.DATA_MEMBER: + ret = createAstNodeInfoList(queryProperties(node)); + break; + case ReferenceType.THIS_CALLS: + ret = createAstNodeInfoList(queryCalls(node)); + break; + case ReferenceType.CALLEE: + ret = createAstNodeInfoList(queryCallees(node)); + break; + case ReferenceType.CALLER: + ret = createAstNodeInfoList(queryCallers(node)); + break; + case ReferenceType.ENUM_CONSTANTS: + ret = createAstNodeInfoList(queryEnumConsts(node)); + break; + case ReferenceType.METHOD: + ret = createAstNodeInfoList(queryMethods(node)); + break; + case ReferenceType.CONSTRUCTOR: + ret = createAstNodeInfoList(queryMethodType(node, MethodTypeEnum.Constructor)); + break; + case ReferenceType.DESTRUCTOR: + ret = createAstNodeInfoList(queryMethodType(node, MethodTypeEnum.Destuctor)); + break; + case ReferenceType.OPERATOR: + ret = createAstNodeInfoList(queryMethodType(node, MethodTypeEnum.Operator)); + break; + case ReferenceType.ACCESSOR: + ret = createAstNodeInfoList(queryMethodType(node, MethodTypeEnum.Accessor)); + break; + case ReferenceType.DELEGATE: + ret = createAstNodeInfoList(queryMethodType(node, MethodTypeEnum.Delegate)); + break; + case ReferenceType.EVENT: + ret = createAstNodeInfoList(queryEvents(node)); + break; + default: + System.Console.WriteLine($"[CSharpService] {(ReferenceType)referenceId}"+ + " ReferenceType is unhandled"); + break; + } + return await Task.FromResult(ret); + } + + public async Task> getFileReferenceTypesAsync( + CancellationToken cancellationToken = default(CancellationToken)) + { + var ret = new Dictionary(); + ret.Add("Types", (int)FileReferenceType.TYPES); + ret.Add("Functions", (int)FileReferenceType.FUNCTIONS); + ret.Add("Includes", (int)FileReferenceType.INCLUDES); + return await Task.FromResult(ret); + } + + public async Task getFileReferenceCountAsync(string path, int referenceId, + CancellationToken cancellationToken = default(CancellationToken)) + { + return await Task.FromResult(0); + } + + public async Task> getFileReferencesAsync(string path, + int referenceId, + CancellationToken cancellationToken = default(CancellationToken)) + { + return await Task.FromResult(new List()); + } + + public async Task> getDiagramTypesAsync(string astNodeId, + CancellationToken cancellationToken = default(CancellationToken)) + { + return await Task.FromResult(new Dictionary()); + } + + public async Task getDiagramAsync(string astNodeId, int diagramId, + CancellationToken cancellationToken = default(CancellationToken)) + { + return await Task.FromResult("Diagram"); + } + + public async Task> getSyntaxHighlightAsync(FileRange range, + List content, + CancellationToken cancellationToken = default(CancellationToken)) + { + return await Task.FromResult(new List()); + } + + +} \ No newline at end of file diff --git a/plugins/csharp/service/src_csharp/CSharpQueryServer.cs b/plugins/csharp/service/src_csharp/CSharpQueryServer.cs new file mode 100644 index 000000000..955de23a1 --- /dev/null +++ b/plugins/csharp/service/src_csharp/CSharpQueryServer.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Security; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Thrift; +using Thrift.Protocol; +using Thrift.Server; +using Thrift.Transport; +using Thrift.Transport.Server; +using Thrift.Processor; +using System.Diagnostics; +using language; +using cc.service.csharp; +using CSharpParser.model; + +namespace Server +{ + public static class LoggingHelper + { + public static ILoggerFactory LogFactory { get; } = LoggerFactory.Create(builder => { + ConfigureLogging(builder); + }); + + public static void ConfigureLogging(ILoggingBuilder logging) + { + logging.SetMinimumLevel(LogLevel.Trace); + logging.AddConsole(); + logging.AddDebug(); + } + + public static ILogger CreateLogger() => LogFactory.CreateLogger(); + } + + public class CSharpQueryServer + { + private static readonly ILogger Logger = LoggingHelper.CreateLogger(); + private static readonly TConfiguration Configuration = new TConfiguration(); + private static int port = 9091; + + /* + * args[0]: database connection string + * args[1]: Thrift server port number + */ + public static void Main(string[] args) + { + System.Console.WriteLine("New query server"); + using (var source = new CancellationTokenSource()) + { + string connenctionString = args[0]; + port = Int32.Parse(args[1]); + System.Console.WriteLine("[CSharpService] Server started!"); + RunAsync(source.Token, connenctionString).GetAwaiter().GetResult(); + + System.Console.WriteLine("[CSharpService] Press any key to stop..."); + + Console.ReadLine(); + source.Cancel(); + System.Console.WriteLine("[CSharpService] Server stopped"); + } + } + + private static async Task RunAsync(CancellationToken cancellationToken, string connenctionString) + { + TServerTransport serverTransport = new TServerSocketTransport(port, Configuration); + TTransportFactory transportFactory = new TBufferedTransport.Factory(); + TProtocolFactory protocolFactory = new TBinaryProtocol.Factory(); + + var handler = new CSharpQueryHandler(connenctionString); + ITAsyncProcessor processor = new CsharpService.AsyncProcessor(handler); + + try + { + var server = new TSimpleAsyncServer( + itProcessorFactory: new TSingletonProcessorFactory(processor), + serverTransport: serverTransport, + inputTransportFactory: transportFactory, + outputTransportFactory: transportFactory, + inputProtocolFactory: protocolFactory, + outputProtocolFactory: protocolFactory, + logger: LoggingHelper.CreateLogger()); + + await server.ServeAsync(cancellationToken); + } + catch (Exception x) + { + Logger.LogInformation("{x}",x); + } + } + } +} diff --git a/plugins/csharp/service/src_csharp/CSharpServiceEnums.cs b/plugins/csharp/service/src_csharp/CSharpServiceEnums.cs new file mode 100644 index 000000000..8046956b6 --- /dev/null +++ b/plugins/csharp/service/src_csharp/CSharpServiceEnums.cs @@ -0,0 +1,142 @@ +enum ReferenceType +{ +DEFINITION, /*!< By this option the definition(s) of the AST node can be + queried. However according to the "one definition rule" a named entity + can have only one definition, in a parsing several definitions might be + available. This is the case when the project is built for several targets + and in the different builds different definitions are defined for an + entity (e.g. because of an #ifdef section). */ + +DECLARATION, /*!< By this options the declaration(s) of the AST node can be + queried. */ + +USAGE, /*!< By this option the usages of the AST node can be queried, i.e. + the nodes of which the entity hash is identical to the queried one. */ + +THIS_CALLS, /*!< Get function calls in a function. WARNING: If the + definition of the AST node is not unique then it returns the callees of + one of them. */ + +CALLS_OF_THIS, /*!< Get calls of a function. */ + +CALLEE, /*!< Get called functions definitions. WARNING: If the definition of + the AST node is not unique then it returns the callees of one of them. */ + +CALLER, /*!< Get caller functions. */ + +VIRTUAL_CALL, /*!< A function may be used virtually on a base type object. + The exact type of the object is based on dynamic information, which can't + be determined statically. Weak usage returns these possible calls. */ + +FUNC_PTR_CALL, /*!< Functions can be assigned to function pointers which + can be invoked later. This option returns these invocations. */ + +PARAMETER, /*!< This option returns the parameters of a function. */ + +LOCAL_VAR, /*!< This option returns the local variables of a function. */ + +RETURN_TYPE, /*!< This option returns the return type of a function. */ + +OVERRIDE, /*!< This option returns the functions which the given function + overrides. */ + +OVERRIDDEN_BY, /*!< This option returns the overrides of a function. */ + +USAGEREAD, /*!< This option returns the places where a variable is read. */ + +WRITE, /*!< This option returns the places where a variable is written. */ + +READ, +TYPE, /*!< This option returns the type of a variable. */ + +ALIAS, /*!< Types may have aliases, e.g. by typedefs. */ + +INHERIT_FROM, /*!< Types from which the queried type inherits. */ + +INHERIT_BY, /*!< Types by which the queried type is inherited. */ + +DATA_MEMBER, /*!< Data members of a class. */ + +METHOD, /*!< Members of a class. */ + +FRIEND, /*!< The friends of a class. */ + +UNDERLYING_TYPE, /*!< Underlying type of a typedef. */ + +ENUM_CONSTANTS, /*!< Enum constants. */ + +EXPANSION, /*!< Macro expansion. */ + +UNDEFINITION, /*!< Macro undefinition. */ + +EVALUATION, // LINQ evaluation + +DATA_MODIFICATION, // LINQ underlying datadtruct is modified + +CONSTRUCTOR, + +DESTRUCTOR, + +OPERATOR, + +ACCESSOR, + +DELEGATE, + +EVENT +}; + +enum FileReferenceType +{ +INCLUDES, /*!< Included source files in the current source file after the + inclusion directive. */ + +TYPES, /*!< User defined data types such as classes, structs etc. */ + +FUNCTIONS, /*!< Functions in the current source file. */ + +MACROS, /*!< Macros in the current source file. */ +}; + +enum DiagramType +{ +FUNCTION_CALL, /*!< In the function call diagram the nodes are functions and + the edges are the function calls between them. The diagram also displays + some dynamic information such as virtual function calls. */ + +DETAILED_CLASS, /*!< This is a classical UML class diagram for the selected + class and its direct children and parents. The nodes contain the methods + and member variables with their visibility. */ + +CLASS_OVERVIEW, /*!< This is a class diagram which contains all classes + which inherit from the current one, and all parents from which the + current one inherits. The methods and member variables are node included + in the nodes, but the type of the member variables are indicated as + aggregation relationship. */ + +CLASS_COLLABORATION, /*!< This returns a class collaboration diagram + which shows the individual class members and their inheritance + hierarchy. */ + +COMPONENT_USERS, /*!< Component users diagram for source file S shows which + source files depend on S through the interfaces S provides. */ + +EXTERNAL_DEPENDENCY, /*!< This diagram shows the module which directory + depends on. The "depends on" diagram on module A traverses the + subdirectories of module A and shows all directories that contain files + that any of the source files in A includes. */ + +EXTERNAL_USERS, /*!< This diagram shows directories (modules) that are + users of the queried module. */ + +INCLUDE_DEPENDENCY, /*!< This diagram shows of the `#include` file + dependencies. */ + +INTERFACE, /*!< Interface diagram shows the used and provided interfaces of + a source code file and shows linking information. */ + +SUBSYSTEM_DEPENDENCY, /*!< This diagram shows the directories relationship + between the subdirectories of the queried module. This diagram is useful + to understand the relationships of the subdirectories (submodules) + of a module. */ +}; \ No newline at end of file diff --git a/plugins/csharp/service/src_csharp/csharpservice.csproj b/plugins/csharp/service/src_csharp/csharpservice.csproj new file mode 100644 index 000000000..c40923804 --- /dev/null +++ b/plugins/csharp/service/src_csharp/csharpservice.csproj @@ -0,0 +1,32 @@ + + + + Exe + net6.0 + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + diff --git a/plugins/csharp/webgui/js/csharpDiagram.js b/plugins/csharp/webgui/js/csharpDiagram.js new file mode 100644 index 000000000..32b3c7caf --- /dev/null +++ b/plugins/csharp/webgui/js/csharpDiagram.js @@ -0,0 +1,99 @@ +require([ + 'dojo/topic', + 'dijit/Menu', + 'dijit/MenuItem', + 'dijit/PopupMenuItem', + 'codecompass/model', + 'codecompass/viewHandler'], +function (topic, Menu, MenuItem, PopupMenuItem, model, viewHandler) { + model.addService('csharpservice', 'CsharpService', LanguageServiceClient); + + var astDiagram = { + id : 'csharp-ast-diagram', + + getDiagram : function (diagramType, nodeId, callback) { + model.csharpservice.getDiagram(nodeId, diagramType, callback); + }, + + getDiagramLegend : function (diagramType) { + return model.csharpservice.getDiagramLegend(diagramType); + }, + + mouseOverInfo : function (diagramType, nodeId) { + var nodeInfo = model.csharpservice.getAstNodeInfo(nodeId); + var range = nodeInfo.range.range; + + return { + fileId : nodeInfo.range.file, + selection : [ + range.startpos.line, + range.startpos.column, + range.endpos.line, + range.endpos.column + ] + }; + } + }; + + viewHandler.registerModule(astDiagram, { + type : viewHandler.moduleType.Diagram + }); + + var fileDiagramHandler = { + id : 'csharp-file-diagram-handler', + + getDiagram : function (diagramType, nodeId, callback) { + model.csharpservice.getFileDiagram(nodeId, diagramType, callback); + }, + + getDiagramLegend : function (diagramType) { + return model.csharpservice.getFileDiagramLegend(diagramType); + }, + + mouseOverInfo : function (diagramType, nodeId) { + return { + fileId : nodeId, + selection : [1,1,1,1] + }; + } + }; + + viewHandler.registerModule(fileDiagramHandler, { + type : viewHandler.moduleType.Diagram + }); + + var fileDiagrams = { + id : 'csharp-file-diagrams', + render : function (fileInfo) { + var submenu = new Menu(); + + var diagramTypes = model.csharpservice.getFileDiagramTypes(fileInfo.id); + for (diagramType in diagramTypes) + submenu.addChild(new MenuItem({ + label : diagramType, + type : diagramType, + onClick : function () { + var that = this; + + topic.publish('codecompass/openFile', { fileId : fileInfo.id }); + + topic.publish('codecompass/openDiagram', { + handler : 'csharp-file-diagram-handler', + diagramType : diagramTypes[that.type], + node : fileInfo.id + }); + } + })); + + if (Object.keys(diagramTypes).length !== 0) + return new PopupMenuItem({ + label : 'C++ Diagrams', + popup : submenu + }); + } + }; + + viewHandler.registerModule(fileDiagrams, { + type : viewHandler.moduleType.FileManagerContextMenu + }); +}); diff --git a/plugins/csharp/webgui/js/csharpInfoTree.js b/plugins/csharp/webgui/js/csharpInfoTree.js new file mode 100644 index 000000000..9377ac3f0 --- /dev/null +++ b/plugins/csharp/webgui/js/csharpInfoTree.js @@ -0,0 +1,373 @@ +require([ + 'codecompass/model', + 'codecompass/viewHandler', + 'codecompass/util'], +function (model, viewHandler, util) { + + model.addService('csharpservice', 'CsharpService', LanguageServiceClient); + + function createTagLabels(tags) { + var label = ''; + + if (!tags) + return label; + + if (tags.indexOf('static') > -1) + label += 'S'; + if (tags.indexOf('constructor') > -1) + label += 'C'; + if (tags.indexOf('destructor') > -1) + label += 'D'; + if (tags.indexOf('implicit') > -1) + label += 'I'; + if (tags.indexOf('inherited') > -1) + label += 'I'; + if (tags.indexOf('virtual') > -1) + label += 'V'; + if (tags.indexOf('global') > -1) + label += 'G'; + + return label; + } + + function createReferenceCountLabel(label, count) { + return label + '(' + count + ')'; + } + + function createLabel(astNodeInfo) { + var labelClass = ''; + + if (astNodeInfo.tags.indexOf('implicit') > -1) + labelClass = 'label-implicit'; + + var labelValue = astNodeInfo.astNodeValue; + + // Create dom node for return type of a function and place it at the end of + // signature. + if (astNodeInfo.symbolType === 'Function') { + var init = labelValue.slice(0, labelValue.indexOf('(')); + var returnTypeEnd = init.lastIndexOf(' '); + + //--- Constructor, destructor doesn't have return type ---// + + if (returnTypeEnd !== -1) { + var funcSignature = init.slice(returnTypeEnd); + + labelValue = funcSignature + + ' : ' + + init.slice(0, returnTypeEnd) + + ""; + } + } + + var label = createTagLabels(astNodeInfo.tags) + + '' + + astNodeInfo.range.range.startpos.line + ':' + + astNodeInfo.range.range.startpos.column + ': ' + + labelValue + + ''; + + return label; + } + + function getCssClass(astNodeInfo) { + var tags = astNodeInfo.tags; + + return tags.indexOf('Public') > -1 ? 'icon-visibility icon-public' : + tags.indexOf('Private') > -1 ? 'icon-visibility icon-private' : + tags.indexOf('Protected') > -1 ? 'icon-visibility icon-protected' : + null; + } + + function groupReferencesByVisibilities(references, parentNode, nodeInfo) { + var res = []; + var visibilities = ['public', 'private', 'protected']; + + visibilities.forEach(function (visibility) { + var nodes = references.filter(function (reference) { + return reference.tags.indexOf(visibility) > -1; + }); + + if (!nodes.length) + return; + + res.push({ + id : nodeInfo.id + visibility + parentNode.refType, + name : createReferenceCountLabel(visibility, nodes.length), + refType : parentNode.refType, + hasChildren : true, + cssClass : 'icon-visibility icon-' + visibility, + getChildren : function () { + var res = []; + + nodes.forEach(function (reference) { + res.push({ + id : visibility + reference.id, + name : createLabel(reference), + refType : parentNode.refType, + nodeInfo : reference, + hasChildren : false, + cssClass : getCssClass(reference) + }); + }); + + return res; + } + }); + }); + + return res; + } + + function loadReferenceNodes(parentNode, nodeInfo, refTypes) { + var res = []; + var fileGroupsId = []; + + var references = model.csharpservice.getReferences( + nodeInfo.id, + parentNode.refType); + + if (parentNode.refType === refTypes['Method'] || + parentNode.refType === refTypes['Data member']) + return groupReferencesByVisibilities(references, parentNode, nodeInfo); + + references.forEach(function (reference) { + if (parentNode.refType === refTypes['Caller'] || + parentNode.refType === refTypes['Usage']) { + + //--- Group nodes by file name ---// + + var fileId = reference.range.file; + if (fileGroupsId[fileId]) + return; + + fileGroupsId[fileId] = parentNode.refType + fileId + reference.id; + + var referenceInFile = references.filter(function (reference) { + return reference.range.file === fileId; + }); + + var fileInfo = model.project.getFileInfo(fileId); + res.push({ + id : fileGroupsId[fileId], + name : createReferenceCountLabel( + fileInfo.name, referenceInFile.length), + refType : parentNode.refType, + hasChildren : true, + cssClass : util.getIconClass(fileInfo.path), + getChildren : function () { + var that = this; + var res = []; + + referenceInFile.forEach(function (reference) { + if (parentNode.refType === refTypes['Caller']) { + res.push({ + id : reference.id, + name : createLabel(reference), + nodeInfo : reference, + refType : parentNode.refType, + cssClass : 'icon icon-Method', + hasChildren : true, + getChildren : function () { + var res = []; + + //--- Recursive Node ---// + + var refCount = model.csharpservice.getReferenceCount( + reference.id, parentNode.refType); + + if (refCount) + res.push({ + id : 'Caller-' + reference.id, + name : createReferenceCountLabel( + parentNode.name, refCount), + nodeInfo : reference, + refType : parentNode.refType, + cssClass : parentNode.cssClass, + hasChildren : true, + getChildren : parentNode.getChildren + }); + + //--- Call ---// + + var calls = model.csharpservice.getReferences( + this.nodeInfo.id, + refTypes['This calls']); + + calls.forEach(function (call) { + if (call.entityHash === nodeInfo.entityHash) + res.push({ + name : createLabel(call), + refType : parentNode.refType, + nodeInfo : call, + hasChildren : false, + cssClass : getCssClass(call) + }); + }); + return res; + } + }); + } else if (parentNode.refType === refTypes['Usage']) { + res.push({ + id : fileGroupsId[fileId] + reference.id, + name : createLabel(reference), + refType : parentNode.refType, + nodeInfo : reference, + hasChildren : false, + cssClass : getCssClass(reference) + }); + } + }); + return res; + } + }); + } else { + res.push({ + name : createLabel(reference), + refType : parentNode.refType, + nodeInfo : reference, + hasChildren : false, + cssClass : getCssClass(reference) + }); + } + }); + + return res; + } + + /** + * This function returns file references children. + * @param parentNode Reference type node in Info Tree. + */ + function loadFileReferenceNodes(parentNode) { + var res = []; + + var references = model.csharpservice.getFileReferences( + parentNode.nodeInfo.id, + parentNode.refType); + + references.forEach(function (reference) { + res.push({ + name : createLabel(reference), + refType : parentNode.refType, + nodeInfo : reference, + hasChildren : false, + cssClass : getCssClass(reference) + }); + }); + + return res; + } + + function createRootNode(elementInfo) { + var rootLabel + = '' + + (elementInfo instanceof AstNodeInfo + ? elementInfo.symbolType + : 'File') + + ''; + + var rootValue + = '' + + (elementInfo instanceof AstNodeInfo + ? elementInfo.astNodeValue + : elementInfo.name) + + ''; + + var label = createTagLabels(elementInfo.tags) + + '' + + rootLabel + ': ' + rootValue + + ''; + + return { + id : 'root', + name : label, + cssClass : 'icon-info', + hasChildren : true, + getChildren : function () { + return that._store.query({ parent : 'root' }); + } + }; + } + + var csharpInfoTree = { + id : 'csharp-infotree', + render : function (elementInfo) { + var ret = []; + + ret.push(createRootNode(elementInfo)); + + if (elementInfo instanceof AstNodeInfo) { + //--- Properties ---// + + var props = model.csharpservice.getProperties(elementInfo.id); + + for (var propName in props) { + var propId = propName.replace(/ /g, '-'); + var label + = '' + propName + ': ' + + '' + props[propName] + ''; + + ret.push({ + name : label, + parent : 'root', + nodeInfo : elementInfo, + cssClass : 'icon-' + propId, + hasChildren : false + }); + } + + //--- References ---// + + var refTypes = model.csharpservice.getReferenceTypes(elementInfo.id); + for (var refType in refTypes) { + var refCount = + model.csharpservice.getReferenceCount(elementInfo.id, refTypes[refType]); + + if (refCount) + ret.push({ + name : createReferenceCountLabel(refType, refCount), + parent : 'root', + refType : refTypes[refType], + cssClass : 'icon-' + refType.replace(/ /g, '-'), + hasChildren : true, + getChildren : function () { + return loadReferenceNodes(this, elementInfo, refTypes); + } + }); + }; + + } else if (elementInfo instanceof FileInfo) { + + //--- File references ---// + + var refTypes = model.csharpservice.getFileReferenceTypes(elementInfo.id); + for (var refType in refTypes) { + var refCount = model.csharpservice.getFileReferenceCount( + elementInfo.id, refTypes[refType]); + + if (refCount) + ret.push({ + name : createReferenceCountLabel(refType, refCount), + parent : 'root', + nodeInfo : elementInfo, + refType : refTypes[refType], + cssClass : 'icon-' + refType.replace(/ /g, '-'), + hasChildren : true, + getChildren : function () { + return loadFileReferenceNodes(this); + } + }); + }; + + } + + return ret; + } + }; + + viewHandler.registerModule(csharpInfoTree, { + type : viewHandler.moduleType.InfoTree, + service : model.csharpservice + }); +}); diff --git a/plugins/csharp/webgui/js/csharpMenu.js b/plugins/csharp/webgui/js/csharpMenu.js new file mode 100644 index 000000000..bd95aca1b --- /dev/null +++ b/plugins/csharp/webgui/js/csharpMenu.js @@ -0,0 +1,151 @@ +require([ + 'dojo/topic', + 'dijit/Menu', + 'dijit/MenuItem', + 'dijit/PopupMenuItem', + 'codecompass/astHelper', + 'codecompass/model', + 'codecompass/urlHandler', + 'codecompass/viewHandler'], +function (topic, Menu, MenuItem, PopupMenuItem, astHelper, model, urlHandler, viewHandler) { + + model.addService('csharpservice', 'CsharpService', LanguageServiceClient); + + var getdefintion = { + id : 'csharp-text-getdefintion', + render : function (nodeInfo, fileInfo) { + return new MenuItem({ + label : 'Jump to definition', + accelKey : 'ctrl - click', + onClick : function () { + if (!nodeInfo || !fileInfo) + return; + + var languageService = model.getLanguageService(fileInfo.type); + astHelper.jumpToDef(nodeInfo.id, model.csharpservice); + + if (window.gtag) { + window.gtag ('event', 'jump_to_def', { + 'event_category' : urlHandler.getState('wsid'), + 'event_label' : urlHandler.getFileInfo().name + + ': ' + + nodeInfo.astNodeValue + }); + } + } + }); + } + }; + + viewHandler.registerModule(getdefintion, { + type : viewHandler.moduleType.TextContextMenu, + service : model.csharpservice + }); + + var infoTree = { + id : 'csharp-text-infotree', + render : function (nodeInfo, fileInfo) { + return new MenuItem({ + label : 'Info Tree', + onClick : function () { + if (!nodeInfo || !fileInfo) + return; + + topic.publish('codecompass/infotree', { + fileType : fileInfo.type, + elementInfo : nodeInfo + }); + + if (window.gtag) { + window.gtag ('event', 'info_tree', { + 'event_category' : urlHandler.getState('wsid'), + 'event_label' : urlHandler.getFileInfo().name + + ': ' + + nodeInfo.astNodeValue + }); + } + } + }); + } + }; + + viewHandler.registerModule(infoTree, { + type : viewHandler.moduleType.TextContextMenu, + service : model.csharpservice + }); + + var infobox = { + id : 'csharp-text-infobox', + render : function (nodeInfo, fileInfo) { + return new MenuItem({ + label : 'Documentation', + onClick : function () { + topic.publish('codecompass/documentation', { + fileType : fileInfo.type, + elementInfo : nodeInfo + }); + + if (window.gtag) { + window.gtag ('event', 'documentation', { + 'event_category' : urlHandler.getState('wsid'), + 'event_label' : urlHandler.getFileInfo().name + + ': ' + + nodeInfo.astNodeValue + }); + } + } + }); + } + }; + + viewHandler.registerModule(infobox, { + type : viewHandler.moduleType.TextContextMenu, + service : model.csharpservice + }); + + var diagrams = { + id : 'csharp-text-diagrams', + render : function (nodeInfo, fileInfo) { + if (!nodeInfo || !fileInfo) + return; + + var submenu = new Menu(); + + var diagramTypes = model.csharpservice.getDiagramTypes(nodeInfo.id); + for (diagramType in diagramTypes) + submenu.addChild(new MenuItem({ + label : diagramType, + type : diagramType, + onClick : function () { + var that = this; + + topic.publish('codecompass/openDiagram', { + handler : 'csharp-ast-diagram', + diagramType : diagramTypes[that.type], + node : nodeInfo.id + }); + } + })); + + submenu.addChild(new MenuItem({ + label : "CodeBites", + onClick : function () { + topic.publish('codecompass/codebites', { + node : nodeInfo + }); + } + })); + + if (Object.keys(diagramTypes).length !== 0) + return new PopupMenuItem({ + label : 'Diagrams', + popup : submenu + }); + } + }; + + viewHandler.registerModule(diagrams, { + type : viewHandler.moduleType.TextContextMenu, + service : model.csharpservice + }); +}); diff --git a/service/language/CMakeLists.txt b/service/language/CMakeLists.txt index 8cb21a098..b37f6f3b2 100644 --- a/service/language/CMakeLists.txt +++ b/service/language/CMakeLists.txt @@ -14,8 +14,9 @@ add_custom_command( ${CMAKE_CURRENT_BINARY_DIR}/gen-cpp/LanguageService.h ${CMAKE_CURRENT_BINARY_DIR}/gen-cpp ${CMAKE_CURRENT_BINARY_DIR}/gen-js + ${CMAKE_CURRENT_BINARY_DIR}/gen-netstd COMMAND - ${THRIFT_EXECUTABLE} --gen cpp --gen js + ${THRIFT_EXECUTABLE} --gen cpp --gen js --gen netstd -o ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/language.thrift DEPENDS diff --git a/service/language/language.thrift b/service/language/language.thrift index 91591abe8..6fc79d267 100644 --- a/service/language/language.thrift +++ b/service/language/language.thrift @@ -2,6 +2,7 @@ include "../project/common.thrift" include "../project/project.thrift" namespace cpp cc.service.language +namespace netstd language struct AstNodeInfo {