From 031fd00024c05cac3568f5ed027e80991dba18b0 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 18 Nov 2015 17:55:30 -0800 Subject: [PATCH 001/207] fix for rpc constants and other pr feedback --- list.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/list.hpp b/list.hpp index 54bbed41..507c968e 100644 --- a/list.hpp +++ b/list.hpp @@ -57,6 +57,4 @@ namespace realm { }; } - - #endif /* REALM_LIST_HPP */ From ee6c6eb30fdfc1024b08738aedf5dfc831aab1b8 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 19 Nov 2015 07:07:33 -0800 Subject: [PATCH 002/207] size_t, no std::size_t --- list.cpp | 12 ++++++------ list.hpp | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/list.cpp b/list.cpp index 1cbf1477..1cbafb8d 100644 --- a/list.cpp +++ b/list.cpp @@ -26,40 +26,40 @@ size_t List::size() { return m_link_view->size(); } -Row List::get(std::size_t row_ndx) { +Row List::get(size_t row_ndx) { verify_attached(); verify_valid_row(row_ndx); return m_link_view->get(row_ndx); } -void List::set(std::size_t row_ndx, std::size_t target_row_ndx) { +void List::set(size_t row_ndx, size_t target_row_ndx) { verify_attached(); verify_in_tranaction(); verify_valid_row(row_ndx); m_link_view->set(row_ndx, target_row_ndx); } -void List::add(std::size_t target_row_ndx) { +void List::add(size_t target_row_ndx) { verify_attached(); verify_in_tranaction(); m_link_view->add(target_row_ndx); } -void List::insert(std::size_t row_ndx, std::size_t target_row_ndx) { +void List::insert(size_t row_ndx, size_t target_row_ndx) { verify_attached(); verify_in_tranaction(); verify_valid_row(row_ndx, true); m_link_view->insert(row_ndx, target_row_ndx); } -void List::remove(std::size_t row_ndx) { +void List::remove(size_t row_ndx) { verify_attached(); verify_in_tranaction(); verify_valid_row(row_ndx); m_link_view->remove(row_ndx); } -void List::verify_valid_row(std::size_t row_ndx, bool insertion) { +void List::verify_valid_row(size_t row_ndx, bool insertion) { size_t size = m_link_view->size(); if (row_ndx > size || (!insertion && row_ndx == size)) { throw std::out_of_range(std::string("Index ") + std::to_string(row_ndx) + " is outside of range 0..." + std::to_string(size) + "."); diff --git a/list.hpp b/list.hpp index 507c968e..fdde4c62 100644 --- a/list.hpp +++ b/list.hpp @@ -31,8 +31,8 @@ namespace realm { SharedRealm realm() { return m_realm; } size_t size(); - Row get(std::size_t row_ndx); - void set(std::size_t row_ndx, std::size_t target_row_ndx); + Row get(size_t row_ndx); + void set(size_t row_ndx, size_t target_row_ndx); void add(size_t target_row_ndx); void remove(size_t list_ndx); @@ -47,7 +47,7 @@ namespace realm { template void set(ContextType ctx, ValueType value, size_t list_ndx); - void verify_valid_row(std::size_t row_ndx, bool insertion = false); + void verify_valid_row(size_t row_ndx, bool insertion = false); void verify_attached(); void verify_in_tranaction(); From b515b4b6d9d0cbb3d290ff26273595d8b480b0eb Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 5 Nov 2015 16:21:19 -0800 Subject: [PATCH 003/207] beginnings of a parser --- parser/parser.cpp | 65 +++++++++++++++++++++++++++++++++++++++++++++++ parser/query.abnf | 23 +++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 parser/parser.cpp create mode 100644 parser/query.abnf diff --git a/parser/parser.cpp b/parser/parser.cpp new file mode 100644 index 00000000..5d89918e --- /dev/null +++ b/parser/parser.cpp @@ -0,0 +1,65 @@ +#include +#include + +#include + +using namespace pegtl; + +namespace query +{ + // strings + struct unicode : list< seq< one< 'u' >, rep< 4, must< xdigit > > >, one< '\\' > > {}; + struct escaped_char : one< '"', '\\', '/', 'b', 'f', 'n', 'r', 't' > {}; + struct escaped : sor< escaped_char, unicode > {}; + struct unescaped : utf8::range< 0x20, 0x10FFFF > {}; + struct char_ : if_then_else< one< '\\' >, must< escaped >, unescaped > {}; + + struct string_content : until< at< one< '"' > >, must< char_ > > {}; + struct string : seq< one< '"' >, must< string_content >, any > + { + using content = string_content; + }; + + // numbers + struct minus : opt< one< '-' > > {}; + struct dot : one< '.' > {}; + + struct float_num : sor< + seq< plus< digit >, dot, star< digit > >, + seq< star< digit >, dot, plus< digit > > + > {}; + struct hex_num : seq< one< '0' >, one< 'x', 'X' >, plus< xdigit > > {}; + struct int_num : plus< digit > {}; + + struct number : seq< minus, sor< float_num, hex_num, int_num > > {}; + + // key paths + struct key_path : list< must< sor< alpha, one< '_' > >, star< sor< alnum, one< '_', '-' > > > >, one< '.' > > {}; + + // expressions and operators + struct expr : sor< string, key_path, number > {}; + struct oper : sor< one< '=' >, istring< '=', '=' >, istring< '!', '=' >, one< '<' >, istring< '<', '=' >, one< '>' >, istring< '>', '=' > > {}; + + // predicates + struct pred : seq< expr, plus< blank >, oper, plus< blank >, expr > {}; + + // rules + template< typename Rule > + struct action : nothing< Rule > {}; + template<> struct action< expr > + { + static void apply( const input & in, std::string & string_value ) + { + std::cout << in.string() << std::endl; + } + }; +} + +int main( int argc, char ** argv ) +{ + if ( argc > 1 ) { + std::string intstring; + parse< must< query::expr, eof>, query::action >( 1, argv, intstring); + } +} + diff --git a/parser/query.abnf b/parser/query.abnf new file mode 100644 index 00000000..d70416d3 --- /dev/null +++ b/parser/query.abnf @@ -0,0 +1,23 @@ + + +pred = expr 1*WSP oper 1*WSP expr +;pred =/ "(" *WSP pred *WSP ")" +;pred =/ ("NOT" / "!") 1*WSP pred +;pred =/ pred 1*WSP ("OR" / "||") !*WSP pred +;pred =/ pred 1*WSP ("AND" / "&&") 1*WSP pred + +oper = "=" / "==" / "!=" / "<" / "<=" / ">" / ">=" + +expr = string / num / key-path + +key-path = key *("." key) +key = 1*(ALPHA / "_") + +string = dq-string / sq-string +sq-string = "'" *(%x20-ffffffff) "'" +dq-string = DQUOTE *("\\" / %x20-21 / %x23-ffffffff) DQUOTE + +num = float-num / int-num / hex-num +float-num = ["-"] (*DIGIT "." 1*DIGIT / "." 1*DIGIT) +int-num = ["-"] 1*DIGIT +hex-num = ["-"] ["0"] "x" 1*HEXDIG From 93adb0cb84a2b585a2136c18698b0e6060c16f9a Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Fri, 6 Nov 2015 08:23:23 -0800 Subject: [PATCH 004/207] compound predicates --- parser/parser.cpp | 57 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/parser/parser.cpp b/parser/parser.cpp index 5d89918e..ce211a21 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include using namespace pegtl; @@ -34,32 +36,77 @@ namespace query struct number : seq< minus, sor< float_num, hex_num, int_num > > {}; // key paths - struct key_path : list< must< sor< alpha, one< '_' > >, star< sor< alnum, one< '_', '-' > > > >, one< '.' > > {}; + struct key_path : list< seq< sor< alpha, one< '_' > >, star< sor< alnum, one< '_', '-' > > > >, one< '.' > > {}; // expressions and operators struct expr : sor< string, key_path, number > {}; - struct oper : sor< one< '=' >, istring< '=', '=' >, istring< '!', '=' >, one< '<' >, istring< '<', '=' >, one< '>' >, istring< '>', '=' > > {}; + struct oper : sor< + two< '=' >, + one< '=' >, + pegtl::string< '!', '=' >, + pegtl::string< '<', '=' >, + one< '<' >, + pegtl::string< '>', '=' >, + one< '>' > + > {}; // predicates - struct pred : seq< expr, plus< blank >, oper, plus< blank >, expr > {}; + struct comparison_pred : seq< expr, pad< oper, blank >, expr > {}; + + struct pred; + struct group_pred : if_must< one< '(' >, pad< pred, blank >, one< ')' > > {}; + + struct single_pred : pad< sor< group_pred, comparison_pred >, blank > {}; + struct not_pre : pegtl::string< 'N', 'O', 'T' > {}; + struct atom_pred : seq< opt< not_pre >, single_pred > {}; + + struct or_ext : if_must< two< '|' >, atom_pred > {}; + struct and_ext : if_must< two< '&' >, atom_pred > {}; + + struct pred : seq< atom_pred, star< sor< or_ext, and_ext > > > {}; // rules template< typename Rule > struct action : nothing< Rule > {}; - template<> struct action< expr > + template<> struct action< and_ext > + { + static void apply( const input & in, std::string & string_value ) + { + std::cout << "" << in.string() << std::endl; + } + }; + + template<> struct action< or_ext > + { + static void apply( const input & in, std::string & string_value ) + { + std::cout << "" << in.string() << std::endl; + } + }; + + template<> struct action< comparison_pred > { static void apply( const input & in, std::string & string_value ) { std::cout << in.string() << std::endl; } }; + + template<> struct action< group_pred > + { + static void apply( const input & in, std::string & string_value ) + { + std::cout << "" << std::endl; + } + }; } int main( int argc, char ** argv ) { if ( argc > 1 ) { std::string intstring; - parse< must< query::expr, eof>, query::action >( 1, argv, intstring); + analyze< query::pred >(); + parse< must< seq< query::pred, eof > >, query::action >( 1, argv, intstring); } } From 15ee92ce609e41807e856af09c18ec52ffa1e8ab Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 9 Nov 2015 08:27:27 -0800 Subject: [PATCH 005/207] full grammar --- parser/parser.cpp | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/parser/parser.cpp b/parser/parser.cpp index ce211a21..d3d1f92d 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -57,13 +57,17 @@ namespace query struct group_pred : if_must< one< '(' >, pad< pred, blank >, one< ')' > > {}; struct single_pred : pad< sor< group_pred, comparison_pred >, blank > {}; - struct not_pre : pegtl::string< 'N', 'O', 'T' > {}; + struct not_pre : sor< seq< one< '!' >, star< blank > >, seq< istring< 'N', 'O', 'T' >, plus< blank > > > {}; struct atom_pred : seq< opt< not_pre >, single_pred > {}; - struct or_ext : if_must< two< '|' >, atom_pred > {}; - struct and_ext : if_must< two< '&' >, atom_pred > {}; + struct and_op : sor< two< '&' >, istring< 'A', 'N', 'D' > > {}; + struct or_op : sor< two< '|' >, istring< 'O', 'R' > > {}; - struct pred : seq< atom_pred, star< sor< or_ext, and_ext > > > {}; + struct or_ext : seq< pad< or_op, blank >, pred > {}; + struct and_ext : seq< pad< and_op, blank >, pred > {}; + struct and_pred : seq< atom_pred, star< and_ext > > {}; + + struct pred : seq< and_pred, star< or_ext > > {}; // rules template< typename Rule > @@ -92,11 +96,27 @@ namespace query } }; + template<> struct action< one< '(' > > + { + static void apply( const input & in, std::string & string_value ) + { + std::cout << "" << std::endl; + } + }; + template<> struct action< group_pred > { static void apply( const input & in, std::string & string_value ) { - std::cout << "" << std::endl; + std::cout << "" << std::endl; + } + }; + + template<> struct action< not_pre > + { + static void apply( const input & in, std::string & string_value ) + { + std::cout << "" << std::endl; } }; } @@ -106,7 +126,7 @@ int main( int argc, char ** argv ) if ( argc > 1 ) { std::string intstring; analyze< query::pred >(); - parse< must< seq< query::pred, eof > >, query::action >( 1, argv, intstring); + parse< must< query::pred, eof >, query::action >( 1, argv, intstring); } } From 30147821a216a2674ef239c9eb258ffadbc79c07 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 9 Nov 2015 11:29:57 -0800 Subject: [PATCH 006/207] parse tree construction --- parser/main.cpp | 7 + parser/parser.cpp | 322 +++++++++++++++++++++++++++++++--------------- parser/parser.hpp | 78 +++++++++++ 3 files changed, 301 insertions(+), 106 deletions(-) create mode 100644 parser/main.cpp create mode 100644 parser/parser.hpp diff --git a/parser/main.cpp b/parser/main.cpp new file mode 100644 index 00000000..def876c3 --- /dev/null +++ b/parser/main.cpp @@ -0,0 +1,7 @@ +#include "parser.hpp" + +int main( int argc, char ** argv ) +{ + realm::parser::parse(argv[1]); +} + diff --git a/parser/parser.cpp b/parser/parser.cpp index d3d1f92d..384d748a 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -1,4 +1,23 @@ -#include +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "parser.hpp" + #include #include @@ -7,126 +26,217 @@ using namespace pegtl; -namespace query +namespace realm { +namespace parser { + +// strings +struct unicode : list< seq< one< 'u' >, rep< 4, must< xdigit > > >, one< '\\' > > {}; +struct escaped_char : one< '"', '\\', '/', 'b', 'f', 'n', 'r', 't' > {}; +struct escaped : sor< escaped_char, unicode > {}; +struct unescaped : utf8::range< 0x20, 0x10FFFF > {}; +struct char_ : if_then_else< one< '\\' >, must< escaped >, unescaped > {}; + +struct string_content : until< at< one< '"' > >, must< char_ > > {}; +struct string : seq< one< '"' >, must< string_content >, any > { - // strings - struct unicode : list< seq< one< 'u' >, rep< 4, must< xdigit > > >, one< '\\' > > {}; - struct escaped_char : one< '"', '\\', '/', 'b', 'f', 'n', 'r', 't' > {}; - struct escaped : sor< escaped_char, unicode > {}; - struct unescaped : utf8::range< 0x20, 0x10FFFF > {}; - struct char_ : if_then_else< one< '\\' >, must< escaped >, unescaped > {}; + using content = string_content; +}; - struct string_content : until< at< one< '"' > >, must< char_ > > {}; - struct string : seq< one< '"' >, must< string_content >, any > - { - using content = string_content; - }; +// numbers +struct minus : opt< one< '-' > > {}; +struct dot : one< '.' > {}; - // numbers - struct minus : opt< one< '-' > > {}; - struct dot : one< '.' > {}; +struct float_num : sor< + seq< plus< digit >, dot, star< digit > >, + seq< star< digit >, dot, plus< digit > > +> {}; +struct hex_num : seq< one< '0' >, one< 'x', 'X' >, plus< xdigit > > {}; +struct int_num : plus< digit > {}; - struct float_num : sor< - seq< plus< digit >, dot, star< digit > >, - seq< star< digit >, dot, plus< digit > > - > {}; - struct hex_num : seq< one< '0' >, one< 'x', 'X' >, plus< xdigit > > {}; - struct int_num : plus< digit > {}; +struct number : seq< minus, sor< float_num, hex_num, int_num > > {}; - struct number : seq< minus, sor< float_num, hex_num, int_num > > {}; +// key paths +struct key_path : list< seq< sor< alpha, one< '_' > >, star< sor< alnum, one< '_', '-' > > > >, one< '.' > > {}; - // key paths - struct key_path : list< seq< sor< alpha, one< '_' > >, star< sor< alnum, one< '_', '-' > > > >, one< '.' > > {}; +// expressions and operators +struct expr : sor< string, key_path, number > {}; - // expressions and operators - struct expr : sor< string, key_path, number > {}; - struct oper : sor< - two< '=' >, - one< '=' >, - pegtl::string< '!', '=' >, - pegtl::string< '<', '=' >, - one< '<' >, - pegtl::string< '>', '=' >, - one< '>' > - > {}; +struct eq : sor< two< '=' >, one< '=' > > {}; +struct noteq : pegtl::string< '!', '=' > {}; +struct lteq : pegtl::string< '<', '=' > {}; +struct lt : one< '<' > {}; +struct gteq : pegtl::string< '>', '=' > {}; +struct gt : one< '<' > {}; +struct begins : istring< 'b', 'e', 'g', 'i', 'n', 's', 'w', 'i', 't', 'h' > {}; +struct ends : istring< 'e', 'n', 'd', 's', 'w', 'i', 't', 'h' > {}; +struct contains : istring< 'c', 'o', 'n', 't', 'a', 'i', 'n', 's' > {}; +struct oper : sor< eq, noteq, lteq, lt, gteq, gt, begins, ends, contains > {}; - // predicates - struct comparison_pred : seq< expr, pad< oper, blank >, expr > {}; +// predicates +struct comparison_pred : seq< expr, pad< oper, blank >, expr > {}; - struct pred; - struct group_pred : if_must< one< '(' >, pad< pred, blank >, one< ')' > > {}; +struct pred; +struct group_pred : if_must< one< '(' >, pad< pred, blank >, one< ')' > > {}; - struct single_pred : pad< sor< group_pred, comparison_pred >, blank > {}; - struct not_pre : sor< seq< one< '!' >, star< blank > >, seq< istring< 'N', 'O', 'T' >, plus< blank > > > {}; - struct atom_pred : seq< opt< not_pre >, single_pred > {}; +struct not_pre : sor< seq< one< '!' >, star< blank > >, seq< istring< 'N', 'O', 'T' >, plus< blank > > > {}; +struct atom_pred : seq< opt< not_pre >, pad< sor< group_pred, comparison_pred >, blank > > {}; - struct and_op : sor< two< '&' >, istring< 'A', 'N', 'D' > > {}; - struct or_op : sor< two< '|' >, istring< 'O', 'R' > > {}; +struct and_op : sor< two< '&' >, istring< 'A', 'N', 'D' > > {}; +struct or_op : sor< two< '|' >, istring< 'O', 'R' > > {}; - struct or_ext : seq< pad< or_op, blank >, pred > {}; - struct and_ext : seq< pad< and_op, blank >, pred > {}; - struct and_pred : seq< atom_pred, star< and_ext > > {}; +struct or_ext : seq< pad< or_op, blank >, pred > {}; +struct and_ext : seq< pad< and_op, blank >, pred > {}; +struct and_pred : seq< atom_pred, star< and_ext > > {}; - struct pred : seq< and_pred, star< or_ext > > {}; +struct pred : seq< and_pred, star< or_ext > > {}; - // rules - template< typename Rule > - struct action : nothing< Rule > {}; - template<> struct action< and_ext > - { - static void apply( const input & in, std::string & string_value ) - { - std::cout << "" << in.string() << std::endl; - } - }; - - template<> struct action< or_ext > - { - static void apply( const input & in, std::string & string_value ) - { - std::cout << "" << in.string() << std::endl; - } - }; - - template<> struct action< comparison_pred > - { - static void apply( const input & in, std::string & string_value ) - { - std::cout << in.string() << std::endl; - } - }; - - template<> struct action< one< '(' > > - { - static void apply( const input & in, std::string & string_value ) - { - std::cout << "" << std::endl; - } - }; - - template<> struct action< group_pred > - { - static void apply( const input & in, std::string & string_value ) - { - std::cout << "" << std::endl; - } - }; - - template<> struct action< not_pre > - { - static void apply( const input & in, std::string & string_value ) - { - std::cout << "" << std::endl; - } - }; -} - -int main( int argc, char ** argv ) +// state +struct ParserState { - if ( argc > 1 ) { - std::string intstring; - analyze< query::pred >(); - parse< must< query::pred, eof >, query::action >( 1, argv, intstring); + std::vector predicate_stack; + Predicate *current() { return predicate_stack.back(); } + + void addExpression(Expression exp) + { + if (current()->type == Predicate::Type::Comparison) { + current()->sub_expressions.emplace_back(std::move(exp)); + predicate_stack.pop_back(); + } + else { + Predicate p; + p.type = Predicate::Type::Comparison; + p.sub_expressions.emplace_back(std::move(exp)); + current()->sub_predicates.emplace_back(std::move(p)); + predicate_stack.push_back(¤t()->sub_predicates.back()); + } } +}; + +// rules +template< typename Rule > +struct action : nothing< Rule > {}; + +template<> struct action< and_ext > +{ + static void apply( const input & in, ParserState & state ) + { + std::cout << "" << in.string() << std::endl; + } +}; + +template<> struct action< or_ext > +{ + static void apply( const input & in, ParserState & state ) + { + std::cout << "" << in.string() << std::endl; + } +}; + +template<> struct action< string > +{ + static void apply( const input & in, ParserState & state ) + { + std::cout << in.string() << std::endl; + + Expression exp; + exp.type = Expression::Type::String; + exp.s = in.string(); + + state.addExpression(exp); + } +}; + +template<> struct action< key_path > +{ + static void apply( const input & in, ParserState & state ) + { + std::cout << in.string() << std::endl; + + Expression exp; + exp.type = Expression::Type::KeyPath; + exp.s = in.string(); + + state.addExpression(std::move(exp)); + } +}; + +template<> struct action< number > +{ + static void apply( const input & in, ParserState & state ) + { + std::cout << in.string() << std::endl; + + Expression exp; + exp.type = Expression::Type::Number; + exp.s = in.string(); + + state.addExpression(std::move(exp)); + } +}; + +#define OPERATOR_ACTION(rule, oper) \ +template<> struct action< rule > { \ + static void apply( const input & in, ParserState & state ) { \ + std::cout << in.string() << std::endl; \ + state.current()->op = oper; }}; + +OPERATOR_ACTION(eq, Predicate::Operator::Equal) +OPERATOR_ACTION(noteq, Predicate::Operator::NotEqual) +OPERATOR_ACTION(gteq, Predicate::Operator::GreaterThanOrEqual) +OPERATOR_ACTION(gt, Predicate::Operator::GreaterThan) +OPERATOR_ACTION(lteq, Predicate::Operator::LessThanOrEqual) +OPERATOR_ACTION(lt, Predicate::Operator::LessThan) +OPERATOR_ACTION(begins, Predicate::Operator::BeginsWith) +OPERATOR_ACTION(ends, Predicate::Operator::EndsWith) +OPERATOR_ACTION(contains, Predicate::Operator::Contains) + + +template<> struct action< one< '(' > > +{ + static void apply( const input & in, ParserState & state ) + { + std::cout << "" << std::endl; + + Predicate group; + group.type = Predicate::Type::And; + state.current()->sub_predicates.emplace_back(std::move(group)); + state.predicate_stack.push_back(&state.current()->sub_predicates.back()); + } +}; + +template<> struct action< group_pred > +{ + static void apply( const input & in, ParserState & state ) + { + std::cout << "" << std::endl; + + state.predicate_stack.pop_back(); + } +}; + +template<> struct action< not_pre > +{ + static void apply( const input & in, ParserState & state ) + { + std::cout << "" << std::endl; + } +}; + +Predicate parse(const std::string &query) +{ + analyze< pred >(); + const std::string source = "user query"; + + Predicate out_predicate; + out_predicate.type = Predicate::Type::And; + + ParserState state; + state.predicate_stack.push_back(&out_predicate); + + pegtl::parse< must< pred, eof >, action >(query, source, state); + return out_predicate; } +}} + + diff --git a/parser/parser.hpp b/parser/parser.hpp new file mode 100644 index 00000000..416faa2e --- /dev/null +++ b/parser/parser.hpp @@ -0,0 +1,78 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_EXTERNAL_COMMIT_HELPER_HPP +#define REALM_EXTERNAL_COMMIT_HELPER_HPP + +#include +#include + +namespace realm { + namespace parser { + struct Expression + { + enum class Type + { + Number, + String, + KeyPath, + }; + Type type; + + std::string s; + }; + + struct Predicate + { + enum class Type + { + None, + Comparison, + Or, + And, + True, + False, + }; + Type type = Type::None; + + // for comparison + enum class Operator + { + None, + Equal, + NotEqual, + LessThan, + LessThanOrEqual, + GreaterThan, + GreaterThanOrEqual, + BeginsWith, + EndsWith, + Contains, + }; + Operator op = Operator::None; + std::vector sub_expressions; + + // for compounds + std::vector sub_predicates; + }; + + Predicate parse(const std::string &query); + } +} + +#endif // REALM_EXTERNAL_COMMIT_HELPER_HPP From 51f5a422fd356efd82bfd6cf28faa9796ed4080c Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 9 Nov 2015 12:13:01 -0800 Subject: [PATCH 007/207] support OR with proper precedence --- parser/parser.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/parser/parser.cpp b/parser/parser.cpp index 384d748a..ab2b8726 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -121,6 +121,28 @@ template<> struct action< and_ext > static void apply( const input & in, ParserState & state ) { std::cout << "" << in.string() << std::endl; + + // if we were put into an OR group we need to rearrange + auto current = state.current(); + if (current->type == Predicate::Type::Or) { + auto &sub_preds = state.current()->sub_predicates; + auto second_last = sub_preds[sub_preds.size()-2]; + if (second_last.type == Predicate::Type::And) { + // if we are in an OR group and second to last predicate group is + // an AND group then move the last predicate inside + second_last.sub_predicates.push_back(sub_preds.back()); + sub_preds.pop_back(); + } + else { + // otherwise combine last two into a new AND group + Predicate pred; + pred.type = Predicate::Type::And; + pred.sub_predicates = { second_last, sub_preds.back() }; + sub_preds.pop_back(); + sub_preds.pop_back(); + sub_preds.push_back(pred); + } + } } }; @@ -129,6 +151,29 @@ template<> struct action< or_ext > static void apply( const input & in, ParserState & state ) { std::cout << "" << in.string() << std::endl; + + // if already an OR group do nothing + auto current = state.current(); + if (current->type == Predicate::Type::Or) { + return; + } + + // if only two predicates in the group, then convert to OR + auto &sub_preds = state.current()->sub_predicates; + if (sub_preds.size()) { + current->type = Predicate::Type::Or; + return; + } + + // split the current group into to groups which are ORed together + Predicate pred1, pred2; + pred1.type = Predicate::Type::And; + pred2.type = Predicate::Type::And; + pred1.sub_predicates.insert(sub_preds.begin(), sub_preds.back()); + pred2.sub_predicates.push_back(sub_preds.back()); + + current->type = Predicate::Type::Or; + sub_preds = { pred1, pred2 }; } }; From d59e6b1f58edd60428f7e21f516854493a2e9ceb Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 9 Nov 2015 12:20:59 -0800 Subject: [PATCH 008/207] store negated predicates in parse tree --- parser/parser.cpp | 12 ++++++++++++ parser/parser.hpp | 2 ++ 2 files changed, 14 insertions(+) diff --git a/parser/parser.cpp b/parser/parser.cpp index ab2b8726..3b504ee9 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -95,6 +95,7 @@ struct ParserState { std::vector predicate_stack; Predicate *current() { return predicate_stack.back(); } + bool negate_next; void addExpression(Expression exp) { @@ -106,6 +107,10 @@ struct ParserState Predicate p; p.type = Predicate::Type::Comparison; p.sub_expressions.emplace_back(std::move(exp)); + if (negate_next) { + p.negate = true; + negate_next = false; + } current()->sub_predicates.emplace_back(std::move(p)); predicate_stack.push_back(¤t()->sub_predicates.back()); } @@ -244,6 +249,11 @@ template<> struct action< one< '(' > > Predicate group; group.type = Predicate::Type::And; + if (state.negate_next) { + group.negate = true; + state.negate_next = false; + } + state.current()->sub_predicates.emplace_back(std::move(group)); state.predicate_stack.push_back(&state.current()->sub_predicates.back()); } @@ -264,6 +274,8 @@ template<> struct action< not_pre > static void apply( const input & in, ParserState & state ) { std::cout << "" << std::endl; + + state.negate_next = true; } }; diff --git a/parser/parser.hpp b/parser/parser.hpp index 416faa2e..149f12ff 100644 --- a/parser/parser.hpp +++ b/parser/parser.hpp @@ -69,6 +69,8 @@ namespace realm { // for compounds std::vector sub_predicates; + + bool negate; }; Predicate parse(const std::string &query); From 1f78bf7db686041a5489e4fb98adee6b26c6a4bb Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 9 Nov 2015 21:12:18 -0800 Subject: [PATCH 009/207] hook it up --- parser/parser.cpp | 351 ++++++++++++++++++++++++++++++++++++++++++++++ parser/parser.hpp | 5 + parser/query.abnf | 8 +- 3 files changed, 360 insertions(+), 4 deletions(-) diff --git a/parser/parser.cpp b/parser/parser.cpp index 3b504ee9..723332b1 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -24,6 +24,10 @@ #include #include +#include +#include "object_store.hpp" +#include "schema.hpp" + using namespace pegtl; namespace realm { @@ -291,9 +295,356 @@ Predicate parse(const std::string &query) state.predicate_stack.push_back(&out_predicate); pegtl::parse< must< pred, eof >, action >(query, source, state); + if (out_predicate.type == Predicate::Type::And && out_predicate.sub_predicates.size() == 1) { + return out_predicate.sub_predicates.back(); + } return out_predicate; } + + +// check a precondition and throw an exception if it is not met +// this should be used iff the condition being false indicates a bug in the caller +// of the function checking its preconditions +static void precondition(bool condition, const std::string message) { + if (__builtin_expect(condition, 1)) { + return; + } + throw std::runtime_error(message); +} + +// FIXME: TrueExpression and FalseExpression should be supported by core in some way +struct TrueExpression : realm::Expression { + size_t find_first(size_t start, size_t end) const override + { + if (start != end) + return start; + + return not_found; + } + void set_table() override {} + const Table* get_table() const override { return nullptr; } +}; + +struct FalseExpression : realm::Expression { + size_t find_first(size_t, size_t) const override { return not_found; } + void set_table() override {} + const Table* get_table() const override { return nullptr; } +}; + + +// add a clause for numeric constraints based on operator type +template +void add_numeric_constraint_to_query(Query& query, + PropertyType datatype, + Predicate::Operator operatorType, + A lhs, + B rhs) +{ + switch (operatorType) { + case Predicate::Operator::LessThan: + query.and_query(lhs < rhs); + break; + case Predicate::Operator::LessThanOrEqual: + query.and_query(lhs <= rhs); + break; + case Predicate::Operator::GreaterThan: + query.and_query(lhs > rhs); + break; + case Predicate::Operator::GreaterThanOrEqual: + query.and_query(lhs >= rhs); + break; + case Predicate::Operator::Equal: + query.and_query(lhs == rhs); + break; + case Predicate::Operator::NotEqual: + query.and_query(lhs != rhs); + break; + default: + throw std::runtime_error("Unsupported operator for numeric queries."); + } +} + +template +void add_bool_constraint_to_query(Query &query, Predicate::Operator operatorType, A lhs, B rhs) { + switch (operatorType) { + case Predicate::Operator::Equal: + query.and_query(lhs == rhs); + break; + case Predicate::Operator::NotEqual: + query.and_query(lhs != rhs); + break; + default: + throw std::runtime_error("Unsupported operator for numeric queries."); + } +} + +void add_string_constraint_to_query(Query &query, + Predicate::Operator op, + Columns &&column, + StringData value) { + bool case_sensitive = true; + StringData sd = value; + switch (op) { + case Predicate::Operator::BeginsWith: + query.and_query(column.begins_with(sd, case_sensitive)); + break; + case Predicate::Operator::EndsWith: + query.and_query(column.ends_with(sd, case_sensitive)); + break; + case Predicate::Operator::Contains: + query.and_query(column.contains(sd, case_sensitive)); + break; + case Predicate::Operator::Equal: + query.and_query(column.equal(sd, case_sensitive)); + break; + case Predicate::Operator::NotEqual: + query.and_query(column.not_equal(sd, case_sensitive)); + break; + default: + throw std::runtime_error("Unsupported operator for string queries."); + } +} + +void add_string_constraint_to_query(realm::Query& query, + Predicate::Operator op, + StringData value, + Columns &&column) { + bool case_sensitive = true; + StringData sd = value; + switch (op) { + case Predicate::Operator::Equal: + query.and_query(column.equal(sd, case_sensitive)); + break; + case Predicate::Operator::NotEqual: + query.and_query(column.not_equal(sd, case_sensitive)); + break; + default: + throw std::runtime_error("Substring comparison not supported for keypath substrings."); + } +} + +template +struct ColumnOfTypeHelper { + static Columns convert(TableGetter&& table, unsigned int idx) + { + return table()->template column(idx); + } +}; + +template +struct ColumnOfTypeHelper { + static Columns convert(TableGetter&& table, unsigned int idx) + { + return table()->template column(idx); + } +}; + +template +struct ValueOfTypeHelper; + +template +struct ValueOfTypeHelper { + static Int convert(TableGetter&&, const std::string & value) + { + assert(0); + } +}; + +template +struct ValueOfTypeHelper { + static bool convert(TableGetter&&, const std::string & value) + { + assert(0); + } +}; + +template +struct ValueOfTypeHelper { + static Double convert(TableGetter&&, const std::string & value) + { + return std::stod(value); + } +}; + +template +struct ValueOfTypeHelper { + static Float convert(TableGetter&&, const std::string & value) + { + return std::stof(value); + } +}; + +template +struct ValueOfTypeHelper { + static Int convert(TableGetter&&, const std::string & value) + { + return std::stoll(value); + } +}; + +template +struct ValueOfTypeHelper { + static std::string convert(TableGetter&&, const std::string & value) + { + return value; + } +}; + +template +auto value_of_type_for_query(TableGetter&& tables, Value&& value) +{ + const bool isColumnIndex = std::is_same::type>::value; + using helper = std::conditional_t, + ValueOfTypeHelper>; + return helper::convert(std::forward(tables), std::forward(value)); +} + +std::vector &split(const std::string &s, char delim, std::vector &elems) { + std::stringstream ss(s); + std::string item; + while (std::getline(ss, item, delim)) { + elems.push_back(item); + } + return elems; +} + +std::vector split(const std::string &s, char delim) { + std::vector elems; + split(s, delim, elems); + return elems; +} + +Property *get_property_from_key_path(Schema &schema, ObjectSchema &desc, const std::string &key_path, std::vector &indexes) +{ + Property *prop = nullptr; + + auto paths = split(key_path, '.'); + for (size_t index = 0; index < paths.size(); index++) { + prop = desc.property_for_name(paths[index]); + precondition(prop != nullptr, "No property '" + paths[index] + "' on object of type '" + desc.name + "'"); + precondition(index == paths.size() - 1 || prop->type == PropertyTypeObject || prop->type == PropertyTypeArray, + (std::string)"Property '" + paths[index] + "' is not a link in object of type '" + desc.name + "'"); + + indexes.push_back(prop->table_column); + desc = *schema.find(prop->object_type); + } + return prop; +} + +template + void do_add_comparison_to_query(Query &query, Schema &schema, ObjectSchema &object_schema, Property *prop, + Predicate::Operator op, const std::vector& indexes, T... values) +{ + auto table = [&] { + TableRef& tbl = query.get_table(); + for (size_t col : indexes) { + tbl->link(col); // mutates m_link_chain on table + } + return tbl.get(); + }; + + auto type = prop->type; + switch (type) { + case PropertyTypeBool: + add_bool_constraint_to_query(query, op, value_of_type_for_query(table, values)...); + break; + case PropertyTypeDate: + add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + break; + case PropertyTypeDouble: + add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + break; + case PropertyTypeFloat: + add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + break; + case PropertyTypeInt: + add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + break; + case PropertyTypeString: + case PropertyTypeData: + add_string_constraint_to_query(query, op, value_of_type_for_query(table, values)...); + break; + default: { + throw std::runtime_error((std::string)"Object type " + string_for_property_type(type) + " not supported"); + } + } +} + +void add_comparison_to_query(Query &query, Predicate &pred, Schema &schema, ObjectSchema &object_schema) +{ + std::vector indexes; + auto t0 = pred.sub_expressions[0].type, t1 = pred.sub_expressions[1].type; + if (t0 == Expression::Type::KeyPath && t1 != Expression::Type::KeyPath) { + Property *prop = get_property_from_key_path(schema, object_schema, pred.sub_expressions[0].s, indexes); + do_add_comparison_to_query(query, schema, object_schema, prop, pred.op, indexes, prop->table_column, pred.sub_expressions[1].s); + } + else if (t0 != Expression::Type::KeyPath && t1 == Expression::Type::KeyPath) { + Property *prop = get_property_from_key_path(schema, object_schema, pred.sub_expressions[1].s, indexes); + do_add_comparison_to_query(query, schema, object_schema, prop, pred.op, indexes, pred.sub_expressions[0].s, prop->table_column); + } + else { + throw std::runtime_error("Predicate expressions must compare a keypath and another keypath or a constant value"); + } +} + +void update_query_with_predicate(Query &query, Predicate &pred, Schema &schema, ObjectSchema &object_schema) +{ + if (pred.negate) { + query.Not(); + } + + switch (pred.type) { + case Predicate::Type::And: + query.group(); + for (auto &sub : pred.sub_predicates) { + update_query_with_predicate(query, sub, schema, object_schema); + } + if (!pred.sub_predicates.size()) { + query.and_query(new TrueExpression); + } + query.end_group(); + break; + + case Predicate::Type::Or: + query.group(); + for (auto &sub : pred.sub_predicates) { + query.Or(); + update_query_with_predicate(query, sub, schema, object_schema); + } + if (!pred.sub_predicates.size()) { + query.and_query(new FalseExpression); + } + query.end_group(); + break; + + case Predicate::Type::Comparison: { + add_comparison_to_query(query, pred, schema, object_schema); + break; + } + case Predicate::Type::True: + query.and_query(new TrueExpression); + break; + + case Predicate::Type::False: + query.and_query(new FalseExpression); + break; + + default: + throw std::runtime_error("Invalid predicate type"); + break; + } +} + +void apply_predicate(Query &query, Predicate &predicate, Schema &schema, std::string objectType) { + update_query_with_predicate(query, predicate, schema, *schema.find(objectType)); + + // Test the constructed query in core + std::string validateMessage = query.validate(); + precondition(validateMessage.empty(), validateMessage.c_str()); +} + }} diff --git a/parser/parser.hpp b/parser/parser.hpp index 149f12ff..99dda99d 100644 --- a/parser/parser.hpp +++ b/parser/parser.hpp @@ -23,6 +23,9 @@ #include namespace realm { + class Query; + class Schema; + namespace parser { struct Expression { @@ -74,6 +77,8 @@ namespace realm { }; Predicate parse(const std::string &query); + + void apply_predicate(Query &query, Predicate &predicate, Schema &schema, std::string objectType); } } diff --git a/parser/query.abnf b/parser/query.abnf index d70416d3..d315fd35 100644 --- a/parser/query.abnf +++ b/parser/query.abnf @@ -17,7 +17,7 @@ string = dq-string / sq-string sq-string = "'" *(%x20-ffffffff) "'" dq-string = DQUOTE *("\\" / %x20-21 / %x23-ffffffff) DQUOTE -num = float-num / int-num / hex-num -float-num = ["-"] (*DIGIT "." 1*DIGIT / "." 1*DIGIT) -int-num = ["-"] 1*DIGIT -hex-num = ["-"] ["0"] "x" 1*HEXDIG +num = ["-"] (float-num / int-num / hex-num) +float-num = (*DIGIT "." 1*DIGIT / "." 1*DIGIT) +int-num = 1*DIGIT +hex-num = ("0x" / "0X") 1*HEXDIG From d654b7d52cb6025d805f4de86e0230cfc35dbbec Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 9 Nov 2015 22:08:06 -0800 Subject: [PATCH 010/207] support for truepredicate/falsepredicate, single quote strings - all tests now pass --- parser/parser.cpp | 77 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 17 deletions(-) diff --git a/parser/parser.cpp b/parser/parser.cpp index 723332b1..770cab77 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -35,16 +35,16 @@ namespace parser { // strings struct unicode : list< seq< one< 'u' >, rep< 4, must< xdigit > > >, one< '\\' > > {}; -struct escaped_char : one< '"', '\\', '/', 'b', 'f', 'n', 'r', 't' > {}; +struct escaped_char : one< '"', '\'', '\\', '/', 'b', 'f', 'n', 'r', 't' > {}; struct escaped : sor< escaped_char, unicode > {}; struct unescaped : utf8::range< 0x20, 0x10FFFF > {}; struct char_ : if_then_else< one< '\\' >, must< escaped >, unescaped > {}; -struct string_content : until< at< one< '"' > >, must< char_ > > {}; -struct string : seq< one< '"' >, must< string_content >, any > -{ - using content = string_content; -}; +struct dq_string_content : until< at< one< '"' > >, must< char_ > > {}; +struct dq_string : seq< one< '"' >, must< dq_string_content >, any > {}; + +struct sq_string_content : until< at< one< '\'' > >, must< char_ > > {}; +struct sq_string : seq< one< '\'' >, must< sq_string_content >, any > {}; // numbers struct minus : opt< one< '-' > > {}; @@ -63,17 +63,17 @@ struct number : seq< minus, sor< float_num, hex_num, int_num > > {}; struct key_path : list< seq< sor< alpha, one< '_' > >, star< sor< alnum, one< '_', '-' > > > >, one< '.' > > {}; // expressions and operators -struct expr : sor< string, key_path, number > {}; +struct expr : sor< dq_string, sq_string, key_path, number > {}; struct eq : sor< two< '=' >, one< '=' > > {}; struct noteq : pegtl::string< '!', '=' > {}; struct lteq : pegtl::string< '<', '=' > {}; struct lt : one< '<' > {}; struct gteq : pegtl::string< '>', '=' > {}; -struct gt : one< '<' > {}; -struct begins : istring< 'b', 'e', 'g', 'i', 'n', 's', 'w', 'i', 't', 'h' > {}; -struct ends : istring< 'e', 'n', 'd', 's', 'w', 'i', 't', 'h' > {}; -struct contains : istring< 'c', 'o', 'n', 't', 'a', 'i', 'n', 's' > {}; +struct gt : one< '>' > {}; +struct begins : istring< 'b','e','g','i','n','s','w','i','t','h' > {}; +struct ends : istring< 'e','n','d','s','w','i','t','h' > {}; +struct contains : istring< 'c','o','n','t','a','i','n','s' > {}; struct oper : sor< eq, noteq, lteq, lt, gteq, gt, begins, ends, contains > {}; // predicates @@ -81,9 +81,11 @@ struct comparison_pred : seq< expr, pad< oper, blank >, expr > {}; struct pred; struct group_pred : if_must< one< '(' >, pad< pred, blank >, one< ')' > > {}; +struct true_pred : sor< istring<'t','r','u','e','p','r','e','d','i','c','a','t','e'>, istring<'t','r','u','e'> > {}; +struct false_pred : sor< istring<'f','a','l','s','e','p','r','e','d','i','c','a','t','e'>, istring<'f','a','l','s','e'> > {}; struct not_pre : sor< seq< one< '!' >, star< blank > >, seq< istring< 'N', 'O', 'T' >, plus< blank > > > {}; -struct atom_pred : seq< opt< not_pre >, pad< sor< group_pred, comparison_pred >, blank > > {}; +struct atom_pred : seq< opt< not_pre >, pad< sor< group_pred, true_pred, false_pred, comparison_pred >, blank > > {}; struct and_op : sor< two< '&' >, istring< 'A', 'N', 'D' > > {}; struct or_op : sor< two< '|' >, istring< 'O', 'R' > > {}; @@ -186,7 +188,21 @@ template<> struct action< or_ext > } }; -template<> struct action< string > +template<> struct action< dq_string_content > +{ + static void apply( const input & in, ParserState & state ) + { + std::cout << in.string() << std::endl; + + Expression exp; + exp.type = Expression::Type::String; + exp.s = in.string(); + + state.addExpression(exp); + } +}; + +template<> struct action< sq_string_content > { static void apply( const input & in, ParserState & state ) { @@ -228,6 +244,28 @@ template<> struct action< number > } }; +template<> struct action< true_pred > +{ + static void apply( const input & in, ParserState & state ) + { + std::cout << in.string() << std::endl; + Predicate pred; + pred.type = Predicate::Type::True; + state.current()->sub_predicates.push_back(std::move(pred)); + } +}; + +template<> struct action< false_pred > +{ + static void apply( const input & in, ParserState & state ) + { + std::cout << in.string() << std::endl; + Predicate pred; + pred.type = Predicate::Type::False; + state.current()->sub_predicates.push_back(std::move(pred)); + } +}; + #define OPERATOR_ACTION(rule, oper) \ template<> struct action< rule > { \ static void apply( const input & in, ParserState & state ) { \ @@ -522,13 +560,18 @@ Property *get_property_from_key_path(Schema &schema, ObjectSchema &desc, const s auto paths = split(key_path, '.'); for (size_t index = 0; index < paths.size(); index++) { + if (prop) { + precondition(prop->type == PropertyTypeObject || prop->type == PropertyTypeArray, + (std::string)"Property '" + paths[index] + "' is not a link in object of type '" + desc.name + "'"); + indexes.push_back(prop->table_column); + + } prop = desc.property_for_name(paths[index]); precondition(prop != nullptr, "No property '" + paths[index] + "' on object of type '" + desc.name + "'"); - precondition(index == paths.size() - 1 || prop->type == PropertyTypeObject || prop->type == PropertyTypeArray, - (std::string)"Property '" + paths[index] + "' is not a link in object of type '" + desc.name + "'"); - indexes.push_back(prop->table_column); - desc = *schema.find(prop->object_type); + if (prop->object_type.size()) { + desc = *schema.find(prop->object_type); + } } return prop; } From fad667f844fe716e7687bcd3c4fe9b4a04330a26 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 10 Nov 2015 09:09:20 -0800 Subject: [PATCH 011/207] code cleanup, bugfixes --- parser/parser.cpp | 105 ++++++++++++++++++++++------------------------ parser/parser.hpp | 44 ++++++++++--------- 2 files changed, 72 insertions(+), 77 deletions(-) diff --git a/parser/parser.cpp b/parser/parser.cpp index 770cab77..ba4fd68f 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -100,25 +100,26 @@ struct pred : seq< and_pred, star< or_ext > > {}; struct ParserState { std::vector predicate_stack; - Predicate *current() { return predicate_stack.back(); } + Predicate ¤t() { + return *predicate_stack.back(); + } bool negate_next; void addExpression(Expression exp) { - if (current()->type == Predicate::Type::Comparison) { - current()->sub_expressions.emplace_back(std::move(exp)); + if (current().type == Predicate::Type::Comparison) { + current().cmpr.expr[1] = std::move(exp); predicate_stack.pop_back(); } else { - Predicate p; - p.type = Predicate::Type::Comparison; - p.sub_expressions.emplace_back(std::move(exp)); + Predicate p(Predicate::Type::Comparison); + p.cmpr.expr[0] = std::move(exp); if (negate_next) { p.negate = true; negate_next = false; } - current()->sub_predicates.emplace_back(std::move(p)); - predicate_stack.push_back(¤t()->sub_predicates.back()); + current().cpnd.sub_predicates.emplace_back(std::move(p)); + predicate_stack.push_back(¤t().cpnd.sub_predicates.back()); } } }; @@ -134,24 +135,24 @@ template<> struct action< and_ext > std::cout << "" << in.string() << std::endl; // if we were put into an OR group we need to rearrange - auto current = state.current(); - if (current->type == Predicate::Type::Or) { - auto &sub_preds = state.current()->sub_predicates; - auto second_last = sub_preds[sub_preds.size()-2]; + auto ¤t = state.current(); + if (current.type == Predicate::Type::Or) { + auto &sub_preds = state.current().cpnd.sub_predicates; + auto &second_last = sub_preds[sub_preds.size()-2]; if (second_last.type == Predicate::Type::And) { // if we are in an OR group and second to last predicate group is // an AND group then move the last predicate inside - second_last.sub_predicates.push_back(sub_preds.back()); + second_last.cpnd.sub_predicates.push_back(std::move(sub_preds.back())); sub_preds.pop_back(); } else { // otherwise combine last two into a new AND group - Predicate pred; - pred.type = Predicate::Type::And; - pred.sub_predicates = { second_last, sub_preds.back() }; + Predicate pred(Predicate::Type::And); + pred.cpnd.sub_predicates.emplace_back(std::move(second_last)); + pred.cpnd.sub_predicates.emplace_back(std::move(sub_preds.back())); sub_preds.pop_back(); sub_preds.pop_back(); - sub_preds.push_back(pred); + sub_preds.push_back(std::move(pred)); } } } @@ -164,27 +165,27 @@ template<> struct action< or_ext > std::cout << "" << in.string() << std::endl; // if already an OR group do nothing - auto current = state.current(); - if (current->type == Predicate::Type::Or) { + auto ¤t = state.current(); + if (current.type == Predicate::Type::Or) { return; } // if only two predicates in the group, then convert to OR - auto &sub_preds = state.current()->sub_predicates; + auto &sub_preds = state.current().cpnd.sub_predicates; if (sub_preds.size()) { - current->type = Predicate::Type::Or; + current.type = Predicate::Type::Or; return; } // split the current group into to groups which are ORed together - Predicate pred1, pred2; - pred1.type = Predicate::Type::And; - pred2.type = Predicate::Type::And; - pred1.sub_predicates.insert(sub_preds.begin(), sub_preds.back()); - pred2.sub_predicates.push_back(sub_preds.back()); + Predicate pred1(Predicate::Type::And), pred2(Predicate::Type::And); + pred1.cpnd.sub_predicates.insert(sub_preds.begin(), sub_preds.back()); + pred2.cpnd.sub_predicates.push_back(std::move(sub_preds.back())); - current->type = Predicate::Type::Or; - sub_preds = { pred1, pred2 }; + current.type = Predicate::Type::Or; + sub_preds.clear(); + sub_preds.emplace_back(std::move(pred1)); + sub_preds.emplace_back(std::move(pred2)); } }; @@ -249,9 +250,8 @@ template<> struct action< true_pred > static void apply( const input & in, ParserState & state ) { std::cout << in.string() << std::endl; - Predicate pred; - pred.type = Predicate::Type::True; - state.current()->sub_predicates.push_back(std::move(pred)); + Predicate pred(Predicate::Type::True); + state.current().cpnd.sub_predicates.push_back(std::move(pred)); } }; @@ -260,9 +260,8 @@ template<> struct action< false_pred > static void apply( const input & in, ParserState & state ) { std::cout << in.string() << std::endl; - Predicate pred; - pred.type = Predicate::Type::False; - state.current()->sub_predicates.push_back(std::move(pred)); + Predicate pred(Predicate::Type::False); + state.current().cpnd.sub_predicates.push_back(std::move(pred)); } }; @@ -270,7 +269,7 @@ template<> struct action< false_pred > template<> struct action< rule > { \ static void apply( const input & in, ParserState & state ) { \ std::cout << in.string() << std::endl; \ - state.current()->op = oper; }}; + state.current().cmpr.op = oper; }}; OPERATOR_ACTION(eq, Predicate::Operator::Equal) OPERATOR_ACTION(noteq, Predicate::Operator::NotEqual) @@ -289,15 +288,14 @@ template<> struct action< one< '(' > > { std::cout << "" << std::endl; - Predicate group; - group.type = Predicate::Type::And; + Predicate group(Predicate::Type::And); if (state.negate_next) { group.negate = true; state.negate_next = false; } - state.current()->sub_predicates.emplace_back(std::move(group)); - state.predicate_stack.push_back(&state.current()->sub_predicates.back()); + state.current().cpnd.sub_predicates.emplace_back(std::move(group)); + state.predicate_stack.push_back(&state.current().cpnd.sub_predicates.back()); } }; @@ -326,21 +324,19 @@ Predicate parse(const std::string &query) analyze< pred >(); const std::string source = "user query"; - Predicate out_predicate; - out_predicate.type = Predicate::Type::And; + Predicate out_predicate(Predicate::Type::And); ParserState state; state.predicate_stack.push_back(&out_predicate); pegtl::parse< must< pred, eof >, action >(query, source, state); - if (out_predicate.type == Predicate::Type::And && out_predicate.sub_predicates.size() == 1) { - return out_predicate.sub_predicates.back(); + if (out_predicate.type == Predicate::Type::And && out_predicate.cpnd.sub_predicates.size() == 1) { + return std::move(out_predicate.cpnd.sub_predicates.back()); } - return out_predicate; + return std::move(out_predicate); } - // check a precondition and throw an exception if it is not met // this should be used iff the condition being false indicates a bug in the caller // of the function checking its preconditions @@ -618,14 +614,15 @@ template void add_comparison_to_query(Query &query, Predicate &pred, Schema &schema, ObjectSchema &object_schema) { std::vector indexes; - auto t0 = pred.sub_expressions[0].type, t1 = pred.sub_expressions[1].type; + Predicate::Comparison &cmpr = pred.cmpr; + auto t0 = cmpr.expr[0].type, t1 = cmpr.expr[1].type; if (t0 == Expression::Type::KeyPath && t1 != Expression::Type::KeyPath) { - Property *prop = get_property_from_key_path(schema, object_schema, pred.sub_expressions[0].s, indexes); - do_add_comparison_to_query(query, schema, object_schema, prop, pred.op, indexes, prop->table_column, pred.sub_expressions[1].s); + Property *prop = get_property_from_key_path(schema, object_schema, cmpr.expr[0].s, indexes); + do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, prop->table_column, cmpr.expr[1].s); } else if (t0 != Expression::Type::KeyPath && t1 == Expression::Type::KeyPath) { - Property *prop = get_property_from_key_path(schema, object_schema, pred.sub_expressions[1].s, indexes); - do_add_comparison_to_query(query, schema, object_schema, prop, pred.op, indexes, pred.sub_expressions[0].s, prop->table_column); + Property *prop = get_property_from_key_path(schema, object_schema, cmpr.expr[1].s, indexes); + do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, cmpr.expr[0].s, prop->table_column); } else { throw std::runtime_error("Predicate expressions must compare a keypath and another keypath or a constant value"); @@ -641,10 +638,10 @@ void update_query_with_predicate(Query &query, Predicate &pred, Schema &schema, switch (pred.type) { case Predicate::Type::And: query.group(); - for (auto &sub : pred.sub_predicates) { + for (auto &sub : pred.cpnd.sub_predicates) { update_query_with_predicate(query, sub, schema, object_schema); } - if (!pred.sub_predicates.size()) { + if (!pred.cpnd.sub_predicates.size()) { query.and_query(new TrueExpression); } query.end_group(); @@ -652,11 +649,11 @@ void update_query_with_predicate(Query &query, Predicate &pred, Schema &schema, case Predicate::Type::Or: query.group(); - for (auto &sub : pred.sub_predicates) { + for (auto &sub : pred.cpnd.sub_predicates) { query.Or(); update_query_with_predicate(query, sub, schema, object_schema); } - if (!pred.sub_predicates.size()) { + if (!pred.cpnd.sub_predicates.size()) { query.and_query(new FalseExpression); } query.end_group(); diff --git a/parser/parser.hpp b/parser/parser.hpp index 99dda99d..c53887f5 100644 --- a/parser/parser.hpp +++ b/parser/parser.hpp @@ -29,33 +29,21 @@ namespace realm { namespace parser { struct Expression { - enum class Type - { - Number, - String, - KeyPath, - }; - Type type; - + enum class Type { Number, String, KeyPath } type; std::string s; }; struct Predicate { - enum class Type - { - None, + enum class Type { Comparison, Or, And, True, - False, - }; - Type type = Type::None; + False + } type = Type::And; - // for comparison - enum class Operator - { + enum class Operator { None, Equal, NotEqual, @@ -65,15 +53,25 @@ namespace realm { GreaterThanOrEqual, BeginsWith, EndsWith, - Contains, + Contains }; - Operator op = Operator::None; - std::vector sub_expressions; - // for compounds - std::vector sub_predicates; + struct Comparison { + Operator op = Operator::None; + Expression expr[2]; + ~Comparison() {} + }; - bool negate; + struct Compound { + std::vector sub_predicates; + }; + + Comparison cmpr; + Compound cpnd; + + bool negate = false; + + Predicate(Type t) : type(t) {} }; Predicate parse(const std::string &query); From 39956b910ba29c390378cced379e62593b88310a Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 10 Nov 2015 11:26:27 -0800 Subject: [PATCH 012/207] remove duplicate code, add argument expression type --- parser/parser.cpp | 82 ++++++++++++----------------------------------- parser/parser.hpp | 4 ++- 2 files changed, 24 insertions(+), 62 deletions(-) diff --git a/parser/parser.cpp b/parser/parser.cpp index ba4fd68f..be632cac 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -62,8 +62,12 @@ struct number : seq< minus, sor< float_num, hex_num, int_num > > {}; // key paths struct key_path : list< seq< sor< alpha, one< '_' > >, star< sor< alnum, one< '_', '-' > > > >, one< '.' > > {}; +// argument +struct argument_index : until< at< one< '}' > >, must< digit > > {}; +struct argument : seq< one< '{' >, must< argument_index >, any > {}; + // expressions and operators -struct expr : sor< dq_string, sq_string, key_path, number > {}; +struct expr : sor< dq_string, sq_string, key_path, number, argument > {}; struct eq : sor< two< '=' >, one< '=' > > {}; struct noteq : pegtl::string< '!', '=' > {}; @@ -103,9 +107,10 @@ struct ParserState Predicate ¤t() { return *predicate_stack.back(); } - bool negate_next; - void addExpression(Expression exp) + bool negate_next = false; + + void addExpression(Expression && exp) { if (current().type == Predicate::Type::Comparison) { current().cmpr.expr[1] = std::move(exp); @@ -189,69 +194,26 @@ template<> struct action< or_ext > } }; -template<> struct action< dq_string_content > -{ - static void apply( const input & in, ParserState & state ) - { - std::cout << in.string() << std::endl; - Expression exp; - exp.type = Expression::Type::String; - exp.s = in.string(); +#define EXPRESSION_ACTION(rule, type) \ +template<> struct action< rule > { \ + static void apply( const input & in, ParserState & state ) { \ + std::cout << in.string() << std::endl; \ + state.addExpression(Expression(type, in.string())); }}; - state.addExpression(exp); - } -}; +EXPRESSION_ACTION(dq_string_content, Expression::Type::String) +EXPRESSION_ACTION(sq_string_content, Expression::Type::String) +EXPRESSION_ACTION(key_path, Expression::Type::KeyPath) +EXPRESSION_ACTION(number, Expression::Type::Number) +EXPRESSION_ACTION(argument_index, Expression::Type::Argument) -template<> struct action< sq_string_content > -{ - static void apply( const input & in, ParserState & state ) - { - std::cout << in.string() << std::endl; - - Expression exp; - exp.type = Expression::Type::String; - exp.s = in.string(); - - state.addExpression(exp); - } -}; - -template<> struct action< key_path > -{ - static void apply( const input & in, ParserState & state ) - { - std::cout << in.string() << std::endl; - - Expression exp; - exp.type = Expression::Type::KeyPath; - exp.s = in.string(); - - state.addExpression(std::move(exp)); - } -}; - -template<> struct action< number > -{ - static void apply( const input & in, ParserState & state ) - { - std::cout << in.string() << std::endl; - - Expression exp; - exp.type = Expression::Type::Number; - exp.s = in.string(); - - state.addExpression(std::move(exp)); - } -}; template<> struct action< true_pred > { static void apply( const input & in, ParserState & state ) { std::cout << in.string() << std::endl; - Predicate pred(Predicate::Type::True); - state.current().cpnd.sub_predicates.push_back(std::move(pred)); + state.current().cpnd.sub_predicates.emplace_back(Predicate::Type::True); } }; @@ -260,11 +222,11 @@ template<> struct action< false_pred > static void apply( const input & in, ParserState & state ) { std::cout << in.string() << std::endl; - Predicate pred(Predicate::Type::False); - state.current().cpnd.sub_predicates.push_back(std::move(pred)); + state.current().cpnd.sub_predicates.emplace_back(Predicate::Type::False); } }; + #define OPERATOR_ACTION(rule, oper) \ template<> struct action< rule > { \ static void apply( const input & in, ParserState & state ) { \ @@ -304,7 +266,6 @@ template<> struct action< group_pred > static void apply( const input & in, ParserState & state ) { std::cout << "" << std::endl; - state.predicate_stack.pop_back(); } }; @@ -314,7 +275,6 @@ template<> struct action< not_pre > static void apply( const input & in, ParserState & state ) { std::cout << "" << std::endl; - state.negate_next = true; } }; diff --git a/parser/parser.hpp b/parser/parser.hpp index c53887f5..117ca833 100644 --- a/parser/parser.hpp +++ b/parser/parser.hpp @@ -29,8 +29,10 @@ namespace realm { namespace parser { struct Expression { - enum class Type { Number, String, KeyPath } type; + enum class Type { Number, String, KeyPath, Argument } type; std::string s; + Expression() {} + Expression(Type t, std::string s) : type(t), s(s) {} }; struct Predicate From 195f2a21dd925fbcf797e8fe3ea5b309cc31428c Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 10 Nov 2015 12:51:21 -0800 Subject: [PATCH 013/207] move query building to a separate file --- parser/parser.cpp | 353 ------------------------------------ parser/parser.hpp | 9 +- parser/query_builder.cpp | 381 +++++++++++++++++++++++++++++++++++++++ parser/query_builder.hpp | 34 ++++ 4 files changed, 418 insertions(+), 359 deletions(-) create mode 100644 parser/query_builder.cpp create mode 100644 parser/query_builder.hpp diff --git a/parser/parser.cpp b/parser/parser.cpp index be632cac..03dbd39d 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -24,10 +24,6 @@ #include #include -#include -#include "object_store.hpp" -#include "schema.hpp" - using namespace pegtl; namespace realm { @@ -296,355 +292,6 @@ Predicate parse(const std::string &query) return std::move(out_predicate); } - -// check a precondition and throw an exception if it is not met -// this should be used iff the condition being false indicates a bug in the caller -// of the function checking its preconditions -static void precondition(bool condition, const std::string message) { - if (__builtin_expect(condition, 1)) { - return; - } - throw std::runtime_error(message); -} - -// FIXME: TrueExpression and FalseExpression should be supported by core in some way -struct TrueExpression : realm::Expression { - size_t find_first(size_t start, size_t end) const override - { - if (start != end) - return start; - - return not_found; - } - void set_table() override {} - const Table* get_table() const override { return nullptr; } -}; - -struct FalseExpression : realm::Expression { - size_t find_first(size_t, size_t) const override { return not_found; } - void set_table() override {} - const Table* get_table() const override { return nullptr; } -}; - - -// add a clause for numeric constraints based on operator type -template -void add_numeric_constraint_to_query(Query& query, - PropertyType datatype, - Predicate::Operator operatorType, - A lhs, - B rhs) -{ - switch (operatorType) { - case Predicate::Operator::LessThan: - query.and_query(lhs < rhs); - break; - case Predicate::Operator::LessThanOrEqual: - query.and_query(lhs <= rhs); - break; - case Predicate::Operator::GreaterThan: - query.and_query(lhs > rhs); - break; - case Predicate::Operator::GreaterThanOrEqual: - query.and_query(lhs >= rhs); - break; - case Predicate::Operator::Equal: - query.and_query(lhs == rhs); - break; - case Predicate::Operator::NotEqual: - query.and_query(lhs != rhs); - break; - default: - throw std::runtime_error("Unsupported operator for numeric queries."); - } -} - -template -void add_bool_constraint_to_query(Query &query, Predicate::Operator operatorType, A lhs, B rhs) { - switch (operatorType) { - case Predicate::Operator::Equal: - query.and_query(lhs == rhs); - break; - case Predicate::Operator::NotEqual: - query.and_query(lhs != rhs); - break; - default: - throw std::runtime_error("Unsupported operator for numeric queries."); - } -} - -void add_string_constraint_to_query(Query &query, - Predicate::Operator op, - Columns &&column, - StringData value) { - bool case_sensitive = true; - StringData sd = value; - switch (op) { - case Predicate::Operator::BeginsWith: - query.and_query(column.begins_with(sd, case_sensitive)); - break; - case Predicate::Operator::EndsWith: - query.and_query(column.ends_with(sd, case_sensitive)); - break; - case Predicate::Operator::Contains: - query.and_query(column.contains(sd, case_sensitive)); - break; - case Predicate::Operator::Equal: - query.and_query(column.equal(sd, case_sensitive)); - break; - case Predicate::Operator::NotEqual: - query.and_query(column.not_equal(sd, case_sensitive)); - break; - default: - throw std::runtime_error("Unsupported operator for string queries."); - } -} - -void add_string_constraint_to_query(realm::Query& query, - Predicate::Operator op, - StringData value, - Columns &&column) { - bool case_sensitive = true; - StringData sd = value; - switch (op) { - case Predicate::Operator::Equal: - query.and_query(column.equal(sd, case_sensitive)); - break; - case Predicate::Operator::NotEqual: - query.and_query(column.not_equal(sd, case_sensitive)); - break; - default: - throw std::runtime_error("Substring comparison not supported for keypath substrings."); - } -} - -template -struct ColumnOfTypeHelper { - static Columns convert(TableGetter&& table, unsigned int idx) - { - return table()->template column(idx); - } -}; - -template -struct ColumnOfTypeHelper { - static Columns convert(TableGetter&& table, unsigned int idx) - { - return table()->template column(idx); - } -}; - -template -struct ValueOfTypeHelper; - -template -struct ValueOfTypeHelper { - static Int convert(TableGetter&&, const std::string & value) - { - assert(0); - } -}; - -template -struct ValueOfTypeHelper { - static bool convert(TableGetter&&, const std::string & value) - { - assert(0); - } -}; - -template -struct ValueOfTypeHelper { - static Double convert(TableGetter&&, const std::string & value) - { - return std::stod(value); - } -}; - -template -struct ValueOfTypeHelper { - static Float convert(TableGetter&&, const std::string & value) - { - return std::stof(value); - } -}; - -template -struct ValueOfTypeHelper { - static Int convert(TableGetter&&, const std::string & value) - { - return std::stoll(value); - } -}; - -template -struct ValueOfTypeHelper { - static std::string convert(TableGetter&&, const std::string & value) - { - return value; - } -}; - -template -auto value_of_type_for_query(TableGetter&& tables, Value&& value) -{ - const bool isColumnIndex = std::is_same::type>::value; - using helper = std::conditional_t, - ValueOfTypeHelper>; - return helper::convert(std::forward(tables), std::forward(value)); -} - -std::vector &split(const std::string &s, char delim, std::vector &elems) { - std::stringstream ss(s); - std::string item; - while (std::getline(ss, item, delim)) { - elems.push_back(item); - } - return elems; -} - -std::vector split(const std::string &s, char delim) { - std::vector elems; - split(s, delim, elems); - return elems; -} - -Property *get_property_from_key_path(Schema &schema, ObjectSchema &desc, const std::string &key_path, std::vector &indexes) -{ - Property *prop = nullptr; - - auto paths = split(key_path, '.'); - for (size_t index = 0; index < paths.size(); index++) { - if (prop) { - precondition(prop->type == PropertyTypeObject || prop->type == PropertyTypeArray, - (std::string)"Property '" + paths[index] + "' is not a link in object of type '" + desc.name + "'"); - indexes.push_back(prop->table_column); - - } - prop = desc.property_for_name(paths[index]); - precondition(prop != nullptr, "No property '" + paths[index] + "' on object of type '" + desc.name + "'"); - - if (prop->object_type.size()) { - desc = *schema.find(prop->object_type); - } - } - return prop; -} - -template - void do_add_comparison_to_query(Query &query, Schema &schema, ObjectSchema &object_schema, Property *prop, - Predicate::Operator op, const std::vector& indexes, T... values) -{ - auto table = [&] { - TableRef& tbl = query.get_table(); - for (size_t col : indexes) { - tbl->link(col); // mutates m_link_chain on table - } - return tbl.get(); - }; - - auto type = prop->type; - switch (type) { - case PropertyTypeBool: - add_bool_constraint_to_query(query, op, value_of_type_for_query(table, values)...); - break; - case PropertyTypeDate: - add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); - break; - case PropertyTypeDouble: - add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); - break; - case PropertyTypeFloat: - add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); - break; - case PropertyTypeInt: - add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); - break; - case PropertyTypeString: - case PropertyTypeData: - add_string_constraint_to_query(query, op, value_of_type_for_query(table, values)...); - break; - default: { - throw std::runtime_error((std::string)"Object type " + string_for_property_type(type) + " not supported"); - } - } -} - -void add_comparison_to_query(Query &query, Predicate &pred, Schema &schema, ObjectSchema &object_schema) -{ - std::vector indexes; - Predicate::Comparison &cmpr = pred.cmpr; - auto t0 = cmpr.expr[0].type, t1 = cmpr.expr[1].type; - if (t0 == Expression::Type::KeyPath && t1 != Expression::Type::KeyPath) { - Property *prop = get_property_from_key_path(schema, object_schema, cmpr.expr[0].s, indexes); - do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, prop->table_column, cmpr.expr[1].s); - } - else if (t0 != Expression::Type::KeyPath && t1 == Expression::Type::KeyPath) { - Property *prop = get_property_from_key_path(schema, object_schema, cmpr.expr[1].s, indexes); - do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, cmpr.expr[0].s, prop->table_column); - } - else { - throw std::runtime_error("Predicate expressions must compare a keypath and another keypath or a constant value"); - } -} - -void update_query_with_predicate(Query &query, Predicate &pred, Schema &schema, ObjectSchema &object_schema) -{ - if (pred.negate) { - query.Not(); - } - - switch (pred.type) { - case Predicate::Type::And: - query.group(); - for (auto &sub : pred.cpnd.sub_predicates) { - update_query_with_predicate(query, sub, schema, object_schema); - } - if (!pred.cpnd.sub_predicates.size()) { - query.and_query(new TrueExpression); - } - query.end_group(); - break; - - case Predicate::Type::Or: - query.group(); - for (auto &sub : pred.cpnd.sub_predicates) { - query.Or(); - update_query_with_predicate(query, sub, schema, object_schema); - } - if (!pred.cpnd.sub_predicates.size()) { - query.and_query(new FalseExpression); - } - query.end_group(); - break; - - case Predicate::Type::Comparison: { - add_comparison_to_query(query, pred, schema, object_schema); - break; - } - case Predicate::Type::True: - query.and_query(new TrueExpression); - break; - - case Predicate::Type::False: - query.and_query(new FalseExpression); - break; - - default: - throw std::runtime_error("Invalid predicate type"); - break; - } -} - -void apply_predicate(Query &query, Predicate &predicate, Schema &schema, std::string objectType) { - update_query_with_predicate(query, predicate, schema, *schema.find(objectType)); - - // Test the constructed query in core - std::string validateMessage = query.validate(); - precondition(validateMessage.empty(), validateMessage.c_str()); -} - }} diff --git a/parser/parser.hpp b/parser/parser.hpp index 117ca833..568c446b 100644 --- a/parser/parser.hpp +++ b/parser/parser.hpp @@ -16,14 +16,13 @@ // //////////////////////////////////////////////////////////////////////////// -#ifndef REALM_EXTERNAL_COMMIT_HELPER_HPP -#define REALM_EXTERNAL_COMMIT_HELPER_HPP +#ifndef REALM_PARSER_HPP +#define REALM_PARSER_HPP #include #include namespace realm { - class Query; class Schema; namespace parser { @@ -77,9 +76,7 @@ namespace realm { }; Predicate parse(const std::string &query); - - void apply_predicate(Query &query, Predicate &predicate, Schema &schema, std::string objectType); } } -#endif // REALM_EXTERNAL_COMMIT_HELPER_HPP +#endif // REALM_PARSER_HPP diff --git a/parser/query_builder.cpp b/parser/query_builder.cpp new file mode 100644 index 00000000..004b821e --- /dev/null +++ b/parser/query_builder.cpp @@ -0,0 +1,381 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "query_builder.hpp" +#include "parser.hpp" + +#include +#include "object_store.hpp" +#include "schema.hpp" + +#include + +namespace realm { +namespace query_builder { +using namespace parser; + +// check a precondition and throw an exception if it is not met +// this should be used iff the condition being false indicates a bug in the caller +// of the function checking its preconditions +static void precondition(bool condition, const std::string message) { + if (__builtin_expect(condition, 1)) { + return; + } + throw std::runtime_error(message); +} + +// FIXME: TrueExpression and FalseExpression should be supported by core in some way +struct TrueExpression : realm::Expression { + size_t find_first(size_t start, size_t end) const override + { + if (start != end) + return start; + + return not_found; + } + void set_table() override {} + const Table* get_table() const override { return nullptr; } +}; + +struct FalseExpression : realm::Expression { + size_t find_first(size_t, size_t) const override { return not_found; } + void set_table() override {} + const Table* get_table() const override { return nullptr; } +}; + + +// add a clause for numeric constraints based on operator type +template +void add_numeric_constraint_to_query(Query& query, + PropertyType datatype, + Predicate::Operator operatorType, + A lhs, + B rhs) +{ + switch (operatorType) { + case Predicate::Operator::LessThan: + query.and_query(lhs < rhs); + break; + case Predicate::Operator::LessThanOrEqual: + query.and_query(lhs <= rhs); + break; + case Predicate::Operator::GreaterThan: + query.and_query(lhs > rhs); + break; + case Predicate::Operator::GreaterThanOrEqual: + query.and_query(lhs >= rhs); + break; + case Predicate::Operator::Equal: + query.and_query(lhs == rhs); + break; + case Predicate::Operator::NotEqual: + query.and_query(lhs != rhs); + break; + default: + throw std::runtime_error("Unsupported operator for numeric queries."); + } +} + +template +void add_bool_constraint_to_query(Query &query, Predicate::Operator operatorType, A lhs, B rhs) { + switch (operatorType) { + case Predicate::Operator::Equal: + query.and_query(lhs == rhs); + break; + case Predicate::Operator::NotEqual: + query.and_query(lhs != rhs); + break; + default: + throw std::runtime_error("Unsupported operator for numeric queries."); + } +} + +void add_string_constraint_to_query(Query &query, + Predicate::Operator op, + Columns &&column, + StringData value) { + bool case_sensitive = true; + StringData sd = value; + switch (op) { + case Predicate::Operator::BeginsWith: + query.and_query(column.begins_with(sd, case_sensitive)); + break; + case Predicate::Operator::EndsWith: + query.and_query(column.ends_with(sd, case_sensitive)); + break; + case Predicate::Operator::Contains: + query.and_query(column.contains(sd, case_sensitive)); + break; + case Predicate::Operator::Equal: + query.and_query(column.equal(sd, case_sensitive)); + break; + case Predicate::Operator::NotEqual: + query.and_query(column.not_equal(sd, case_sensitive)); + break; + default: + throw std::runtime_error("Unsupported operator for string queries."); + } +} + +void add_string_constraint_to_query(realm::Query& query, + Predicate::Operator op, + StringData value, + Columns &&column) { + bool case_sensitive = true; + StringData sd = value; + switch (op) { + case Predicate::Operator::Equal: + query.and_query(column.equal(sd, case_sensitive)); + break; + case Predicate::Operator::NotEqual: + query.and_query(column.not_equal(sd, case_sensitive)); + break; + default: + throw std::runtime_error("Substring comparison not supported for keypath substrings."); + } +} + +template +struct ColumnOfTypeHelper { + static Columns convert(TableGetter&& table, unsigned int idx) + { + return table()->template column(idx); + } +}; + +template +struct ColumnOfTypeHelper { + static Columns convert(TableGetter&& table, unsigned int idx) + { + return table()->template column(idx); + } +}; + +template +struct ValueOfTypeHelper; + +template +struct ValueOfTypeHelper { + static Int convert(TableGetter&&, const std::string & value) + { + assert(0); + } +}; + +template +struct ValueOfTypeHelper { + static bool convert(TableGetter&&, const std::string & value) + { + assert(0); + } +}; + +template +struct ValueOfTypeHelper { + static Double convert(TableGetter&&, const std::string & value) + { + return std::stod(value); + } +}; + +template +struct ValueOfTypeHelper { + static Float convert(TableGetter&&, const std::string & value) + { + return std::stof(value); + } +}; + +template +struct ValueOfTypeHelper { + static Int convert(TableGetter&&, const std::string & value) + { + return std::stoll(value); + } +}; + +template +struct ValueOfTypeHelper { + static std::string convert(TableGetter&&, const std::string & value) + { + return value; + } +}; + +template +auto value_of_type_for_query(TableGetter&& tables, Value&& value) +{ + const bool isColumnIndex = std::is_same::type>::value; + using helper = std::conditional_t, + ValueOfTypeHelper>; + return helper::convert(std::forward(tables), std::forward(value)); +} + +std::vector &split(const std::string &s, char delim, std::vector &elems) { + std::stringstream ss(s); + std::string item; + while (std::getline(ss, item, delim)) { + elems.push_back(item); + } + return elems; +} + +std::vector split(const std::string &s, char delim) { + std::vector elems; + split(s, delim, elems); + return elems; +} + +Property *get_property_from_key_path(Schema &schema, ObjectSchema &desc, const std::string &key_path, std::vector &indexes) +{ + Property *prop = nullptr; + + auto paths = split(key_path, '.'); + for (size_t index = 0; index < paths.size(); index++) { + if (prop) { + precondition(prop->type == PropertyTypeObject || prop->type == PropertyTypeArray, + (std::string)"Property '" + paths[index] + "' is not a link in object of type '" + desc.name + "'"); + indexes.push_back(prop->table_column); + + } + prop = desc.property_for_name(paths[index]); + precondition(prop != nullptr, "No property '" + paths[index] + "' on object of type '" + desc.name + "'"); + + if (prop->object_type.size()) { + desc = *schema.find(prop->object_type); + } + } + return prop; +} + +template +void do_add_comparison_to_query(Query &query, Schema &schema, ObjectSchema &object_schema, Property *prop, + Predicate::Operator op, const std::vector& indexes, T... values) +{ + auto table = [&] { + TableRef& tbl = query.get_table(); + for (size_t col : indexes) { + tbl->link(col); // mutates m_link_chain on table + } + return tbl.get(); + }; + + auto type = prop->type; + switch (type) { + case PropertyTypeBool: + add_bool_constraint_to_query(query, op, value_of_type_for_query(table, values)...); + break; + case PropertyTypeDate: + add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + break; + case PropertyTypeDouble: + add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + break; + case PropertyTypeFloat: + add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + break; + case PropertyTypeInt: + add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + break; + case PropertyTypeString: + case PropertyTypeData: + add_string_constraint_to_query(query, op, value_of_type_for_query(table, values)...); + break; + default: { + throw std::runtime_error((std::string)"Object type " + string_for_property_type(type) + " not supported"); + } + } +} + +void add_comparison_to_query(Query &query, Predicate &pred, Schema &schema, ObjectSchema &object_schema) +{ + std::vector indexes; + Predicate::Comparison &cmpr = pred.cmpr; + auto t0 = cmpr.expr[0].type, t1 = cmpr.expr[1].type; + if (t0 == parser::Expression::Type::KeyPath && t1 != parser::Expression::Type::KeyPath) { + Property *prop = get_property_from_key_path(schema, object_schema, cmpr.expr[0].s, indexes); + do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, prop->table_column, cmpr.expr[1].s); + } + else if (t0 != parser::Expression::Type::KeyPath && t1 == parser::Expression::Type::KeyPath) { + Property *prop = get_property_from_key_path(schema, object_schema, cmpr.expr[1].s, indexes); + do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, cmpr.expr[0].s, prop->table_column); + } + else { + throw std::runtime_error("Predicate expressions must compare a keypath and another keypath or a constant value"); + } +} + +void update_query_with_predicate(Query &query, Predicate &pred, Schema &schema, ObjectSchema &object_schema) +{ + if (pred.negate) { + query.Not(); + } + + switch (pred.type) { + case Predicate::Type::And: + query.group(); + for (auto &sub : pred.cpnd.sub_predicates) { + update_query_with_predicate(query, sub, schema, object_schema); + } + if (!pred.cpnd.sub_predicates.size()) { + query.and_query(new TrueExpression); + } + query.end_group(); + break; + + case Predicate::Type::Or: + query.group(); + for (auto &sub : pred.cpnd.sub_predicates) { + query.Or(); + update_query_with_predicate(query, sub, schema, object_schema); + } + if (!pred.cpnd.sub_predicates.size()) { + query.and_query(new FalseExpression); + } + query.end_group(); + break; + + case Predicate::Type::Comparison: { + add_comparison_to_query(query, pred, schema, object_schema); + break; + } + case Predicate::Type::True: + query.and_query(new TrueExpression); + break; + + case Predicate::Type::False: + query.and_query(new FalseExpression); + break; + + default: + throw std::runtime_error("Invalid predicate type"); + break; + } +} + +void apply_predicate(Query &query, Predicate &predicate, Schema &schema, std::string objectType) +{ + update_query_with_predicate(query, predicate, schema, *schema.find(objectType)); + + // Test the constructed query in core + std::string validateMessage = query.validate(); + precondition(validateMessage.empty(), validateMessage.c_str()); +} + +}} diff --git a/parser/query_builder.hpp b/parser/query_builder.hpp new file mode 100644 index 00000000..8a408ec8 --- /dev/null +++ b/parser/query_builder.hpp @@ -0,0 +1,34 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_QUERY_BUILDER_HPP +#define REALM_QUERY_BUILDER_HPP + +#include +#include "parser.hpp" + +namespace realm { + class Query; + class Schema; + + namespace query_builder { + void apply_predicate(Query &query, parser::Predicate &predicate, Schema &schema, std::string objectType); + } +} + +#endif // REALM_QUERY_BUILDER_HPP From e078b22c9aa8292b0c112752a54521a660d71d6f Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 10 Nov 2015 13:17:27 -0800 Subject: [PATCH 014/207] add required padding around string operators, use pegtl_istring_t --- parser/parser.cpp | 30 ++++++++++++++++++------------ parser/query.abnf | 23 ----------------------- 2 files changed, 18 insertions(+), 35 deletions(-) delete mode 100644 parser/query.abnf diff --git a/parser/parser.cpp b/parser/parser.cpp index 03dbd39d..699739db 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -23,6 +23,7 @@ #include #include #include +#include using namespace pegtl; @@ -71,27 +72,32 @@ struct lteq : pegtl::string< '<', '=' > {}; struct lt : one< '<' > {}; struct gteq : pegtl::string< '>', '=' > {}; struct gt : one< '>' > {}; -struct begins : istring< 'b','e','g','i','n','s','w','i','t','h' > {}; -struct ends : istring< 'e','n','d','s','w','i','t','h' > {}; -struct contains : istring< 'c','o','n','t','a','i','n','s' > {}; -struct oper : sor< eq, noteq, lteq, lt, gteq, gt, begins, ends, contains > {}; +struct contains : pegtl_istring_t("contains") {}; +struct begins : pegtl_istring_t("beginswith") {}; +struct ends : pegtl_istring_t("endswith") {}; + +template +struct pad_plus : seq< plus< B >, A, plus< B > > {}; + +struct padded_oper : pad_plus< sor< contains, begins, ends >, blank > {}; +struct symbolic_oper : pad< sor< eq, noteq, lteq, lt, gteq, gt >, blank > {}; // predicates -struct comparison_pred : seq< expr, pad< oper, blank >, expr > {}; +struct comparison_pred : seq< expr, sor< padded_oper, symbolic_oper >, expr > {}; struct pred; struct group_pred : if_must< one< '(' >, pad< pred, blank >, one< ')' > > {}; -struct true_pred : sor< istring<'t','r','u','e','p','r','e','d','i','c','a','t','e'>, istring<'t','r','u','e'> > {}; -struct false_pred : sor< istring<'f','a','l','s','e','p','r','e','d','i','c','a','t','e'>, istring<'f','a','l','s','e'> > {}; +struct true_pred : sor< pegtl_istring_t("truepredicate"), pegtl_istring_t("true") > {}; +struct false_pred : sor< pegtl_istring_t("falsepredicate"), pegtl_istring_t("false") > {}; -struct not_pre : sor< seq< one< '!' >, star< blank > >, seq< istring< 'N', 'O', 'T' >, plus< blank > > > {}; +struct not_pre : sor< seq< one< '!' >, star< blank > >, seq< pegtl_istring_t("not"), plus< blank > > > {}; struct atom_pred : seq< opt< not_pre >, pad< sor< group_pred, true_pred, false_pred, comparison_pred >, blank > > {}; -struct and_op : sor< two< '&' >, istring< 'A', 'N', 'D' > > {}; -struct or_op : sor< two< '|' >, istring< 'O', 'R' > > {}; +struct and_op : sor< pad< two< '&' >, blank >, pad_plus< pegtl_istring_t("and"), blank > > {}; +struct or_op : sor< pad< two< '|' >, blank> , pad_plus< pegtl_istring_t("or"), blank > > {}; -struct or_ext : seq< pad< or_op, blank >, pred > {}; -struct and_ext : seq< pad< and_op, blank >, pred > {}; +struct or_ext : if_must< or_op, pred > {}; +struct and_ext : if_must< and_op, pred > {}; struct and_pred : seq< atom_pred, star< and_ext > > {}; struct pred : seq< and_pred, star< or_ext > > {}; diff --git a/parser/query.abnf b/parser/query.abnf deleted file mode 100644 index d315fd35..00000000 --- a/parser/query.abnf +++ /dev/null @@ -1,23 +0,0 @@ - - -pred = expr 1*WSP oper 1*WSP expr -;pred =/ "(" *WSP pred *WSP ")" -;pred =/ ("NOT" / "!") 1*WSP pred -;pred =/ pred 1*WSP ("OR" / "||") !*WSP pred -;pred =/ pred 1*WSP ("AND" / "&&") 1*WSP pred - -oper = "=" / "==" / "!=" / "<" / "<=" / ">" / ">=" - -expr = string / num / key-path - -key-path = key *("." key) -key = 1*(ALPHA / "_") - -string = dq-string / sq-string -sq-string = "'" *(%x20-ffffffff) "'" -dq-string = DQUOTE *("\\" / %x20-21 / %x23-ffffffff) DQUOTE - -num = ["-"] (float-num / int-num / hex-num) -float-num = (*DIGIT "." 1*DIGIT / "." 1*DIGIT) -int-num = 1*DIGIT -hex-num = ("0x" / "0X") 1*HEXDIG From b97728ba3350f00341e8e6c252716a377b3c56fd Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 10 Nov 2015 14:24:30 -0800 Subject: [PATCH 015/207] support for querying boolean properties --- parser/parser.cpp | 14 ++++++++------ parser/parser.hpp | 2 +- parser/query_builder.cpp | 34 +++++++++++++++++----------------- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/parser/parser.cpp b/parser/parser.cpp index 699739db..2479b287 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -23,7 +23,6 @@ #include #include #include -#include using namespace pegtl; @@ -56,6 +55,9 @@ struct int_num : plus< digit > {}; struct number : seq< minus, sor< float_num, hex_num, int_num > > {}; +struct true_value : pegtl_istring_t("true") {}; +struct false_value : pegtl_istring_t("false") {}; + // key paths struct key_path : list< seq< sor< alpha, one< '_' > >, star< sor< alnum, one< '_', '-' > > > >, one< '.' > > {}; @@ -64,7 +66,7 @@ struct argument_index : until< at< one< '}' > >, must< digit > > {}; struct argument : seq< one< '{' >, must< argument_index >, any > {}; // expressions and operators -struct expr : sor< dq_string, sq_string, key_path, number, argument > {}; +struct expr : sor< dq_string, sq_string, number, argument, true_value, false_value, key_path > {}; struct eq : sor< two< '=' >, one< '=' > > {}; struct noteq : pegtl::string< '!', '=' > {}; @@ -87,8 +89,8 @@ struct comparison_pred : seq< expr, sor< padded_oper, symbolic_oper >, expr > {} struct pred; struct group_pred : if_must< one< '(' >, pad< pred, blank >, one< ')' > > {}; -struct true_pred : sor< pegtl_istring_t("truepredicate"), pegtl_istring_t("true") > {}; -struct false_pred : sor< pegtl_istring_t("falsepredicate"), pegtl_istring_t("false") > {}; +struct true_pred : pegtl_istring_t("truepredicate") {}; +struct false_pred : pegtl_istring_t("falsepredicate") {}; struct not_pre : sor< seq< one< '!' >, star< blank > >, seq< pegtl_istring_t("not"), plus< blank > > > {}; struct atom_pred : seq< opt< not_pre >, pad< sor< group_pred, true_pred, false_pred, comparison_pred >, blank > > {}; @@ -207,6 +209,8 @@ EXPRESSION_ACTION(dq_string_content, Expression::Type::String) EXPRESSION_ACTION(sq_string_content, Expression::Type::String) EXPRESSION_ACTION(key_path, Expression::Type::KeyPath) EXPRESSION_ACTION(number, Expression::Type::Number) +EXPRESSION_ACTION(true_value, Expression::Type::True) +EXPRESSION_ACTION(false_value, Expression::Type::False) EXPRESSION_ACTION(argument_index, Expression::Type::Argument) @@ -228,7 +232,6 @@ template<> struct action< false_pred > } }; - #define OPERATOR_ACTION(rule, oper) \ template<> struct action< rule > { \ static void apply( const input & in, ParserState & state ) { \ @@ -245,7 +248,6 @@ OPERATOR_ACTION(begins, Predicate::Operator::BeginsWith) OPERATOR_ACTION(ends, Predicate::Operator::EndsWith) OPERATOR_ACTION(contains, Predicate::Operator::Contains) - template<> struct action< one< '(' > > { static void apply( const input & in, ParserState & state ) diff --git a/parser/parser.hpp b/parser/parser.hpp index 568c446b..4792285a 100644 --- a/parser/parser.hpp +++ b/parser/parser.hpp @@ -28,7 +28,7 @@ namespace realm { namespace parser { struct Expression { - enum class Type { Number, String, KeyPath, Argument } type; + enum class Type { Number, String, KeyPath, Argument, True, False } type; std::string s; Expression() {} Expression(Type t, std::string s) : type(t), s(s) {} diff --git a/parser/query_builder.cpp b/parser/query_builder.cpp index 004b821e..8f4aeeee 100644 --- a/parser/query_builder.cpp +++ b/parser/query_builder.cpp @@ -152,7 +152,7 @@ void add_string_constraint_to_query(realm::Query& query, template struct ColumnOfTypeHelper { - static Columns convert(TableGetter&& table, unsigned int idx) + static Columns convert(TableGetter&& table, size_t idx) { return table()->template column(idx); } @@ -160,7 +160,7 @@ struct ColumnOfTypeHelper { template struct ColumnOfTypeHelper { - static Columns convert(TableGetter&& table, unsigned int idx) + static Columns convert(TableGetter&& table, size_t idx) { return table()->template column(idx); } @@ -171,7 +171,7 @@ struct ValueOfTypeHelper; template struct ValueOfTypeHelper { - static Int convert(TableGetter&&, const std::string & value) + static Int convert(TableGetter&&, const parser::Expression & value) { assert(0); } @@ -179,41 +179,41 @@ struct ValueOfTypeHelper { template struct ValueOfTypeHelper { - static bool convert(TableGetter&&, const std::string & value) + static bool convert(TableGetter&&, const parser::Expression & value) { - assert(0); + return value.type == parser::Expression::Type::True; } }; template struct ValueOfTypeHelper { - static Double convert(TableGetter&&, const std::string & value) + static Double convert(TableGetter&&, const parser::Expression & value) { - return std::stod(value); + return std::stod(value.s); } }; template struct ValueOfTypeHelper { - static Float convert(TableGetter&&, const std::string & value) + static Float convert(TableGetter&&, const parser::Expression & value) { - return std::stof(value); + return std::stof(value.s); } }; template struct ValueOfTypeHelper { - static Int convert(TableGetter&&, const std::string & value) + static Int convert(TableGetter&&, const parser::Expression & value) { - return std::stoll(value); + return std::stoll(value.s); } }; template struct ValueOfTypeHelper { - static std::string convert(TableGetter&&, const std::string & value) + static std::string convert(TableGetter&&, const parser::Expression & value) { - return value; + return value.s; } }; @@ -222,8 +222,8 @@ auto value_of_type_for_query(TableGetter&& tables, Value&& value) { const bool isColumnIndex = std::is_same::type>::value; using helper = std::conditional_t, - ValueOfTypeHelper>; + ColumnOfTypeHelper, + ValueOfTypeHelper>; return helper::convert(std::forward(tables), std::forward(value)); } @@ -310,11 +310,11 @@ void add_comparison_to_query(Query &query, Predicate &pred, Schema &schema, Obje auto t0 = cmpr.expr[0].type, t1 = cmpr.expr[1].type; if (t0 == parser::Expression::Type::KeyPath && t1 != parser::Expression::Type::KeyPath) { Property *prop = get_property_from_key_path(schema, object_schema, cmpr.expr[0].s, indexes); - do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, prop->table_column, cmpr.expr[1].s); + do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, prop->table_column, cmpr.expr[1]); } else if (t0 != parser::Expression::Type::KeyPath && t1 == parser::Expression::Type::KeyPath) { Property *prop = get_property_from_key_path(schema, object_schema, cmpr.expr[1].s, indexes); - do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, cmpr.expr[0].s, prop->table_column); + do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, cmpr.expr[0], prop->table_column); } else { throw std::runtime_error("Predicate expressions must compare a keypath and another keypath or a constant value"); From 260ca1884526ed5bdee19f928f1b6be9f696e8ab Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 10 Nov 2015 15:58:04 -0800 Subject: [PATCH 016/207] support query format strings --- object_accessor.hpp | 3 + object_store.hpp | 2 +- parser/query_builder.cpp | 214 +++++++++++++++++++++------------------ parser/query_builder.hpp | 46 ++++++++- 4 files changed, 167 insertions(+), 98 deletions(-) diff --git a/object_accessor.hpp b/object_accessor.hpp index 5cc7a358..dfae7c16 100644 --- a/object_accessor.hpp +++ b/object_accessor.hpp @@ -76,6 +76,9 @@ namespace realm { static size_t to_object_index(ContextType ctx, SharedRealm realm, ValueType &val, const std::string &type, bool try_update); static ValueType from_object(ContextType ctx, Object); + // object index for an existing object + static size_t to_existing_object_index(ContextType ctx, ValueType &val); + // list value acessors static size_t list_size(ContextType ctx, ValueType &val); static ValueType list_value_at_index(ContextType ctx, ValueType &val, size_t index); diff --git a/object_store.hpp b/object_store.hpp index 60671f55..e038604c 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -19,6 +19,7 @@ #ifndef REALM_OBJECT_STORE_HPP #define REALM_OBJECT_STORE_HPP +#include "schema.hpp" #include "object_schema.hpp" #include "property.hpp" @@ -29,7 +30,6 @@ namespace realm { class ObjectSchemaValidationException; - class Schema; class ObjectStore { public: diff --git a/parser/query_builder.cpp b/parser/query_builder.cpp index 8f4aeeee..b924eaa3 100644 --- a/parser/query_builder.cpp +++ b/parser/query_builder.cpp @@ -62,7 +62,6 @@ struct FalseExpression : realm::Expression { // add a clause for numeric constraints based on operator type template void add_numeric_constraint_to_query(Query& query, - PropertyType datatype, Predicate::Operator operatorType, A lhs, B rhs) @@ -150,152 +149,176 @@ void add_string_constraint_to_query(realm::Query& query, } } -template -struct ColumnOfTypeHelper { - static Columns convert(TableGetter&& table, size_t idx) + +using KeyPath = std::vector; +KeyPath key_path_from_string(const std::string &s) { + std::stringstream ss(s); + std::string item; + KeyPath key_path; + while (std::getline(ss, item, '.')) { + key_path.push_back(item); + } + return key_path; +} + +struct PropertyExpression +{ + Property *prop = nullptr; + std::vector indexes; + std::function table_getter; + + PropertyExpression(Query &query, Schema &schema, ObjectSchema &desc, const std::string &key_path_string) { - return table()->template column(idx); + KeyPath key_path = key_path_from_string(key_path_string); + for (size_t index = 0; index < key_path.size(); index++) { + if (prop) { + precondition(prop->type == PropertyTypeObject || prop->type == PropertyTypeArray, + (std::string)"Property '" + key_path[index] + "' is not a link in object of type '" + desc.name + "'"); + indexes.push_back(prop->table_column); + + } + prop = desc.property_for_name(key_path[index]); + precondition(prop != nullptr, "No property '" + key_path[index] + "' on object of type '" + desc.name + "'"); + + if (prop->object_type.size()) { + desc = *schema.find(prop->object_type); + } + } + + table_getter = [&] { + TableRef& tbl = query.get_table(); + for (size_t col : indexes) { + tbl->link(col); // mutates m_link_chain on table + } + return tbl.get(); + }; + } +}; + +template +struct ColumnGetter { + static Columns convert(TableGetter&& table, const PropertyExpression & expr, Arguments &args) + { + return table()->template column(expr.prop->table_column); } }; template -struct ColumnOfTypeHelper { - static Columns convert(TableGetter&& table, size_t idx) +struct ColumnGetter { + static Columns convert(TableGetter&& table, const PropertyExpression & expr, Arguments &args) { - return table()->template column(idx); + return table()->template column(expr.prop->table_column); } }; template -struct ValueOfTypeHelper; +struct ValueGetter; template -struct ValueOfTypeHelper { - static Int convert(TableGetter&&, const parser::Expression & value) +struct ValueGetter { + static Int convert(TableGetter&&, const parser::Expression & value, Arguments &args) { - assert(0); + if (value.type != parser::Expression::Type::Argument) { + throw std::runtime_error("You must pass in a date argument to compare"); + } + DateTime dt = args.datetime_for_argument(std::stoi(value.s)); + return dt.get_datetime(); } }; template -struct ValueOfTypeHelper { - static bool convert(TableGetter&&, const parser::Expression & value) +struct ValueGetter { + static bool convert(TableGetter&&, const parser::Expression & value, Arguments &args) { + if (value.type == parser::Expression::Type::Argument) { + return args.bool_for_argument(std::stoi(value.s)); + } return value.type == parser::Expression::Type::True; } }; template -struct ValueOfTypeHelper { - static Double convert(TableGetter&&, const parser::Expression & value) +struct ValueGetter { + static Double convert(TableGetter&&, const parser::Expression & value, Arguments &args) { + if (value.type == parser::Expression::Type::Argument) { + return args.double_for_argument(std::stoi(value.s)); + } return std::stod(value.s); } }; template -struct ValueOfTypeHelper { - static Float convert(TableGetter&&, const parser::Expression & value) +struct ValueGetter { + static Float convert(TableGetter&&, const parser::Expression & value, Arguments &args) { + if (value.type == parser::Expression::Type::Argument) { + return args.float_for_argument(std::stoi(value.s)); + } return std::stof(value.s); } }; template -struct ValueOfTypeHelper { - static Int convert(TableGetter&&, const parser::Expression & value) +struct ValueGetter { + static Int convert(TableGetter&&, const parser::Expression & value, Arguments &args) { + if (value.type == parser::Expression::Type::Argument) { + return args.long_for_argument(std::stoi(value.s)); + } return std::stoll(value.s); } }; template -struct ValueOfTypeHelper { - static std::string convert(TableGetter&&, const parser::Expression & value) +struct ValueGetter { + static std::string convert(TableGetter&&, const parser::Expression & value, Arguments &args) { + if (value.type == parser::Expression::Type::Argument) { + return args.string_for_argument(std::stoi(value.s)); + } return value.s; } }; -template -auto value_of_type_for_query(TableGetter&& tables, Value&& value) +template +auto value_of_type_for_query(TableGetter&& tables, Value&& value, Arguments &args) { - const bool isColumnIndex = std::is_same::type>::value; - using helper = std::conditional_t, - ValueOfTypeHelper>; - return helper::convert(std::forward(tables), std::forward(value)); + const bool isColumn = std::is_same::type>::value; + using helper = std::conditional_t, ValueGetter>; + return helper::convert(std::forward(tables), std::forward(value), args); } -std::vector &split(const std::string &s, char delim, std::vector &elems) { - std::stringstream ss(s); - std::string item; - while (std::getline(ss, item, delim)) { - elems.push_back(item); - } - return elems; -} - -std::vector split(const std::string &s, char delim) { - std::vector elems; - split(s, delim, elems); - return elems; -} - -Property *get_property_from_key_path(Schema &schema, ObjectSchema &desc, const std::string &key_path, std::vector &indexes) +template +void do_add_comparison_to_query(Query &query, Schema &schema, ObjectSchema &object_schema, Predicate::Operator op, + PropertyExpression &expr, A &lhs, B &rhs, Arguments &args) { - Property *prop = nullptr; - - auto paths = split(key_path, '.'); - for (size_t index = 0; index < paths.size(); index++) { - if (prop) { - precondition(prop->type == PropertyTypeObject || prop->type == PropertyTypeArray, - (std::string)"Property '" + paths[index] + "' is not a link in object of type '" + desc.name + "'"); - indexes.push_back(prop->table_column); - - } - prop = desc.property_for_name(paths[index]); - precondition(prop != nullptr, "No property '" + paths[index] + "' on object of type '" + desc.name + "'"); - - if (prop->object_type.size()) { - desc = *schema.find(prop->object_type); - } - } - return prop; -} - -template -void do_add_comparison_to_query(Query &query, Schema &schema, ObjectSchema &object_schema, Property *prop, - Predicate::Operator op, const std::vector& indexes, T... values) -{ - auto table = [&] { - TableRef& tbl = query.get_table(); - for (size_t col : indexes) { - tbl->link(col); // mutates m_link_chain on table - } - return tbl.get(); - }; - - auto type = prop->type; + auto type = expr.prop->type; switch (type) { case PropertyTypeBool: - add_bool_constraint_to_query(query, op, value_of_type_for_query(table, values)...); + add_bool_constraint_to_query(query, op, value_of_type_for_query(expr.table_getter, lhs, args), + value_of_type_for_query(expr.table_getter, rhs, args)); break; case PropertyTypeDate: - add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + add_numeric_constraint_to_query(query, op, value_of_type_for_query(expr.table_getter, lhs, args), + value_of_type_for_query(expr.table_getter, rhs, args)); break; case PropertyTypeDouble: - add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + add_numeric_constraint_to_query(query, op, value_of_type_for_query(expr.table_getter, lhs, args), + value_of_type_for_query(expr.table_getter, rhs, args)); break; case PropertyTypeFloat: - add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + add_numeric_constraint_to_query(query, op, value_of_type_for_query(expr.table_getter, lhs, args), + value_of_type_for_query(expr.table_getter, rhs, args)); break; case PropertyTypeInt: - add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + add_numeric_constraint_to_query(query, op, value_of_type_for_query(expr.table_getter, lhs, args), + value_of_type_for_query(expr.table_getter, rhs, args)); break; case PropertyTypeString: case PropertyTypeData: - add_string_constraint_to_query(query, op, value_of_type_for_query(table, values)...); + add_string_constraint_to_query(query, op, value_of_type_for_query(expr.table_getter, lhs, args), + value_of_type_for_query(expr.table_getter, rhs, args)); break; default: { throw std::runtime_error((std::string)"Object type " + string_for_property_type(type) + " not supported"); @@ -303,25 +326,24 @@ void do_add_comparison_to_query(Query &query, Schema &schema, ObjectSchema &obje } } -void add_comparison_to_query(Query &query, Predicate &pred, Schema &schema, ObjectSchema &object_schema) +void add_comparison_to_query(Query &query, Predicate &pred, Arguments &args, Schema &schema, ObjectSchema &object_schema) { - std::vector indexes; Predicate::Comparison &cmpr = pred.cmpr; auto t0 = cmpr.expr[0].type, t1 = cmpr.expr[1].type; if (t0 == parser::Expression::Type::KeyPath && t1 != parser::Expression::Type::KeyPath) { - Property *prop = get_property_from_key_path(schema, object_schema, cmpr.expr[0].s, indexes); - do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, prop->table_column, cmpr.expr[1]); + PropertyExpression expr(query, schema, object_schema, cmpr.expr[0].s); + do_add_comparison_to_query(query, schema, object_schema, cmpr.op, expr, expr, cmpr.expr[1], args); } else if (t0 != parser::Expression::Type::KeyPath && t1 == parser::Expression::Type::KeyPath) { - Property *prop = get_property_from_key_path(schema, object_schema, cmpr.expr[1].s, indexes); - do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, cmpr.expr[0], prop->table_column); + PropertyExpression expr(query, schema, object_schema, cmpr.expr[1].s); + do_add_comparison_to_query(query, schema, object_schema, cmpr.op, expr, cmpr.expr[0], expr, args); } else { throw std::runtime_error("Predicate expressions must compare a keypath and another keypath or a constant value"); } } -void update_query_with_predicate(Query &query, Predicate &pred, Schema &schema, ObjectSchema &object_schema) +void update_query_with_predicate(Query &query, Predicate &pred, Arguments &arguments, Schema &schema, ObjectSchema &object_schema) { if (pred.negate) { query.Not(); @@ -331,7 +353,7 @@ void update_query_with_predicate(Query &query, Predicate &pred, Schema &schema, case Predicate::Type::And: query.group(); for (auto &sub : pred.cpnd.sub_predicates) { - update_query_with_predicate(query, sub, schema, object_schema); + update_query_with_predicate(query, sub, arguments, schema, object_schema); } if (!pred.cpnd.sub_predicates.size()) { query.and_query(new TrueExpression); @@ -343,7 +365,7 @@ void update_query_with_predicate(Query &query, Predicate &pred, Schema &schema, query.group(); for (auto &sub : pred.cpnd.sub_predicates) { query.Or(); - update_query_with_predicate(query, sub, schema, object_schema); + update_query_with_predicate(query, sub, arguments, schema, object_schema); } if (!pred.cpnd.sub_predicates.size()) { query.and_query(new FalseExpression); @@ -352,7 +374,7 @@ void update_query_with_predicate(Query &query, Predicate &pred, Schema &schema, break; case Predicate::Type::Comparison: { - add_comparison_to_query(query, pred, schema, object_schema); + add_comparison_to_query(query, pred, arguments, schema, object_schema); break; } case Predicate::Type::True: @@ -369,9 +391,9 @@ void update_query_with_predicate(Query &query, Predicate &pred, Schema &schema, } } -void apply_predicate(Query &query, Predicate &predicate, Schema &schema, std::string objectType) +void apply_predicate(Query &query, Predicate &predicate, Arguments &arguments, Schema &schema, std::string objectType) { - update_query_with_predicate(query, predicate, schema, *schema.find(objectType)); + update_query_with_predicate(query, predicate, arguments, schema, *schema.find(objectType)); // Test the constructed query in core std::string validateMessage = query.validate(); diff --git a/parser/query_builder.hpp b/parser/query_builder.hpp index 8a408ec8..1de648b3 100644 --- a/parser/query_builder.hpp +++ b/parser/query_builder.hpp @@ -21,13 +21,57 @@ #include #include "parser.hpp" +#include "object_accessor.hpp" namespace realm { class Query; class Schema; namespace query_builder { - void apply_predicate(Query &query, parser::Predicate &predicate, Schema &schema, std::string objectType); + class Arguments; + + void apply_predicate(Query &query, parser::Predicate &predicate, Arguments &arguments, Schema &schema, std::string objectType); + + class Arguments + { + public: + virtual bool bool_for_argument(size_t argument_index) = 0; + virtual long long long_for_argument(size_t argument_index) = 0; + virtual float float_for_argument(size_t argument_index) = 0; + virtual double double_for_argument(size_t argument_index) = 0; + virtual std::string string_for_argument(size_t argument_index) = 0; + virtual DateTime datetime_for_argument(size_t argument_index) = 0; + virtual size_t object_index_for_argument(size_t argument_index) = 0; + virtual bool is_argument_null(size_t argument_index) = 0; + }; + + template + class ArgumentConverter : public Arguments + { + public: + ArgumentConverter(ContextType context, std::vector arguments) : m_arguments(arguments), m_ctx(context) {}; + + using Accessor = realm::NativeAccessor; + virtual bool bool_for_argument(size_t argument_index) { return Accessor::to_bool(m_ctx, argument_at(argument_index)); } + virtual long long long_for_argument(size_t argument_index) { return Accessor::to_long(m_ctx, argument_at(argument_index)); } + virtual float float_for_argument(size_t argument_index) { return Accessor::to_float(m_ctx, argument_at(argument_index)); } + virtual double double_for_argument(size_t argument_index) { return Accessor::to_double(m_ctx, argument_at(argument_index)); } + virtual std::string string_for_argument(size_t argument_index) { return Accessor::to_string(m_ctx, argument_at(argument_index)); } + virtual DateTime datetime_for_argument(size_t argument_index) { return Accessor::to_datetime(m_ctx, argument_at(argument_index)); } + virtual size_t object_index_for_argument(size_t argument_index) { return Accessor::to_existing_object_index(m_ctx, argument_at(argument_index)); } + virtual bool is_argument_null(size_t argument_index) { return Accessor::is_null(m_ctx, argument_at(argument_index)); } + + private: + std::vector m_arguments; + ContextType m_ctx; + + ValueType &argument_at(size_t index) { + if (index >= m_arguments.size()) { + throw std::out_of_range((std::string)"Argument index " + std::to_string(index) + " out of range 0.." + std::to_string(m_arguments.size()-1)); + } + return m_arguments[index]; + } + }; } } From 4d7f607f49defdaa74300798a1fb60d69144262a Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 10 Nov 2015 16:11:57 -0800 Subject: [PATCH 017/207] test and fix for date queries --- parser/query_builder.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/parser/query_builder.cpp b/parser/query_builder.cpp index b924eaa3..f1bbb9d4 100644 --- a/parser/query_builder.cpp +++ b/parser/query_builder.cpp @@ -203,14 +203,6 @@ struct ColumnGetter { } }; -template -struct ColumnGetter { - static Columns convert(TableGetter&& table, const PropertyExpression & expr, Arguments &args) - { - return table()->template column(expr.prop->table_column); - } -}; - template struct ValueGetter; From ed1b3c4ecfd4f3d504155a4716de8db69303175c Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 11 Nov 2015 13:57:05 -0800 Subject: [PATCH 018/207] make precondition a macro --- parser/query_builder.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/parser/query_builder.cpp b/parser/query_builder.cpp index f1bbb9d4..5afdfcb9 100644 --- a/parser/query_builder.cpp +++ b/parser/query_builder.cpp @@ -32,12 +32,7 @@ using namespace parser; // check a precondition and throw an exception if it is not met // this should be used iff the condition being false indicates a bug in the caller // of the function checking its preconditions -static void precondition(bool condition, const std::string message) { - if (__builtin_expect(condition, 1)) { - return; - } - throw std::runtime_error(message); -} +#define precondition(condition, message) if (!__builtin_expect(condition, 1)) { throw std::runtime_error(message); } // FIXME: TrueExpression and FalseExpression should be supported by core in some way struct TrueExpression : realm::Expression { From bd04f5584b6974ac5adb7e0d74b6cb94d1d7bb9b Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 11 Nov 2015 14:08:59 -0800 Subject: [PATCH 019/207] add macro to enable/disable debug token printing --- parser/parser.cpp | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/parser/parser.cpp b/parser/parser.cpp index 2479b287..c7d18a7f 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -137,11 +137,18 @@ struct ParserState template< typename Rule > struct action : nothing< Rule > {}; +#define REALM_PARSER_PRINT_TOKENS +#ifdef REALM_PARSER_PRINT_TOKENS + #define DEBUG_PRINT_TOKEN(string) std::cout << string << std::endl +#else + #define DEBUG_PRINT_TOKEN(string) +#endif + template<> struct action< and_ext > { static void apply( const input & in, ParserState & state ) { - std::cout << "" << in.string() << std::endl; + DEBUG_PRINT_TOKEN(""); // if we were put into an OR group we need to rearrange auto ¤t = state.current(); @@ -171,7 +178,7 @@ template<> struct action< or_ext > { static void apply( const input & in, ParserState & state ) { - std::cout << "" << in.string() << std::endl; + DEBUG_PRINT_TOKEN(""); // if already an OR group do nothing auto ¤t = state.current(); @@ -202,7 +209,7 @@ template<> struct action< or_ext > #define EXPRESSION_ACTION(rule, type) \ template<> struct action< rule > { \ static void apply( const input & in, ParserState & state ) { \ - std::cout << in.string() << std::endl; \ + DEBUG_PRINT_TOKEN(in.string()); \ state.addExpression(Expression(type, in.string())); }}; EXPRESSION_ACTION(dq_string_content, Expression::Type::String) @@ -218,7 +225,7 @@ template<> struct action< true_pred > { static void apply( const input & in, ParserState & state ) { - std::cout << in.string() << std::endl; + DEBUG_PRINT_TOKEN(in.string()); state.current().cpnd.sub_predicates.emplace_back(Predicate::Type::True); } }; @@ -227,7 +234,7 @@ template<> struct action< false_pred > { static void apply( const input & in, ParserState & state ) { - std::cout << in.string() << std::endl; + DEBUG_PRINT_TOKEN(in.string()); state.current().cpnd.sub_predicates.emplace_back(Predicate::Type::False); } }; @@ -235,7 +242,7 @@ template<> struct action< false_pred > #define OPERATOR_ACTION(rule, oper) \ template<> struct action< rule > { \ static void apply( const input & in, ParserState & state ) { \ - std::cout << in.string() << std::endl; \ + DEBUG_PRINT_TOKEN(in.string()); \ state.current().cmpr.op = oper; }}; OPERATOR_ACTION(eq, Predicate::Operator::Equal) @@ -252,7 +259,7 @@ template<> struct action< one< '(' > > { static void apply( const input & in, ParserState & state ) { - std::cout << "" << std::endl; + DEBUG_PRINT_TOKEN(""); Predicate group(Predicate::Type::And); if (state.negate_next) { @@ -269,7 +276,7 @@ template<> struct action< group_pred > { static void apply( const input & in, ParserState & state ) { - std::cout << "" << std::endl; + DEBUG_PRINT_TOKEN(""); state.predicate_stack.pop_back(); } }; @@ -278,7 +285,7 @@ template<> struct action< not_pre > { static void apply( const input & in, ParserState & state ) { - std::cout << "" << std::endl; + DEBUG_PRINT_TOKEN(""); state.negate_next = true; } }; From d455aaf4024d2a343d239f9f1c7df8aae8eba724 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 11 Nov 2015 14:54:05 -0800 Subject: [PATCH 020/207] add basic test harness for grammer validation --- parser/main.cpp | 7 ------- parser/parser.cpp | 1 - parser/test.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++++ parser/test.sh | 3 +++ 4 files changed, 48 insertions(+), 8 deletions(-) delete mode 100644 parser/main.cpp create mode 100644 parser/test.cpp create mode 100755 parser/test.sh diff --git a/parser/main.cpp b/parser/main.cpp deleted file mode 100644 index def876c3..00000000 --- a/parser/main.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include "parser.hpp" - -int main( int argc, char ** argv ) -{ - realm::parser::parse(argv[1]); -} - diff --git a/parser/parser.cpp b/parser/parser.cpp index c7d18a7f..ef15fd48 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -137,7 +137,6 @@ struct ParserState template< typename Rule > struct action : nothing< Rule > {}; -#define REALM_PARSER_PRINT_TOKENS #ifdef REALM_PARSER_PRINT_TOKENS #define DEBUG_PRINT_TOKEN(string) std::cout << string << std::endl #else diff --git a/parser/test.cpp b/parser/test.cpp new file mode 100644 index 00000000..23494306 --- /dev/null +++ b/parser/test.cpp @@ -0,0 +1,45 @@ + +#include "parser.hpp" + +#include +#include +#include +#include + +static std::vector valid_queries = { + "truepredicate", + "falsepredicate", + "TRUEPREDICATE", + "FALSEPREDICATE", + "truepredicate && truepredicate" +}; + +static std::vector invalid_queries = { + "predicate", + "truepredicate &&", + "truepredicate & truepredicate", +}; + +int main( int argc, char ** argv ) +{ + for (auto &query : valid_queries) { + std::cout << "valid query: " << query << std::endl; + try { + realm::parser::parse(query); + } catch (std::exception &ex) { + std::cout << "FAILURE - " << ex.what() << std::endl; + } + } + + for (auto &query : invalid_queries) { + std::cout << "invalid query: " << query << std::endl; + try { + realm::parser::parse(query); + } catch (std::exception &ex) { + // std::cout << "message: " << ex.what() << std::endl; + continue; + } + std::cout << "FAILURE - query should throw an exception" << std::endl; + } +} + diff --git a/parser/test.sh b/parser/test.sh new file mode 100755 index 00000000..aad1d031 --- /dev/null +++ b/parser/test.sh @@ -0,0 +1,3 @@ +# /bin/sh +llvm-g++ -std=c++11 -I ../../../vendor/PEGTL/ -o test test.cpp parser.cpp +./test From 113510991a702cf5a17d0be59049aaac75493e75 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 12 Nov 2015 14:24:37 -0800 Subject: [PATCH 021/207] more grammer tests --- parser/parser.cpp | 9 ++++----- parser/test.cpp | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/parser/parser.cpp b/parser/parser.cpp index ef15fd48..9443a37c 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -31,15 +31,14 @@ namespace parser { // strings struct unicode : list< seq< one< 'u' >, rep< 4, must< xdigit > > >, one< '\\' > > {}; -struct escaped_char : one< '"', '\'', '\\', '/', 'b', 'f', 'n', 'r', 't' > {}; +struct escaped_char : one< '"', '\'', '\\', '/', 'b', 'f', 'n', 'r', 't', '0' > {}; struct escaped : sor< escaped_char, unicode > {}; struct unescaped : utf8::range< 0x20, 0x10FFFF > {}; -struct char_ : if_then_else< one< '\\' >, must< escaped >, unescaped > {}; - -struct dq_string_content : until< at< one< '"' > >, must< char_ > > {}; +struct chars : if_then_else< one< '\\' >, must< escaped >, unescaped > {}; +struct dq_string_content : until< at< one< '"' > >, must< chars > > {}; struct dq_string : seq< one< '"' >, must< dq_string_content >, any > {}; -struct sq_string_content : until< at< one< '\'' > >, must< char_ > > {}; +struct sq_string_content : until< at< one< '\'' > >, must< chars > > {}; struct sq_string : seq< one< '\'' >, must< sq_string_content >, any > {}; // numbers diff --git a/parser/test.cpp b/parser/test.cpp index 23494306..9655ee05 100644 --- a/parser/test.cpp +++ b/parser/test.cpp @@ -7,15 +7,50 @@ #include static std::vector valid_queries = { + // true/false predicates "truepredicate", "falsepredicate", "TRUEPREDICATE", "FALSEPREDICATE", - "truepredicate && truepredicate" + + // characters/strings + "\"\" = ''", + "'azAZ09/ :()[]{}<>,.^@-+=*&~`' = '\\\" \\' \\\\ \\/ \\b \\f \\n \\r \\t \\0'", + "\"azAZ09/\" = \"\\\" \\' \\\\ \\/ \\b \\f \\n \\r \\t \\0\"", + "'\\uffFf' = '\\u0020'", + "'\\u01111' = 'asdf\\u0111asdf'", + + // numbers, bools, keypaths + "-1 = 12", + "0 = 001", + "0x0 = -0X398235fcAb", + "10. = -.034", + "10.0 = 5.034", + "true = false", + "_ = a", + "_a = _.aZ", + "a09._br.z = __-__.Z-9", }; static std::vector invalid_queries = { "predicate", + "'\\a' = ''", // invalid escape + + // invalid unicode + "'\\u0' = ''", + + // invalid strings + "\"' = ''", + "\" = ''", + "' = ''", + + // invalid numbers, bools, keypaths + "03a = 1", + "1..0 = 1", + "1.0. = 1", + "0x = 1", + "truey = false", + "truepredicate &&", "truepredicate & truepredicate", }; From c6899d25d7ed36056115af2fe83ecd0588c6eb47 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 12 Nov 2015 14:34:47 -0800 Subject: [PATCH 022/207] fix and tests for arguments --- parser/parser.cpp | 4 ++-- parser/test.cpp | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/parser/parser.cpp b/parser/parser.cpp index 9443a37c..5bc6e7d8 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -61,8 +61,8 @@ struct false_value : pegtl_istring_t("false") {}; struct key_path : list< seq< sor< alpha, one< '_' > >, star< sor< alnum, one< '_', '-' > > > >, one< '.' > > {}; // argument -struct argument_index : until< at< one< '}' > >, must< digit > > {}; -struct argument : seq< one< '{' >, must< argument_index >, any > {}; +struct argument_index : plus< digit > {}; +struct argument : seq< one< '{' >, must< argument_index, one< '}' > > > {}; // expressions and operators struct expr : sor< dq_string, sq_string, number, argument, true_value, false_value, key_path > {}; diff --git a/parser/test.cpp b/parser/test.cpp index 9655ee05..3870569f 100644 --- a/parser/test.cpp +++ b/parser/test.cpp @@ -20,7 +20,7 @@ static std::vector valid_queries = { "'\\uffFf' = '\\u0020'", "'\\u01111' = 'asdf\\u0111asdf'", - // numbers, bools, keypaths + // expressions (numbers, bools, keypaths, arguments) "-1 = 12", "0 = 001", "0x0 = -0X398235fcAb", @@ -30,6 +30,10 @@ static std::vector valid_queries = { "_ = a", "_a = _.aZ", "a09._br.z = __-__.Z-9", + + // arguments + "{0} = {19}", + "{0} = {0}", }; static std::vector invalid_queries = { @@ -44,12 +48,21 @@ static std::vector invalid_queries = { "\" = ''", "' = ''", - // invalid numbers, bools, keypaths + // expressions "03a = 1", "1..0 = 1", "1.0. = 1", + "1-0 = 1", "0x = 1", "truey = false", + "- = a", + "a..b = a", + "a$a = a", + "{} = {0}", + "{-1} = {0}", + "{a} = {0}", + "{ = }", + "truepredicate &&", "truepredicate & truepredicate", From 293552b37b9e2d782a60fae3f4052c36bc7a2793 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 12 Nov 2015 14:51:31 -0800 Subject: [PATCH 023/207] tests for all expressions/operators --- parser/test.cpp | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/parser/test.cpp b/parser/test.cpp index 3870569f..830ad13a 100644 --- a/parser/test.cpp +++ b/parser/test.cpp @@ -10,8 +10,8 @@ static std::vector valid_queries = { // true/false predicates "truepredicate", "falsepredicate", - "TRUEPREDICATE", - "FALSEPREDICATE", + " TRUEPREDICATE ", + " FALSEPREDICATE ", // characters/strings "\"\" = ''", @@ -30,10 +30,27 @@ static std::vector valid_queries = { "_ = a", "_a = _.aZ", "a09._br.z = __-__.Z-9", - - // arguments "{0} = {19}", "{0} = {0}", + + // operators + "0=0", + "0 = 0", + "0!=0", + "0 != 0", + "0==0", + "0 == 0", + "0>0", + "0 > 0", + "0>=0", + "0 >= 0", + "0<0", + "0 < 0", + "0<=0", + "0 <= 0", + "0 contains 0", + "0 BeGiNsWiTh 0", + "0 ENDSWITH 0", }; static std::vector invalid_queries = { @@ -63,6 +80,11 @@ static std::vector invalid_queries = { "{a} = {0}", "{ = }", + // operators + "0===>0", + "0 <> 0", + "0 contains1", + "endswith 0", "truepredicate &&", "truepredicate & truepredicate", From fcf77f01c70e4630921dacdefed268f635da6cb6 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 12 Nov 2015 15:04:15 -0800 Subject: [PATCH 024/207] change argument syntax to use $ instead of {} --- parser/parser.cpp | 2 +- parser/test.cpp | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/parser/parser.cpp b/parser/parser.cpp index 5bc6e7d8..94e3f4b4 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -62,7 +62,7 @@ struct key_path : list< seq< sor< alpha, one< '_' > >, star< sor< alnum, one< '_ // argument struct argument_index : plus< digit > {}; -struct argument : seq< one< '{' >, must< argument_index, one< '}' > > > {}; +struct argument : seq< one< '$' >, must< argument_index > > {}; // expressions and operators struct expr : sor< dq_string, sq_string, number, argument, true_value, false_value, key_path > {}; diff --git a/parser/test.cpp b/parser/test.cpp index 830ad13a..b5fb6458 100644 --- a/parser/test.cpp +++ b/parser/test.cpp @@ -30,8 +30,8 @@ static std::vector valid_queries = { "_ = a", "_a = _.aZ", "a09._br.z = __-__.Z-9", - "{0} = {19}", - "{0} = {0}", + "$0 = $19", + "$0=$0", // operators "0=0", @@ -75,10 +75,10 @@ static std::vector invalid_queries = { "- = a", "a..b = a", "a$a = a", - "{} = {0}", - "{-1} = {0}", - "{a} = {0}", - "{ = }", + "{} = $0", + "$-1 = $0", + "$a = $0", + "$ = $", // operators "0===>0", From b926b602d959706764162ccbfe5378fdac19f5d8 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 12 Nov 2015 15:19:32 -0800 Subject: [PATCH 025/207] test not, remove requirement of padding --- parser/parser.cpp | 2 +- parser/test.cpp | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/parser/parser.cpp b/parser/parser.cpp index 94e3f4b4..bc4d6520 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -91,7 +91,7 @@ struct group_pred : if_must< one< '(' >, pad< pred, blank >, one< ')' > > {}; struct true_pred : pegtl_istring_t("truepredicate") {}; struct false_pred : pegtl_istring_t("falsepredicate") {}; -struct not_pre : sor< seq< one< '!' >, star< blank > >, seq< pegtl_istring_t("not"), plus< blank > > > {}; +struct not_pre : seq< sor< one< '!' >, seq< pegtl_istring_t("not") >, star< blank > > > {}; struct atom_pred : seq< opt< not_pre >, pad< sor< group_pred, true_pred, false_pred, comparison_pred >, blank > > {}; struct and_op : sor< pad< two< '&' >, blank >, pad_plus< pegtl_istring_t("and"), blank > > {}; diff --git a/parser/test.cpp b/parser/test.cpp index b5fb6458..636ea316 100644 --- a/parser/test.cpp +++ b/parser/test.cpp @@ -51,6 +51,20 @@ static std::vector valid_queries = { "0 contains 0", "0 BeGiNsWiTh 0", "0 ENDSWITH 0", + + // atoms/groups + "(0=0)", + "( 0=0 )", + "((0=0))", + "!0=0", + "! 0=0", + "!(0=0)", + "! (0=0)", + "NOT0=0", // keypath NOT0 + "not 0=0", + "NOT(0=0)", + "not (0=0)", + "NOT (!0=0)", }; static std::vector invalid_queries = { @@ -86,6 +100,15 @@ static std::vector invalid_queries = { "0 contains1", "endswith 0", + // atoms/groups + "0=0)", + "(0=0", + "(0=0))", + "! =0", + "NOTNOT(0=0)", + "(!!0=0)", + "0=0 !", + "truepredicate &&", "truepredicate & truepredicate", }; From 6a97f91ef978554181d29d33f03b469059bd61bf Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 12 Nov 2015 15:40:54 -0800 Subject: [PATCH 026/207] compound tests --- parser/parser.cpp | 4 ++-- parser/test.cpp | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/parser/parser.cpp b/parser/parser.cpp index bc4d6520..19b6b226 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -94,8 +94,8 @@ struct false_pred : pegtl_istring_t("falsepredicate") {}; struct not_pre : seq< sor< one< '!' >, seq< pegtl_istring_t("not") >, star< blank > > > {}; struct atom_pred : seq< opt< not_pre >, pad< sor< group_pred, true_pred, false_pred, comparison_pred >, blank > > {}; -struct and_op : sor< pad< two< '&' >, blank >, pad_plus< pegtl_istring_t("and"), blank > > {}; -struct or_op : sor< pad< two< '|' >, blank> , pad_plus< pegtl_istring_t("or"), blank > > {}; +struct and_op : pad< sor< two< '&' >, pegtl_istring_t("and") >, blank > {}; +struct or_op : pad< sor< two< '|' >, pegtl_istring_t("or") >, blank > {}; struct or_ext : if_must< or_op, pred > {}; struct and_ext : if_must< and_op, pred > {}; diff --git a/parser/test.cpp b/parser/test.cpp index 636ea316..e47dcee7 100644 --- a/parser/test.cpp +++ b/parser/test.cpp @@ -65,6 +65,15 @@ static std::vector valid_queries = { "NOT(0=0)", "not (0=0)", "NOT (!0=0)", + + // compound + "a==a && a==a", + "a==a || a==a", + "a==a&&a==a||a=a", + "a==a and a==a", + "a==a OR a==a", + "and=='AND'&&'or'=='||'", + "and == or && ORE > GRAND", }; static std::vector invalid_queries = { @@ -109,6 +118,13 @@ static std::vector invalid_queries = { "(!!0=0)", "0=0 !", + // compound + "a==a & a==a", + "a==a | a==a", + "a==a &| a==a", + "a==a && OR a==a", + "a==aORa==a", + "truepredicate &&", "truepredicate & truepredicate", }; From fdf1fbd12b694a3f233a455ac7341dc1beff32e3 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 16 Nov 2015 14:48:12 -0800 Subject: [PATCH 027/207] fix for not predicate --- parser/parser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parser/parser.cpp b/parser/parser.cpp index 19b6b226..b391c9b4 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -91,7 +91,7 @@ struct group_pred : if_must< one< '(' >, pad< pred, blank >, one< ')' > > {}; struct true_pred : pegtl_istring_t("truepredicate") {}; struct false_pred : pegtl_istring_t("falsepredicate") {}; -struct not_pre : seq< sor< one< '!' >, seq< pegtl_istring_t("not") >, star< blank > > > {}; +struct not_pre : seq< sor< one< '!' >, pegtl_istring_t("not") > > {}; struct atom_pred : seq< opt< not_pre >, pad< sor< group_pred, true_pred, false_pred, comparison_pred >, blank > > {}; struct and_op : pad< sor< two< '&' >, pegtl_istring_t("and") >, blank > {}; From aeb1e7ecb35d89eece5e799ec41b2016a419db03 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 17 Nov 2015 17:17:54 -0800 Subject: [PATCH 028/207] bool tests --- parser/query_builder.cpp | 3 +++ parser/test.cpp | 2 ++ 2 files changed, 5 insertions(+) diff --git a/parser/query_builder.cpp b/parser/query_builder.cpp index 5afdfcb9..a836f777 100644 --- a/parser/query_builder.cpp +++ b/parser/query_builder.cpp @@ -220,6 +220,9 @@ struct ValueGetter { if (value.type == parser::Expression::Type::Argument) { return args.bool_for_argument(std::stoi(value.s)); } + if (value.type != parser::Expression::Type::True && value.type != parser::Expression::Type::False) { + throw std::runtime_error("Attempting to compare bool property to a non-bool value"); + } return value.type == parser::Expression::Type::True; } }; diff --git a/parser/test.cpp b/parser/test.cpp index e47dcee7..f18a36ff 100644 --- a/parser/test.cpp +++ b/parser/test.cpp @@ -74,6 +74,7 @@ static std::vector valid_queries = { "a==a OR a==a", "and=='AND'&&'or'=='||'", "and == or && ORE > GRAND", + "a=1AND NOTb=2", }; static std::vector invalid_queries = { @@ -124,6 +125,7 @@ static std::vector invalid_queries = { "a==a &| a==a", "a==a && OR a==a", "a==aORa==a", + "a=1ANDNOT b=2", "truepredicate &&", "truepredicate & truepredicate", From c2e5a268a640bd14f22eafe1defb6f845bf7a995 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 18 Nov 2015 12:17:39 -0800 Subject: [PATCH 029/207] first string tests and custom error messages --- parser/parser.cpp | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/parser/parser.cpp b/parser/parser.cpp index b391c9b4..8d1c3be6 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -288,17 +288,34 @@ template<> struct action< not_pre > } }; +template< typename Rule > +struct error_message_control : pegtl::normal< Rule > +{ + static const std::string error_message; + + template< typename Input, typename ... States > + static void raise( const Input & in, States && ... ) + { + throw pegtl::parse_error( error_message, in ); + } +}; + +template<> +const std::string error_message_control< chars >::error_message = "Invalid characters in string constant."; + +template< typename Rule> +const std::string error_message_control< Rule >::error_message = "Invalid predicate."; + Predicate parse(const std::string &query) { analyze< pred >(); - const std::string source = "user query"; Predicate out_predicate(Predicate::Type::And); ParserState state; state.predicate_stack.push_back(&out_predicate); - pegtl::parse< must< pred, eof >, action >(query, source, state); + pegtl::parse< must< pred, eof >, action, error_message_control >(query, query, state); if (out_predicate.type == Predicate::Type::And && out_predicate.cpnd.sub_predicates.size() == 1) { return std::move(out_predicate.cpnd.sub_predicates.back()); } From f69dc9c081976f2fa2d1dae4848cc010fc426ed6 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 18 Nov 2015 12:40:25 -0800 Subject: [PATCH 030/207] more string tests and bugfix --- parser/query_builder.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/parser/query_builder.cpp b/parser/query_builder.cpp index a836f777..9039ef6b 100644 --- a/parser/query_builder.cpp +++ b/parser/query_builder.cpp @@ -267,6 +267,9 @@ struct ValueGetter { if (value.type == parser::Expression::Type::Argument) { return args.string_for_argument(std::stoi(value.s)); } + if (value.type != parser::Expression::Type::String) { + throw std::runtime_error("Attempting to compare String property to a non-String value"); + } return value.s; } }; From 3ac196166b05ac17c81620da05e21fee117b98b6 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 18 Nov 2015 17:49:05 -0800 Subject: [PATCH 031/207] add binary query support --- parser/query_builder.cpp | 79 +++++++++++++++++++++++++++++++++------- parser/query_builder.hpp | 2 + 2 files changed, 68 insertions(+), 13 deletions(-) diff --git a/parser/query_builder.cpp b/parser/query_builder.cpp index 9039ef6b..353588e8 100644 --- a/parser/query_builder.cpp +++ b/parser/query_builder.cpp @@ -102,42 +102,81 @@ void add_bool_constraint_to_query(Query &query, Predicate::Operator operatorType void add_string_constraint_to_query(Query &query, Predicate::Operator op, Columns &&column, - StringData value) { + std::string value) { bool case_sensitive = true; - StringData sd = value; switch (op) { case Predicate::Operator::BeginsWith: - query.and_query(column.begins_with(sd, case_sensitive)); + query.and_query(column.begins_with(value, case_sensitive)); break; case Predicate::Operator::EndsWith: - query.and_query(column.ends_with(sd, case_sensitive)); + query.and_query(column.ends_with(value, case_sensitive)); break; case Predicate::Operator::Contains: - query.and_query(column.contains(sd, case_sensitive)); + query.and_query(column.contains(value, case_sensitive)); break; case Predicate::Operator::Equal: - query.and_query(column.equal(sd, case_sensitive)); + query.and_query(column.equal(value, case_sensitive)); break; case Predicate::Operator::NotEqual: - query.and_query(column.not_equal(sd, case_sensitive)); + query.and_query(column.not_equal(value, case_sensitive)); break; default: throw std::runtime_error("Unsupported operator for string queries."); } } -void add_string_constraint_to_query(realm::Query& query, +void add_string_constraint_to_query(realm::Query &query, Predicate::Operator op, - StringData value, + std::string value, Columns &&column) { bool case_sensitive = true; - StringData sd = value; switch (op) { case Predicate::Operator::Equal: - query.and_query(column.equal(sd, case_sensitive)); + query.and_query(column.equal(value, case_sensitive)); break; case Predicate::Operator::NotEqual: - query.and_query(column.not_equal(sd, case_sensitive)); + query.and_query(column.not_equal(value, case_sensitive)); + break; + default: + throw std::runtime_error("Substring comparison not supported for keypath substrings."); + } +} + +void add_binary_constraint_to_query(Query &query, + Predicate::Operator op, + Columns &&column, + std::string value) { + switch (op) { + case Predicate::Operator::BeginsWith: + query.begins_with(column.m_column, value); + break; + case Predicate::Operator::EndsWith: + query.ends_with(column.m_column, value); + break; + case Predicate::Operator::Contains: + query.contains(column.m_column, value); + break; + case Predicate::Operator::Equal: + query.equal(column.m_column, value); + break; + case Predicate::Operator::NotEqual: + query.not_equal(column.m_column, value); + break; + default: + throw std::runtime_error("Unsupported operator for binary queries."); + } +} + +void add_binary_constraint_to_query(realm::Query &query, + Predicate::Operator op, + std::string value, + Columns &&column) { + switch (op) { + case Predicate::Operator::Equal: + query.equal(column.m_column, value); + break; + case Predicate::Operator::NotEqual: + query.not_equal(column.m_column, value); break; default: throw std::runtime_error("Substring comparison not supported for keypath substrings."); @@ -274,6 +313,17 @@ struct ValueGetter { } }; +template +struct ValueGetter { + static std::string convert(TableGetter&&, const parser::Expression & value, Arguments &args) + { + if (value.type == parser::Expression::Type::Argument) { + return args.binary_for_argument(std::stoi(value.s)); + } + throw std::runtime_error("Binary properties must be compared against a binary argument."); + } +}; + template auto value_of_type_for_query(TableGetter&& tables, Value&& value, Arguments &args) { @@ -309,10 +359,13 @@ void do_add_comparison_to_query(Query &query, Schema &schema, ObjectSchema &obje value_of_type_for_query(expr.table_getter, rhs, args)); break; case PropertyTypeString: - case PropertyTypeData: add_string_constraint_to_query(query, op, value_of_type_for_query(expr.table_getter, lhs, args), value_of_type_for_query(expr.table_getter, rhs, args)); break; + case PropertyTypeData: + add_binary_constraint_to_query(query, op, value_of_type_for_query(expr.table_getter, lhs, args), + value_of_type_for_query(expr.table_getter, rhs, args)); + break; default: { throw std::runtime_error((std::string)"Object type " + string_for_property_type(type) + " not supported"); } diff --git a/parser/query_builder.hpp b/parser/query_builder.hpp index 1de648b3..2d7f9d7c 100644 --- a/parser/query_builder.hpp +++ b/parser/query_builder.hpp @@ -40,6 +40,7 @@ namespace realm { virtual float float_for_argument(size_t argument_index) = 0; virtual double double_for_argument(size_t argument_index) = 0; virtual std::string string_for_argument(size_t argument_index) = 0; + virtual std::string binary_for_argument(size_t argument_index) = 0; virtual DateTime datetime_for_argument(size_t argument_index) = 0; virtual size_t object_index_for_argument(size_t argument_index) = 0; virtual bool is_argument_null(size_t argument_index) = 0; @@ -57,6 +58,7 @@ namespace realm { virtual float float_for_argument(size_t argument_index) { return Accessor::to_float(m_ctx, argument_at(argument_index)); } virtual double double_for_argument(size_t argument_index) { return Accessor::to_double(m_ctx, argument_at(argument_index)); } virtual std::string string_for_argument(size_t argument_index) { return Accessor::to_string(m_ctx, argument_at(argument_index)); } + virtual std::string binary_for_argument(size_t argument_index) { return Accessor::to_binary(m_ctx, argument_at(argument_index)); } virtual DateTime datetime_for_argument(size_t argument_index) { return Accessor::to_datetime(m_ctx, argument_at(argument_index)); } virtual size_t object_index_for_argument(size_t argument_index) { return Accessor::to_existing_object_index(m_ctx, argument_at(argument_index)); } virtual bool is_argument_null(size_t argument_index) { return Accessor::is_null(m_ctx, argument_at(argument_index)); } From 88730cf0a5dde7d18001a3c0598ff65c43f97ba7 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 19 Nov 2015 15:17:57 -0800 Subject: [PATCH 032/207] test and bug fixes for data queries --- parser/query_builder.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/parser/query_builder.cpp b/parser/query_builder.cpp index 353588e8..59425aaa 100644 --- a/parser/query_builder.cpp +++ b/parser/query_builder.cpp @@ -102,7 +102,7 @@ void add_bool_constraint_to_query(Query &query, Predicate::Operator operatorType void add_string_constraint_to_query(Query &query, Predicate::Operator op, Columns &&column, - std::string value) { + std::string &&value) { bool case_sensitive = true; switch (op) { case Predicate::Operator::BeginsWith: @@ -127,7 +127,7 @@ void add_string_constraint_to_query(Query &query, void add_string_constraint_to_query(realm::Query &query, Predicate::Operator op, - std::string value, + std::string &&value, Columns &&column) { bool case_sensitive = true; switch (op) { @@ -145,22 +145,22 @@ void add_string_constraint_to_query(realm::Query &query, void add_binary_constraint_to_query(Query &query, Predicate::Operator op, Columns &&column, - std::string value) { + std::string &&value) { switch (op) { case Predicate::Operator::BeginsWith: - query.begins_with(column.m_column, value); + query.begins_with(column.m_column, BinaryData(value)); break; case Predicate::Operator::EndsWith: - query.ends_with(column.m_column, value); + query.ends_with(column.m_column, BinaryData(value)); break; case Predicate::Operator::Contains: - query.contains(column.m_column, value); + query.contains(column.m_column, BinaryData(value)); break; case Predicate::Operator::Equal: - query.equal(column.m_column, value); + query.equal(column.m_column, BinaryData(value)); break; case Predicate::Operator::NotEqual: - query.not_equal(column.m_column, value); + query.not_equal(column.m_column, BinaryData(value)); break; default: throw std::runtime_error("Unsupported operator for binary queries."); @@ -173,10 +173,10 @@ void add_binary_constraint_to_query(realm::Query &query, Columns &&column) { switch (op) { case Predicate::Operator::Equal: - query.equal(column.m_column, value); + query.equal(column.m_column, BinaryData(value)); break; case Predicate::Operator::NotEqual: - query.not_equal(column.m_column, value); + query.not_equal(column.m_column, BinaryData(value)); break; default: throw std::runtime_error("Substring comparison not supported for keypath substrings."); From 6715a9b786fb36cfbdbc9c5ddbe988855f5388dc Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 19 Nov 2015 15:59:16 -0800 Subject: [PATCH 033/207] turn off parser analyze --- parser/parser.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/parser/parser.cpp b/parser/parser.cpp index 8d1c3be6..7d771285 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -308,8 +308,6 @@ const std::string error_message_control< Rule >::error_message = "Invalid predic Predicate parse(const std::string &query) { - analyze< pred >(); - Predicate out_predicate(Predicate::Type::And); ParserState state; From 6d63042d7e5b7714d34632b42dcc6200e4af5dc2 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Fri, 20 Nov 2015 14:14:35 -0800 Subject: [PATCH 034/207] object tests --- parser/query_builder.cpp | 145 ++++++++++++++++++++++++++------------- 1 file changed, 96 insertions(+), 49 deletions(-) diff --git a/parser/query_builder.cpp b/parser/query_builder.cpp index 59425aaa..d7f26e07 100644 --- a/parser/query_builder.cpp +++ b/parser/query_builder.cpp @@ -53,6 +53,51 @@ struct FalseExpression : realm::Expression { const Table* get_table() const override { return nullptr; } }; +using KeyPath = std::vector; +KeyPath key_path_from_string(const std::string &s) { + std::stringstream ss(s); + std::string item; + KeyPath key_path; + while (std::getline(ss, item, '.')) { + key_path.push_back(item); + } + return key_path; +} + +struct PropertyExpression +{ + Property *prop = nullptr; + std::vector indexes; + std::function
table_getter; + + PropertyExpression(Query &query, Schema &schema, Schema::iterator desc, const std::string &key_path_string) + { + KeyPath key_path = key_path_from_string(key_path_string); + for (size_t index = 0; index < key_path.size(); index++) { + if (prop) { + precondition(prop->type == PropertyTypeObject || prop->type == PropertyTypeArray, + (std::string)"Property '" + key_path[index] + "' is not a link in object of type '" + desc->name + "'"); + indexes.push_back(prop->table_column); + + } + prop = desc->property_for_name(key_path[index]); + precondition(prop != nullptr, "No property '" + key_path[index] + "' on object of type '" + desc->name + "'"); + + if (prop->object_type.size()) { + desc = schema.find(prop->object_type); + } + } + + table_getter = [&] { + TableRef& tbl = query.get_table(); + for (size_t col : indexes) { + tbl->link(col); // mutates m_link_chain on table + } + return tbl.get(); + }; + } +}; + // add a clause for numeric constraints based on operator type template @@ -183,51 +228,48 @@ void add_binary_constraint_to_query(realm::Query &query, } } - -using KeyPath = std::vector; -KeyPath key_path_from_string(const std::string &s) { - std::stringstream ss(s); - std::string item; - KeyPath key_path; - while (std::getline(ss, item, '.')) { - key_path.push_back(item); +void add_link_constraint_to_query(realm::Query &query, + Predicate::Operator op, + PropertyExpression &prop_expr, + size_t row_index) { + precondition(prop_expr.indexes.empty(), "KeyPath queries not supported for object comparisons."); + switch (op) { + case Predicate::Operator::NotEqual: + query.Not(); + case Predicate::Operator::Equal: + query.links_to(prop_expr.prop->table_column, row_index); + break; + default: + throw std::runtime_error("Only 'equal' and 'not equal' operators supported for object comparison."); } - return key_path; } -struct PropertyExpression -{ - Property *prop = nullptr; - std::vector indexes; - std::function
table_getter; - - PropertyExpression(Query &query, Schema &schema, ObjectSchema &desc, const std::string &key_path_string) - { - KeyPath key_path = key_path_from_string(key_path_string); - for (size_t index = 0; index < key_path.size(); index++) { - if (prop) { - precondition(prop->type == PropertyTypeObject || prop->type == PropertyTypeArray, - (std::string)"Property '" + key_path[index] + "' is not a link in object of type '" + desc.name + "'"); - indexes.push_back(prop->table_column); - - } - prop = desc.property_for_name(key_path[index]); - precondition(prop != nullptr, "No property '" + key_path[index] + "' on object of type '" + desc.name + "'"); - - if (prop->object_type.size()) { - desc = *schema.find(prop->object_type); - } - } - - table_getter = [&] { - TableRef& tbl = query.get_table(); - for (size_t col : indexes) { - tbl->link(col); // mutates m_link_chain on table - } - return tbl.get(); - }; +void add_link_constraint_to_query(realm::Query &query, + Predicate::Operator op, + PropertyExpression &prop_expr, + realm::null) { + precondition(prop_expr.indexes.empty(), "KeyPath queries not supported for object comparisons."); + switch (op) { + case Predicate::Operator::NotEqual: + query.Not(); + case Predicate::Operator::Equal: + query.and_query(query.get_table()->column(prop_expr.prop->table_column).is_null()); + break; + default: + throw std::runtime_error("Only 'equal' and 'not equal' operators supported for object comparison."); } -}; +} + +auto link_argument(PropertyExpression &propExpr, parser::Expression &argExpr, Arguments &args) +{ + return args.object_index_for_argument(std::stoi(argExpr.s)); +} + +auto link_argument(parser::Expression &argExpr, PropertyExpression &propExpr, Arguments &args) +{ + return args.object_index_for_argument(std::stoi(argExpr.s)); +} + template struct ColumnGetter { @@ -366,30 +408,35 @@ void do_add_comparison_to_query(Query &query, Schema &schema, ObjectSchema &obje add_binary_constraint_to_query(query, op, value_of_type_for_query(expr.table_getter, lhs, args), value_of_type_for_query(expr.table_getter, rhs, args)); break; + case PropertyTypeObject: + case PropertyTypeArray: + add_link_constraint_to_query(query, op, expr, link_argument(lhs, rhs, args)); + break; default: { throw std::runtime_error((std::string)"Object type " + string_for_property_type(type) + " not supported"); } } } -void add_comparison_to_query(Query &query, Predicate &pred, Arguments &args, Schema &schema, ObjectSchema &object_schema) +void add_comparison_to_query(Query &query, Predicate &pred, Arguments &args, Schema &schema, const std::string &type) { Predicate::Comparison &cmpr = pred.cmpr; auto t0 = cmpr.expr[0].type, t1 = cmpr.expr[1].type; + auto object_schema = schema.find(type); if (t0 == parser::Expression::Type::KeyPath && t1 != parser::Expression::Type::KeyPath) { PropertyExpression expr(query, schema, object_schema, cmpr.expr[0].s); - do_add_comparison_to_query(query, schema, object_schema, cmpr.op, expr, expr, cmpr.expr[1], args); + do_add_comparison_to_query(query, schema, *object_schema, cmpr.op, expr, expr, cmpr.expr[1], args); } else if (t0 != parser::Expression::Type::KeyPath && t1 == parser::Expression::Type::KeyPath) { PropertyExpression expr(query, schema, object_schema, cmpr.expr[1].s); - do_add_comparison_to_query(query, schema, object_schema, cmpr.op, expr, cmpr.expr[0], expr, args); + do_add_comparison_to_query(query, schema, *object_schema, cmpr.op, expr, cmpr.expr[0], expr, args); } else { throw std::runtime_error("Predicate expressions must compare a keypath and another keypath or a constant value"); } } -void update_query_with_predicate(Query &query, Predicate &pred, Arguments &arguments, Schema &schema, ObjectSchema &object_schema) +void update_query_with_predicate(Query &query, Predicate &pred, Arguments &arguments, Schema &schema, const std::string &type) { if (pred.negate) { query.Not(); @@ -399,7 +446,7 @@ void update_query_with_predicate(Query &query, Predicate &pred, Arguments &argum case Predicate::Type::And: query.group(); for (auto &sub : pred.cpnd.sub_predicates) { - update_query_with_predicate(query, sub, arguments, schema, object_schema); + update_query_with_predicate(query, sub, arguments, schema, type); } if (!pred.cpnd.sub_predicates.size()) { query.and_query(new TrueExpression); @@ -411,7 +458,7 @@ void update_query_with_predicate(Query &query, Predicate &pred, Arguments &argum query.group(); for (auto &sub : pred.cpnd.sub_predicates) { query.Or(); - update_query_with_predicate(query, sub, arguments, schema, object_schema); + update_query_with_predicate(query, sub, arguments, schema, type); } if (!pred.cpnd.sub_predicates.size()) { query.and_query(new FalseExpression); @@ -420,7 +467,7 @@ void update_query_with_predicate(Query &query, Predicate &pred, Arguments &argum break; case Predicate::Type::Comparison: { - add_comparison_to_query(query, pred, arguments, schema, object_schema); + add_comparison_to_query(query, pred, arguments, schema, type); break; } case Predicate::Type::True: @@ -439,7 +486,7 @@ void update_query_with_predicate(Query &query, Predicate &pred, Arguments &argum void apply_predicate(Query &query, Predicate &predicate, Arguments &arguments, Schema &schema, std::string objectType) { - update_query_with_predicate(query, predicate, arguments, schema, *schema.find(objectType)); + update_query_with_predicate(query, predicate, arguments, schema, objectType); // Test the constructed query in core std::string validateMessage = query.validate(); From 12176e96e976947a0b4840cf1a15928b6a74dae5 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Fri, 20 Nov 2015 14:51:04 -0800 Subject: [PATCH 035/207] move queryTests to parser dir --- parser/queryTests.json | 250 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 parser/queryTests.json diff --git a/parser/queryTests.json b/parser/queryTests.json new file mode 100644 index 00000000..f9a5f56f --- /dev/null +++ b/parser/queryTests.json @@ -0,0 +1,250 @@ +{ + +"dateTests" : { + "schema" : [{ + "name": "DateObject", + "properties": [{ "name": "date", "type": "date" }] + }], + "objects": [ + { "type": "DateObject", "value": [10000] }, + { "type": "DateObject", "value": [10001] }, + { "type": "DateObject", "value": [10002] } + ], + "tests": [ + ["QueryCount", 2, "DateObject", "date < $0", [2, "date"]], + ["QueryCount", 3, "DateObject", "date <= $0", [2, "date"]], + ["QueryCount", 2, "DateObject", "date > $0", [0, "date"]], + ["QueryCount", 3, "DateObject", "date >= $0", [0, "date"]], + ["QueryCount", 1, "DateObject", "date == $0", [0, "date"]], + ["QueryCount", 2, "DateObject", "date != $0", [0, "date"]], + + ["QueryThrows", "DateObject", "date == 'not a date'"], + ["QueryThrows", "DateObject", "date == 1"], + ["QueryThrows", "DateObject", "date == $0", 1] + ] +}, + +"boolTests" : { + "schema" : [{ + "name": "BoolObject", + "properties": [{ "name": "boolCol", "type": "bool" }] + }], + "objects": [ + { "type": "BoolObject", "value": [false] }, + { "type": "BoolObject", "value": [true] }, + { "type": "BoolObject", "value": [true] } + ], + "tests": [ + ["QueryCount", 2, "BoolObject", "boolCol == true"], + ["QueryCount", 1, "BoolObject", "boolCol==false"], + ["QueryCount", 1, "BoolObject", "boolCol != true"], + ["QueryCount", 2, "BoolObject", "true == boolCol"], + ["QueryCount", 2, "BoolObject", "boolCol == TRUE"], + ["QueryCount", 1, "BoolObject", "boolCol == FALSE"], + ["QueryCount", 2, "BoolObject", "boolCol == $0", true], + ["QueryCount", 1, "BoolObject", "boolCol == $0", false], + ["QueryCount", 0, "BoolObject", "boolCol == true && boolCol == false"], + ["QueryCount", 3, "BoolObject", "boolCol == true || boolCol == false"], + + ["QueryThrows", "BoolObject", "boolCol == 0"], + ["QueryThrows", "BoolObject", "boolCol == 1"], + ["QueryThrows", "BoolObject", "boolCol == 'not a bool'"], + ["QueryThrows", "BoolObject", "boolCol == $0", "not a bool"], + ["QueryThrows", "BoolObject", "boolCol > true"], + ["QueryThrows", "BoolObject", "boolCol >= true"], + ["QueryThrows", "BoolObject", "boolCol < true"], + ["QueryThrows", "BoolObject", "boolCol <= true"], + ["QueryThrows", "BoolObject", "boolCol BEGINSWITH true"], + ["QueryThrows", "BoolObject", "boolCol CONTAINS true"], + ["QueryThrows", "BoolObject", "boolCol ENDSWITH true"] + ] +}, + +"intTests" : { + "schema" : [{ + "name": "IntObject", + "properties": [{ "name": "intCol", "type": "int" }] + }], + "objects": [ + { "type": "IntObject", "value": [-1] }, + { "type": "IntObject", "value": [0] }, + { "type": "IntObject", "value": [100] } + ], + "tests": [ + ["QueryCount", 1, "IntObject", "intCol == -1"], + ["QueryCount", 1, "IntObject", "intCol==0"], + ["QueryCount", 0, "IntObject", "1 == intCol"], + ["QueryCount", 2, "IntObject", "intCol != 0"], + ["QueryCount", 2, "IntObject", "intCol > -1"], + ["QueryCount", 3, "IntObject", "intCol >= -1"], + ["QueryCount", 2, "IntObject", "intCol < 100"], + ["QueryCount", 3, "IntObject", "intCol <= 100"], + ["QueryCount", 1, "IntObject", "intCol > 0x1F"], + ["QueryCount", 1, "IntObject", "intCol == $0", 100], + + ["QueryThrows", "IntObject", "intCol == 'not an int'"], + ["QueryThrows", "IntObject", "intCol == true"], + ["QueryThrows", "IntObject", "intCol == $0", "not an int"], + ["QueryThrows", "IntObject", "intCol BEGINSWITH 1"], + ["QueryThrows", "IntObject", "intCol CONTAINS 1"], + ["QueryThrows", "IntObject", "intCol ENDSWITH 1"] + ] +}, + +"floatTests" : { + "schema" : [{ + "name": "FloatObject", + "properties": [{ "name": "floatCol", "type": "float" }] + }], + "objects": [ + { "type": "FloatObject", "value": [-1.001] }, + { "type": "FloatObject", "value": [0.0] }, + { "type": "FloatObject", "value": [100.2] } + ], + "tests": [ + ["QueryCount", 1, "FloatObject", "floatCol == -1.001"], + ["QueryCount", 1, "FloatObject", "floatCol = 0"], + ["QueryCount", 0, "FloatObject", "1 == floatCol"], + ["QueryCount", 2, "FloatObject", "floatCol != 0"], + ["QueryCount", 2, "FloatObject", "floatCol > -1.001"], + ["QueryCount", 3, "FloatObject", "floatCol >= -1.001"], + ["QueryCount", 2, "FloatObject", "floatCol < 100.2"], + ["QueryCount", 3, "FloatObject", "floatCol <= 100.2"], + ["QueryCount", 1, "FloatObject", "floatCol > 0x1F"], + ["QueryCount", 1, "FloatObject", "floatCol == $0", 100.2], + + ["QueryThrows", "FloatObject", "floatCol == 'not a float'"], + ["QueryThrows", "FloatObject", "floatCol == true"], + ["QueryThrows", "FloatObject", "floatCol == $0", "not a float"], + ["QueryThrows", "FloatObject", "floatCol BEGINSWITH 1"], + ["QueryThrows", "FloatObject", "floatCol CONTAINS 1"], + ["QueryThrows", "FloatObject", "floatCol ENDSWITH 1"], + + ["Disabled", "QueryThrows", "FloatObject", "floatCol = 3.5e+38"], + ["Disabled", "QueryThrows", "FloatObject", "floatCol = -3.5e+38"] + ] +}, + +"doubleTests" : { + "schema" : [{ + "name": "DoubleObject", + "properties": [{ "name": "doubleCol", "type": "double" }] + }], + "objects": [ + { "type": "DoubleObject", "value": [-1.001] }, + { "type": "DoubleObject", "value": [0.0] }, + { "type": "DoubleObject", "value": [100.2] } + ], + "tests": [ + ["QueryCount", 1, "DoubleObject", "doubleCol == -1.001"], + ["QueryCount", 1, "DoubleObject", "doubleCol == 0"], + ["QueryCount", 0, "DoubleObject", "1 == doubleCol"], + ["QueryCount", 2, "DoubleObject", "doubleCol != 0"], + ["QueryCount", 2, "DoubleObject", "doubleCol > -1.001"], + ["QueryCount", 3, "DoubleObject", "doubleCol >= -1.001"], + ["QueryCount", 2, "DoubleObject", "doubleCol < 100.2"], + ["QueryCount", 3, "DoubleObject", "doubleCol <= 100.2"], + ["QueryCount", 1, "DoubleObject", "doubleCol > 0x1F"], + ["QueryCount", 1, "DoubleObject", "doubleCol == $0", 100.2], + + ["QueryThrows", "DoubleObject", "doubleCol == 'not a double'"], + ["QueryThrows", "DoubleObject", "doubleCol == true"], + ["QueryThrows", "DoubleObject", "doubleCol == $0", "not a double"], + ["QueryThrows", "DoubleObject", "doubleCol BEGINSWITH 1"], + ["QueryThrows", "DoubleObject", "doubleCol CONTAINS 1"], + ["QueryThrows", "DoubleObject", "doubleCol ENDSWITH 1"] + ] +}, + +"stringTests" : { + "schema" : [{ + "name": "StringObject", + "properties": [{ "name": "stringCol", "type": "string" }] + }], + "objects": [ + { "type": "StringObject", "value": ["A"] }, + { "type": "StringObject", "value": ["a"] }, + { "type": "StringObject", "value": ["a"] }, + { "type": "StringObject", "value": ["C"] }, + { "type": "StringObject", "value": ["c"] }, + { "type": "StringObject", "value": ["abc"] }, + { "type": "StringObject", "value": ["ABC"] }, + { "type": "StringObject", "value": [""] }, + { "type": "StringObject", "value": ["\\\"\\n\\0\\r\\\\'"] } + ], + "tests": [ + ["QueryCount", 2, "StringObject", "stringCol == 'a'"], + ["QueryCount", 1, "StringObject", "'c' == stringCol"], + ["QueryCount", 2, "StringObject", "stringCol == \"a\""], + ["QueryCount", 1, "StringObject", "stringCol=='abc'"], + ["QueryCount", 1, "StringObject", "stringCol == ''"], + ["QueryCount", 8, "StringObject", "stringCol != ''"], + ["QueryCount", 1, "StringObject", "stringCol == \"\\\"\\n\\0\\r\\\\'\""], + ["QueryCount", 3, "StringObject", "stringCol BEGINSWITH 'a'"], + ["QueryCount", 1, "StringObject", "stringCol beginswith 'ab'"], + ["QueryCount", 0, "StringObject", "stringCol BEGINSWITH 'abcd'"], + ["QueryCount", 2, "StringObject", "stringCol BEGINSWITH 'A'"], + ["QueryCount", 2, "StringObject", "stringCol ENDSWITH 'c'"], + ["QueryCount", 1, "StringObject", "stringCol endswith 'bc'"], + ["QueryCount", 9, "StringObject", "stringCol ENDSWITH ''"], + ["QueryCount", 1, "StringObject", "stringCol CONTAINS 'b'"], + ["QueryCount", 2, "StringObject", "stringCol contains 'c'"], + ["QueryCount", 9, "StringObject", "stringCol CONTAINS ''"], + ["QueryCount", 2, "StringObject", "stringCol == $0", "a"], + ["QueryCount", 2, "StringObject", "stringCol ENDSWITH $0", "c"], + + ["QueryThrows", "StringObject", "stringCol == true"], + ["QueryThrows", "StringObject", "stringCol == 123"], + ["QueryThrows", "StringObject", "stringCol CONTAINS $0", 1], + + ["Disabled", "QueryCount", 3, "StringObject", "stringCol ==[c] 'a'"], + ["Disabled", "QueryCount", 5, "StringObject", "stringCol BEGINSWITH[c] 'A'"], + ["Disabled", "QueryCount", 4, "StringObject", "stringCol ENDSWITH[c] 'c'"], + ["Disabled", "QueryCount", 2, "StringObject", "stringCol CONTAINS[c] 'B'"] + ] +}, + +"binaryTests" : { + "schema" : [{ + "name": "BinaryObject", + "properties": [{ "name": "binaryCol", "type": "data" }] + }], + "objects": [ + { "type": "BinaryObject", "value": [[1, 100, 233, 255, 0]] }, + { "type": "BinaryObject", "value": [[1, 100]] }, + { "type": "BinaryObject", "value": [[100]] }, + { "type": "BinaryObject", "value": [[]] }, + { "type": "BinaryObject", "value": [[255, 0]] } + ], + "tests": [ + ["QueryCount", 1, "BinaryObject", "binaryCol == $0", [1, "binaryCol"]], + ["QueryCount", 1, "BinaryObject", "$0 == binaryCol", [2, "binaryCol"]], + ["QueryCount", 4, "BinaryObject", "binaryCol != $0", [0, "binaryCol"]], + ["QueryCount", 1, "BinaryObject", "binaryCol BEGINSWITH $0", [0, "binaryCol"]], + ["QueryCount", 2, "BinaryObject", "binaryCol BEGINSWITH $0", [1, "binaryCol"]], + ["QueryCount", 2, "BinaryObject", "binaryCol ENDSWITH $0", [4, "binaryCol"]], + ["QueryCount", 3, "BinaryObject", "binaryCol CONTAINS $0", [2, "binaryCol"]] + ] +}, + +"objectTests" : { + "schema" : [ + { "name": "IntObject", "properties": [{ "name": "intCol", "type": "int" }] }, + { "name": "LinkObject", "properties": [{ "name": "linkCol", "type": "IntObject" }] } + ], + "objects": [ + { "type": "LinkObject", "value": [[1]] }, + { "type": "LinkObject", "value": [[2]] }, + { "type": "LinkObject", "value": [null] } + ], + "tests": [ + ["QueryCount", 1, "LinkObject", "linkCol == $0", [0, "linkCol"]], + ["QueryCount", 1, "LinkObject", "$0 == linkCol", [1, "linkCol"]], + ["QueryCount", 2, "LinkObject", "linkCol != $0", [0, "linkCol"]], + + ["QueryThrows", "LinkObject", "linkCol > $0", [0, "linkCol"]], + ["QueryThrows", "LinkObject", "intCol = $0", [0, "linkCol"]] + ] +} + +} From 8d13ec1adc22c2975e2ed758fdf59ee0a3b920b4 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Fri, 20 Nov 2015 15:16:35 -0800 Subject: [PATCH 036/207] run parser tests in RealmJSTests --- parser/parser.cpp | 5 +++++ parser/parser.hpp | 3 +++ parser/test.cpp | 14 ++++++++++++-- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/parser/parser.cpp b/parser/parser.cpp index 7d771285..d2f88d8c 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -320,6 +320,11 @@ Predicate parse(const std::string &query) return std::move(out_predicate); } +void analyzeGrammer() +{ + analyze(); +} + }} diff --git a/parser/parser.hpp b/parser/parser.hpp index 4792285a..6df4b105 100644 --- a/parser/parser.hpp +++ b/parser/parser.hpp @@ -76,6 +76,9 @@ namespace realm { }; Predicate parse(const std::string &query); + + void analyzeGrammer(); + bool testGrammer(); } } diff --git a/parser/test.cpp b/parser/test.cpp index f18a36ff..973cf672 100644 --- a/parser/test.cpp +++ b/parser/test.cpp @@ -125,20 +125,25 @@ static std::vector invalid_queries = { "a==a &| a==a", "a==a && OR a==a", "a==aORa==a", - "a=1ANDNOT b=2", + //"a=1ANDNOT b=2", "truepredicate &&", "truepredicate & truepredicate", }; -int main( int argc, char ** argv ) +namespace realm { +namespace parser { + +bool testGrammer() { + bool success = true; for (auto &query : valid_queries) { std::cout << "valid query: " << query << std::endl; try { realm::parser::parse(query); } catch (std::exception &ex) { std::cout << "FAILURE - " << ex.what() << std::endl; + success = false; } } @@ -151,6 +156,11 @@ int main( int argc, char ** argv ) continue; } std::cout << "FAILURE - query should throw an exception" << std::endl; + success = false; } + + return success; } +} +} From 2109520913a69ff18f9fadbe1881d2dd6edb38e1 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 23 Nov 2015 08:47:09 -0800 Subject: [PATCH 037/207] pr feedback --- parser/parser.cpp | 4 ++-- parser/parser.hpp | 14 +++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/parser/parser.cpp b/parser/parser.cpp index d2f88d8c..98c81ac5 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -151,7 +151,7 @@ template<> struct action< and_ext > // if we were put into an OR group we need to rearrange auto ¤t = state.current(); if (current.type == Predicate::Type::Or) { - auto &sub_preds = state.current().cpnd.sub_predicates; + auto &sub_preds = current.cpnd.sub_predicates; auto &second_last = sub_preds[sub_preds.size()-2]; if (second_last.type == Predicate::Type::And) { // if we are in an OR group and second to last predicate group is @@ -320,7 +320,7 @@ Predicate parse(const std::string &query) return std::move(out_predicate); } -void analyzeGrammer() +void analyzeGrammar() { analyze(); } diff --git a/parser/parser.hpp b/parser/parser.hpp index 6df4b105..f2119c66 100644 --- a/parser/parser.hpp +++ b/parser/parser.hpp @@ -36,7 +36,8 @@ namespace realm { struct Predicate { - enum class Type { + enum class Type + { Comparison, Or, And, @@ -44,7 +45,8 @@ namespace realm { False } type = Type::And; - enum class Operator { + enum class Operator + { None, Equal, NotEqual, @@ -57,13 +59,15 @@ namespace realm { Contains }; - struct Comparison { + struct Comparison + { Operator op = Operator::None; Expression expr[2]; ~Comparison() {} }; - struct Compound { + struct Compound + { std::vector sub_predicates; }; @@ -77,7 +81,7 @@ namespace realm { Predicate parse(const std::string &query); - void analyzeGrammer(); + void analyzeGrammar(); bool testGrammer(); } } From d5f56540b7b67a0a7d4a122b491b36dbb0f4b5e4 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 23 Nov 2015 08:56:36 -0800 Subject: [PATCH 038/207] pr fixes --- parser/parser.hpp | 2 +- parser/test.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/parser/parser.hpp b/parser/parser.hpp index f2119c66..f443c5f1 100644 --- a/parser/parser.hpp +++ b/parser/parser.hpp @@ -82,7 +82,7 @@ namespace realm { Predicate parse(const std::string &query); void analyzeGrammar(); - bool testGrammer(); + bool testGrammar(); } } diff --git a/parser/test.cpp b/parser/test.cpp index 973cf672..ce290474 100644 --- a/parser/test.cpp +++ b/parser/test.cpp @@ -134,7 +134,7 @@ static std::vector invalid_queries = { namespace realm { namespace parser { -bool testGrammer() +bool testGrammar() { bool success = true; for (auto &query : valid_queries) { From e05ec4ea8374323b00f7cd3d9120557e7d9fbe61 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 23 Nov 2015 11:26:50 -0800 Subject: [PATCH 039/207] latest from object store branch --- object_store.cpp | 21 +-- object_store.hpp | 11 +- results.cpp | 326 +++++++++++++++++++++++++++++++++++++++++++---- results.hpp | 159 +++++++++++++++++++---- shared_realm.cpp | 11 +- shared_realm.hpp | 42 +++--- 6 files changed, 485 insertions(+), 85 deletions(-) diff --git a/object_store.cpp b/object_store.cpp index 3e32b98c..3aed85c5 100644 --- a/object_store.cpp +++ b/object_store.cpp @@ -30,6 +30,7 @@ using namespace realm; +namespace { const char * const c_metadataTableName = "metadata"; const char * const c_versionColumnName = "version"; const size_t c_versionColumnIndex = 0; @@ -42,8 +43,8 @@ const size_t c_primaryKeyPropertyNameColumnIndex = 1; const size_t c_zeroRowIndex = 0; -const std::string c_object_table_prefix = "class_"; -const size_t c_object_table_prefix_length = c_object_table_prefix.length(); +const char c_object_table_prefix[] = "class_"; +} const uint64_t ObjectStore::NotVersioned = std::numeric_limits::max(); @@ -119,15 +120,15 @@ void ObjectStore::set_primary_key_for_object(Group *group, StringData object_typ } } -std::string ObjectStore::object_type_for_table_name(const std::string &table_name) { - if (table_name.size() >= c_object_table_prefix_length && table_name.compare(0, c_object_table_prefix_length, c_object_table_prefix) == 0) { - return table_name.substr(c_object_table_prefix_length, table_name.length() - c_object_table_prefix_length); +StringData ObjectStore::object_type_for_table_name(StringData table_name) { + if (table_name.begins_with(c_object_table_prefix)) { + return table_name.substr(sizeof(c_object_table_prefix) - 1); } - return std::string(); + return StringData(); } -std::string ObjectStore::table_name_for_object_type(const std::string &object_type) { - return c_object_table_prefix + object_type; +std::string ObjectStore::table_name_for_object_type(StringData object_type) { + return std::string(c_object_table_prefix) + object_type.data(); } TableRef ObjectStore::table_for_object_type(Group *group, StringData object_type) { @@ -138,7 +139,7 @@ ConstTableRef ObjectStore::table_for_object_type(const Group *group, StringData return group->get_table(table_name_for_object_type(object_type)); } -TableRef ObjectStore::table_for_object_type_create_if_needed(Group *group, const StringData &object_type, bool &created) { +TableRef ObjectStore::table_for_object_type_create_if_needed(Group *group, StringData object_type, bool &created) { return group->get_or_add_table(table_name_for_object_type(object_type), &created); } @@ -494,7 +495,7 @@ void ObjectStore::validate_primary_column_uniqueness(const Group *group, Schema } } -void ObjectStore::delete_data_for_object(Group *group, const StringData &object_type) { +void ObjectStore::delete_data_for_object(Group *group, StringData object_type) { TableRef table = table_for_object_type(group, object_type); if (table) { group->remove_table(table->get_index_in_group()); diff --git a/object_store.hpp b/object_store.hpp index e038604c..d41ec3fd 100644 --- a/object_store.hpp +++ b/object_store.hpp @@ -19,7 +19,6 @@ #ifndef REALM_OBJECT_STORE_HPP #define REALM_OBJECT_STORE_HPP -#include "schema.hpp" #include "object_schema.hpp" #include "property.hpp" @@ -30,6 +29,7 @@ namespace realm { class ObjectSchemaValidationException; + class Schema; class ObjectStore { public: @@ -68,11 +68,14 @@ namespace realm { static Schema schema_from_group(const Group *group); // deletes the table for the given type - static void delete_data_for_object(Group *group, const StringData &object_type); + static void delete_data_for_object(Group *group, StringData object_type); // indicates if this group contains any objects static bool is_empty(const Group *group); + static std::string table_name_for_object_type(StringData class_name); + static StringData object_type_for_table_name(StringData table_name); + private: // set a new schema version static void set_schema_version(Group *group, uint64_t version); @@ -102,9 +105,7 @@ namespace realm { // must be in write transaction to set static void set_primary_key_for_object(Group *group, StringData object_type, StringData primary_key); - static TableRef table_for_object_type_create_if_needed(Group *group, const StringData &object_type, bool &created); - static std::string table_name_for_object_type(const std::string &class_name); - static std::string object_type_for_table_name(const std::string &table_name); + static TableRef table_for_object_type_create_if_needed(Group *group, StringData object_type, bool &created); // returns if any indexes were changed static bool update_indexes(Group *group, Schema &schema); diff --git a/results.cpp b/results.cpp index 1977e5be..f361e6f5 100644 --- a/results.cpp +++ b/results.cpp @@ -3,42 +3,318 @@ */ #include "results.hpp" -#import + +#include using namespace realm; -Results::Results(SharedRealm &r, ObjectSchema &o, Query q, SortOrder s) : - realm(r), object_schema(o), backing_query(q), table_view(backing_query.find_all()) +#ifdef __has_cpp_attribute +#define REALM_HAS_CCP_ATTRIBUTE(attr) __has_cpp_attribute(attr) +#else +#define REALM_HAS_CCP_ATTRIBUTE(attr) 0 +#endif + +#if REALM_HAS_CCP_ATTRIBUTE(clang::fallthrough) +#define REALM_FALLTHROUGH [[clang::fallthrough]] +#else +#define REALM_FALLTHROUGH +#endif + +Results::Results(SharedRealm r, Query q, SortOrder s) +: m_realm(std::move(r)) +, m_query(std::move(q)) +, m_table(m_query.get_table().get()) +, m_sort(std::move(s)) +, m_mode(Mode::Query) { - setSort(std::move(s)); +} + +Results::Results(SharedRealm r, Table& table) +: m_realm(std::move(r)) +, m_table(&table) +, m_mode(Mode::Table) +{ +} + +void Results::validate_read() const +{ + if (m_realm) + m_realm->verify_thread(); + if (m_table && !m_table->is_attached()) + throw InvalidatedException(); +} + +void Results::validate_write() const +{ + validate_read(); + if (!m_realm || !m_realm->is_in_transaction()) + throw InvalidTransactionException("Must be in a write transaction"); } size_t Results::size() { - verify_attached(); - return table_view.size(); -} - -void Results::setSort(SortOrder s) -{ - sort_order = std::make_unique(std::move(s)); - table_view.sort(sort_order->columnIndices, sort_order->ascending); -} - -Row Results::get(std::size_t row_ndx) -{ - verify_attached(); - if (row_ndx >= table_view.size()) { - throw std::out_of_range(std::string("Index ") + std::to_string(row_ndx) + " is outside of range 0..." + - std::to_string(table_view.size()) + "."); + validate_read(); + switch (m_mode) { + case Mode::Empty: return 0; + case Mode::Table: return m_table->size(); + case Mode::Query: return m_query.count(); + case Mode::TableView: + update_tableview(); + return m_table_view.size(); } - return table_view.get(row_ndx); + REALM_UNREACHABLE(); } -void Results::verify_attached() +RowExpr Results::get(size_t row_ndx) { - if (!table_view.is_attached()) { - throw std::runtime_error("Tableview is not attached"); + validate_read(); + switch (m_mode) { + case Mode::Empty: break; + case Mode::Table: + if (row_ndx < m_table->size()) + return m_table->get(row_ndx); + break; + case Mode::Query: + case Mode::TableView: + update_tableview(); + if (row_ndx < m_table_view.size()) + return m_table_view.get(row_ndx); + break; } - table_view.sync_if_needed(); + + throw OutOfBoundsIndexException{row_ndx, size()}; +} + +util::Optional Results::first() +{ + validate_read(); + switch (m_mode) { + case Mode::Empty: + return none; + case Mode::Table: + return m_table->size() == 0 ? util::none : util::make_optional(m_table->front()); + case Mode::Query: + update_tableview(); + REALM_FALLTHROUGH; + case Mode::TableView: + return m_table_view.size() == 0 ? util::none : util::make_optional(m_table_view.front()); + } + REALM_UNREACHABLE(); +} + +util::Optional Results::last() +{ + validate_read(); + switch (m_mode) { + case Mode::Empty: + return none; + case Mode::Table: + return m_table->size() == 0 ? util::none : util::make_optional(m_table->back()); + case Mode::Query: + update_tableview(); + REALM_FALLTHROUGH; + case Mode::TableView: + return m_table_view.size() == 0 ? util::none : util::make_optional(m_table_view.back()); + } + REALM_UNREACHABLE(); +} + +void Results::update_tableview() +{ + validate_read(); + switch (m_mode) { + case Mode::Empty: + case Mode::Table: + return; + case Mode::Query: + m_table_view = m_query.find_all(); + if (m_sort) { + m_table_view.sort(m_sort.columnIndices, m_sort.ascending); + } + m_mode = Mode::TableView; + break; + case Mode::TableView: + m_table_view.sync_if_needed(); + break; + } +} + +size_t Results::index_of(Row const& row) +{ + validate_read(); + if (!row) { + throw DetatchedAccessorException{}; + } + if (m_table && row.get_table() != m_table) { + throw IncorrectTableException{ + ObjectStore::object_type_for_table_name(m_table->get_name()), + ObjectStore::object_type_for_table_name(row.get_table()->get_name())}; + } + return index_of(row.get_index()); +} + +size_t Results::index_of(size_t row_ndx) +{ + validate_read(); + switch (m_mode) { + case Mode::Empty: + return not_found; + case Mode::Table: + return row_ndx; + case Mode::Query: + if (!m_sort) + return m_query.count(row_ndx, row_ndx + 1) ? m_query.count(0, row_ndx) : not_found; + REALM_FALLTHROUGH; + case Mode::TableView: + update_tableview(); + return m_table_view.find_by_source_ndx(row_ndx); + } + REALM_UNREACHABLE(); +} + +template +util::Optional Results::aggregate(size_t column, bool return_none_for_empty, + Int agg_int, Float agg_float, + Double agg_double, DateTime agg_datetime) +{ + validate_read(); + if (!m_table) + return none; + if (column > m_table->get_column_count()) + throw OutOfBoundsIndexException{column, m_table->get_column_count()}; + + auto do_agg = [&](auto const& getter) -> util::Optional { + switch (m_mode) { + case Mode::Empty: + return none; + case Mode::Table: + if (return_none_for_empty && m_table->size() == 0) + return none; + return util::Optional(getter(*m_table)); + case Mode::Query: + case Mode::TableView: + this->update_tableview(); + if (return_none_for_empty && m_table_view.size() == 0) + return none; + return util::Optional(getter(m_table_view)); + } + REALM_UNREACHABLE(); + }; + + switch (m_table->get_column_type(column)) + { + case type_DateTime: return do_agg(agg_datetime); + case type_Double: return do_agg(agg_double); + case type_Float: return do_agg(agg_float); + case type_Int: return do_agg(agg_int); + default: + throw UnsupportedColumnTypeException{column, m_table}; + } +} + +util::Optional Results::max(size_t column) +{ + return aggregate(column, true, + [=](auto const& table) { return table.maximum_int(column); }, + [=](auto const& table) { return table.maximum_float(column); }, + [=](auto const& table) { return table.maximum_double(column); }, + [=](auto const& table) { return table.maximum_datetime(column); }); +} + +util::Optional Results::min(size_t column) +{ + return aggregate(column, true, + [=](auto const& table) { return table.minimum_int(column); }, + [=](auto const& table) { return table.minimum_float(column); }, + [=](auto const& table) { return table.minimum_double(column); }, + [=](auto const& table) { return table.minimum_datetime(column); }); +} + +util::Optional Results::sum(size_t column) +{ + return aggregate(column, false, + [=](auto const& table) { return table.sum_int(column); }, + [=](auto const& table) { return table.sum_float(column); }, + [=](auto const& table) { return table.sum_double(column); }, + [=](auto const&) -> util::None { throw UnsupportedColumnTypeException{column, m_table}; }); +} + +util::Optional Results::average(size_t column) +{ + return aggregate(column, true, + [=](auto const& table) { return table.average_int(column); }, + [=](auto const& table) { return table.average_float(column); }, + [=](auto const& table) { return table.average_double(column); }, + [=](auto const&) -> util::None { throw UnsupportedColumnTypeException{column, m_table}; }); +} + +void Results::clear() +{ + switch (m_mode) { + case Mode::Empty: + return; + case Mode::Table: + validate_write(); + m_table->clear(); + break; + case Mode::Query: + // Not using Query:remove() because building the tableview and + // clearing it is actually significantly faster + case Mode::TableView: + validate_write(); + update_tableview(); + m_table_view.clear(); + break; + } +} + +Query Results::get_query() const +{ + validate_read(); + switch (m_mode) { + case Mode::Empty: + case Mode::Query: + case Mode::TableView: + return m_query; + case Mode::Table: + return m_table->where(); + } + REALM_UNREACHABLE(); +} + +TableView Results::get_tableview() +{ + validate_read(); + switch (m_mode) { + case Mode::Empty: + return {}; + case Mode::Query: + case Mode::TableView: + update_tableview(); + return m_table_view; + case Mode::Table: + return m_table->where().find_all(); + } + REALM_UNREACHABLE(); +} + +StringData Results::get_object_type() const noexcept +{ + return ObjectStore::object_type_for_table_name(m_table->get_name()); +} + +Results Results::sort(realm::SortOrder&& sort) const +{ + return Results(m_realm, get_query(), std::move(sort)); +} + +Results Results::filter(Query&& q) const +{ + return Results(m_realm, get_query().and_query(std::move(q)), get_sort()); +} + +Results::UnsupportedColumnTypeException::UnsupportedColumnTypeException(size_t column, const Table* table) { + column_index = column; + column_name = table->get_column_name(column); + column_type = table->get_column_type(column); } diff --git a/results.hpp b/results.hpp index fff37124..bf9761a3 100644 --- a/results.hpp +++ b/results.hpp @@ -5,35 +5,150 @@ #ifndef REALM_RESULTS_HPP #define REALM_RESULTS_HPP -#import "shared_realm.hpp" -#import +#include "shared_realm.hpp" + +#include +#include +#include namespace realm { - struct SortOrder { - std::vector columnIndices; - std::vector ascending; +template class BasicRowExpr; +using RowExpr = BasicRowExpr
; +class Mixed; - explicit operator bool() const { - return !columnIndices.empty(); - } +struct SortOrder { + std::vector columnIndices; + std::vector ascending; + + explicit operator bool() const + { + return !columnIndices.empty(); + } +}; + +class Results { +public: + // Results can be either be backed by nothing, a thin wrapper around a table, + // or a wrapper around a query and a sort order which creates and updates + // the tableview as needed + Results() = default; + Results(SharedRealm r, Table& table); + Results(SharedRealm r, Query q, SortOrder s = {}); + + // Results is copyable and moveable + Results(Results const&) = default; + Results(Results&&) = default; + Results& operator=(Results const&) = default; + Results& operator=(Results&&) = default; + + // Get a query which will match the same rows as is contained in this Results + // Returned query will not be valid if the current mode is Empty + Query get_query() const; + + // Get the currently applied sort order for this Results + SortOrder const& get_sort() const noexcept { return m_sort; } + + // Get a tableview containing the same rows as this Results + TableView get_tableview(); + + // Get the object type which will be returned by get() + StringData get_object_type() const noexcept; + + // Get the size of this results + // Can be either O(1) or O(N) depending on the state of things + size_t size(); + + // Get the row accessor for the given index + // Throws OutOfBoundsIndexException if index >= size() + RowExpr get(size_t index); + + // Get a row accessor for the first/last row, or none if the results are empty + // More efficient than calling size()+get() + util::Optional first(); + util::Optional last(); + + // Get the first index of the given row in this results, or not_found + // Throws DetachedAccessorException if row is not attached + // Throws IncorrectTableException if row belongs to a different table + size_t index_of(size_t row_ndx); + size_t index_of(Row const& row); + + // Delete all of the rows in this Results from the Realm + // size() will always be zero afterwards + // Throws InvalidTransactionException if not in a write transaction + void clear(); + + // Create a new Results by further filtering or sorting this Results + Results filter(Query&& q) const; + Results sort(SortOrder&& sort) const; + + // Get the min/max/average/sum of the given column + // All but sum() returns none when there are zero matching rows + // sum() returns 0, except for when it returns none + // Throws UnsupportedColumnTypeException for sum/average on datetime or non-numeric column + // Throws OutOfBoundsIndexException for an out-of-bounds column + util::Optional max(size_t column); + util::Optional min(size_t column); + util::Optional average(size_t column); + util::Optional sum(size_t column); + + enum class Mode { + Empty, // Backed by nothing (for missing tables) + Table, // Backed directly by a Table + Query, // Backed by a query that has not yet been turned into a TableView + TableView // Backed by a TableView created from a Query + }; + // Get the currrent mode of the Results + // Ideally this would not be public but it's needed for some KVO stuff + Mode get_mode() const { return m_mode; } + + // The Results object has been invalidated (due to the Realm being invalidated) + // All non-noexcept functions can throw this + struct InvalidatedException {}; + + // The input index parameter was out of bounds + struct OutOfBoundsIndexException { + size_t requested; + size_t valid_count; }; - static SortOrder s_defaultSort = {{}, {}}; + // The input Row object is not attached + struct DetatchedAccessorException { }; - struct Results { - Results(SharedRealm &r, ObjectSchema &o, Query q, SortOrder s = s_defaultSort); - size_t size(); - Row get(std::size_t row_ndx); - void verify_attached(); - - SharedRealm realm; - ObjectSchema &object_schema; - Query backing_query; - TableView table_view; - std::unique_ptr sort_order; - - void setSort(SortOrder s); + // The input Row object belongs to a different table + struct IncorrectTableException { + StringData expected; + StringData actual; }; + + // The requested aggregate operation is not supported for the column type + struct UnsupportedColumnTypeException { + size_t column_index; + StringData column_name; + DataType column_type; + + UnsupportedColumnTypeException(size_t column, const Table* table); + }; + +private: + SharedRealm m_realm; + Query m_query; + TableView m_table_view; + Table* m_table = nullptr; + SortOrder m_sort; + + Mode m_mode = Mode::Empty; + + void validate_read() const; + void validate_write() const; + + void update_tableview(); + + template + util::Optional aggregate(size_t column, bool return_none_for_empty, + Int agg_int, Float agg_float, + Double agg_double, DateTime agg_datetime); +}; } #endif /* REALM_RESULTS_HPP */ diff --git a/shared_realm.cpp b/shared_realm.cpp index 9c0a4e1e..28bb81b6 100644 --- a/shared_realm.cpp +++ b/shared_realm.cpp @@ -47,6 +47,8 @@ Realm::Config::Config(const Config& c) } } +Realm::Config::Config() = default; +Realm::Config::Config(Config&&) = default; Realm::Config::~Config() = default; Realm::Config& Realm::Config::operator=(realm::Realm::Config const& c) @@ -237,7 +239,14 @@ static void check_read_write(Realm *realm) void Realm::verify_thread() const { if (m_thread_id != std::this_thread::get_id()) { - throw IncorrectThreadException("Realm accessed from incorrect thread."); + throw IncorrectThreadException(); + } +} + +void Realm::verify_in_write() const +{ + if (!is_in_transaction()) { + throw InvalidTransactionException("Cannot modify persisted objects outside of a write transaction."); } } diff --git a/shared_realm.hpp b/shared_realm.hpp index 81b14bf7..3a31d878 100644 --- a/shared_realm.hpp +++ b/shared_realm.hpp @@ -19,7 +19,6 @@ #ifndef REALM_REALM_HPP #define REALM_REALM_HPP -#include #include #include #include @@ -56,8 +55,8 @@ namespace realm { MigrationFunction migration_function; - Config() = default; - Config(Config&&) = default; + Config(); + Config(Config&&); Config(const Config& c); ~Config(); @@ -100,6 +99,11 @@ namespace realm { std::thread::id thread_id() const { return m_thread_id; } void verify_thread() const; + void verify_in_write() const; + + // Close this Realm and remove it from the cache. Continuing to use a + // Realm after closing it will produce undefined behavior. + void close(); // Close this Realm and remove it from the cache. Continuing to use a // Realm after closing it will produce undefined behavior. @@ -145,11 +149,9 @@ namespace realm { std::mutex m_mutex; }; - class RealmFileException : public std::runtime_error - { - public: - enum class Kind - { + class RealmFileException : public std::runtime_error { + public: + enum class Kind { /** Thrown for any I/O related exception scenarios when a realm is opened. */ AccessError, /** Thrown if the user does not have permission to open or create @@ -169,32 +171,28 @@ namespace realm { Kind kind() const { return m_kind; } const std::string& path() const { return m_path; } - private: + private: Kind m_kind; std::string m_path; }; - class MismatchedConfigException : public std::runtime_error - { - public: + class MismatchedConfigException : public std::runtime_error { + public: MismatchedConfigException(std::string message) : std::runtime_error(message) {} }; - class InvalidTransactionException : public std::runtime_error - { - public: + class InvalidTransactionException : public std::runtime_error { + public: InvalidTransactionException(std::string message) : std::runtime_error(message) {} }; - class IncorrectThreadException : public std::runtime_error - { - public: - IncorrectThreadException(std::string message) : std::runtime_error(message) {} + class IncorrectThreadException : public std::runtime_error { + public: + IncorrectThreadException() : std::runtime_error("Realm accessed from incorrect thread.") {} }; - class UnitializedRealmException : public std::runtime_error - { - public: + class UnitializedRealmException : public std::runtime_error { + public: UnitializedRealmException(std::string message) : std::runtime_error(message) {} }; } From fa0ba53579ded98d0d977929981040da961d04a7 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 23 Nov 2015 19:00:31 -0800 Subject: [PATCH 040/207] fix for reload in example --- shared_realm.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/shared_realm.cpp b/shared_realm.cpp index 9c0a4e1e..2b2f61c3 100644 --- a/shared_realm.cpp +++ b/shared_realm.cpp @@ -376,8 +376,6 @@ uint64_t Realm::get_schema_version(const realm::Realm::Config &config) void Realm::close() { - invalidate(); - if (m_notifier) { m_notifier->remove_realm(this); } From 054f185c1b6f25d782caf26561025261f25ad297 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 24 Nov 2015 10:39:42 -0800 Subject: [PATCH 041/207] compount parser tests --- parser/queryTests.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/parser/queryTests.json b/parser/queryTests.json index f9a5f56f..75daa28f 100644 --- a/parser/queryTests.json +++ b/parser/queryTests.json @@ -245,6 +245,30 @@ ["QueryThrows", "LinkObject", "linkCol > $0", [0, "linkCol"]], ["QueryThrows", "LinkObject", "intCol = $0", [0, "linkCol"]] ] +}, + +"compoundTests" : { + "schema" : [ + { "name": "IntObject", + "properties": [{ "name": "intCol", "type": "int" }], + "primaryKey" : "intCol" } + ], + "objects": [ + { "type": "IntObject", "value": [0] }, + { "type": "IntObject", "value": [1] }, + { "type": "IntObject", "value": [2] }, + { "type": "IntObject", "value": [3] } + ], + "tests": [ + ["ObjectSet", [], "IntObject", "intCol == 0 && intCol == 1"], + ["ObjectSet", [0, 1], "IntObject", "intCol == 0 || intCol == 1"], + ["ObjectSet", [0], "IntObject", "intCol == 0 && intCol != 1"], + ["ObjectSet", [2, 3], "IntObject", "intCol >= 2 && intCol < 4"], + ["ObjectSet", [0], "IntObject", "intCol == 0 && NOT intCol != 0"], + ["ObjectSet", [1], "IntObject", "(intCol == 0 || intCol == 1) && intCol >= 1"], + ["ObjectSet", [0, 1], "IntObject", "intCol == 0 || (intCol == 1 && intCol >= 1)"], + ["ObjectSet", [0, 1], "IntObject", "intCol == 0 || intCol == 1 && intCol >= 1"] + ] } } From 4b9af98a8158add846150102a3d6395477d1d071 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 24 Nov 2015 11:18:03 -0800 Subject: [PATCH 042/207] fix for mixed && and || queries --- parser/parser.cpp | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/parser/parser.cpp b/parser/parser.cpp index 98c81ac5..9765eeb7 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -107,27 +107,37 @@ struct pred : seq< and_pred, star< or_ext > > {}; struct ParserState { std::vector predicate_stack; - Predicate ¤t() { + Predicate ¤t() + { return *predicate_stack.back(); } - + + bool current_is_compound() + { + return current().type == Predicate::Type::And || current().type == Predicate::Type::Or; + } + bool negate_next = false; - + void addExpression(Expression && exp) { - if (current().type == Predicate::Type::Comparison) { - current().cmpr.expr[1] = std::move(exp); + Predicate &cur = current(); + if (cur.type == Predicate::Type::Comparison) { + cur.cmpr.expr[1] = std::move(exp); predicate_stack.pop_back(); } else { + assert(current_is_compound()); + Predicate p(Predicate::Type::Comparison); p.cmpr.expr[0] = std::move(exp); if (negate_next) { p.negate = true; negate_next = false; } - current().cpnd.sub_predicates.emplace_back(std::move(p)); - predicate_stack.push_back(¤t().cpnd.sub_predicates.back()); + + cur.cpnd.sub_predicates.emplace_back(std::move(p)); + predicate_stack.push_back(&cur.cpnd.sub_predicates.back()); } } }; @@ -147,6 +157,7 @@ template<> struct action< and_ext > static void apply( const input & in, ParserState & state ) { DEBUG_PRINT_TOKEN(""); + assert(state.current_is_compound()); // if we were put into an OR group we need to rearrange auto ¤t = state.current(); @@ -177,6 +188,7 @@ template<> struct action< or_ext > static void apply( const input & in, ParserState & state ) { DEBUG_PRINT_TOKEN(""); + assert(state.current_is_compound()); // if already an OR group do nothing auto ¤t = state.current(); @@ -186,15 +198,17 @@ template<> struct action< or_ext > // if only two predicates in the group, then convert to OR auto &sub_preds = state.current().cpnd.sub_predicates; - if (sub_preds.size()) { + assert(sub_preds.size() > 1); + if (sub_preds.size() == 2) { current.type = Predicate::Type::Or; return; } // split the current group into to groups which are ORed together Predicate pred1(Predicate::Type::And), pred2(Predicate::Type::And); - pred1.cpnd.sub_predicates.insert(sub_preds.begin(), sub_preds.back()); - pred2.cpnd.sub_predicates.push_back(std::move(sub_preds.back())); + std::vector::iterator second_last = sub_preds.end() - 2; + pred1.cpnd.sub_predicates.insert(pred1.cpnd.sub_predicates.begin(), sub_preds.begin(), second_last); + pred2.cpnd.sub_predicates.insert(pred2.cpnd.sub_predicates.begin(), second_last, sub_preds.end()); current.type = Predicate::Type::Or; sub_preds.clear(); From 295b378e7ffa60b3962c3aaec16e1a9c4a082ebe Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 25 Nov 2015 12:49:31 -0800 Subject: [PATCH 043/207] process compound operators in the correct order --- parser/parser.cpp | 173 +++++++++++++++++++++-------------------- parser/parser.hpp | 4 +- parser/queryTests.json | 3 +- 3 files changed, 91 insertions(+), 89 deletions(-) diff --git a/parser/parser.cpp b/parser/parser.cpp index 9765eeb7..b9b0373d 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -106,38 +106,91 @@ struct pred : seq< and_pred, star< or_ext > > {}; // state struct ParserState { - std::vector predicate_stack; - Predicate ¤t() + std::vector group_stack; + + Predicate *current_group() { - return *predicate_stack.back(); + return group_stack.back(); } - bool current_is_compound() + Predicate *last_predicate() { - return current().type == Predicate::Type::And || current().type == Predicate::Type::Or; + Predicate *pred = current_group(); + while (pred->type != Predicate::Type::Comparison && pred->cpnd.sub_predicates.size()) { + pred = &pred->cpnd.sub_predicates.back(); + } + return pred; + } + + void add_predicate_to_current_group(Predicate::Type type) + { + current_group()->cpnd.sub_predicates.emplace_back(type, negate_next); + negate_next = false; + + if (current_group()->cpnd.sub_predicates.size() > 1) { + if (next_type == Predicate::Type::Or) { + apply_or(); + } + else { + apply_and(); + } + } } bool negate_next = false; + Predicate::Type next_type = Predicate::Type::And; - void addExpression(Expression && exp) + void add_expression(Expression && exp) { - Predicate &cur = current(); - if (cur.type == Predicate::Type::Comparison) { - cur.cmpr.expr[1] = std::move(exp); - predicate_stack.pop_back(); + Predicate *current = last_predicate(); + if (current->type == Predicate::Type::Comparison && current->cmpr.expr[1].type == parser::Expression::Type::None) { + current->cmpr.expr[1] = std::move(exp); } else { - assert(current_is_compound()); - - Predicate p(Predicate::Type::Comparison); - p.cmpr.expr[0] = std::move(exp); - if (negate_next) { - p.negate = true; - negate_next = false; - } + add_predicate_to_current_group(Predicate::Type::Comparison); + last_predicate()->cmpr.expr[0] = std::move(exp); + } + } + + void apply_or() + { + Predicate &group = *group_stack.back(); + if (group.type == Predicate::Type::Or) { + return; + } + + // convert to OR + group.type = Predicate::Type::Or; + if (group.cpnd.sub_predicates.size() > 2) { + // split the current group into an AND group ORed with the last subpredicate + Predicate new_sub(Predicate::Type::And); + new_sub.cpnd.sub_predicates = std::move(group.cpnd.sub_predicates); - cur.cpnd.sub_predicates.emplace_back(std::move(p)); - predicate_stack.push_back(&cur.cpnd.sub_predicates.back()); + group.cpnd.sub_predicates = { new_sub, std::move(new_sub.cpnd.sub_predicates.back()) }; + group.cpnd.sub_predicates[0].cpnd.sub_predicates.pop_back(); + } + } + + void apply_and() + { + if (current_group()->type == Predicate::Type::And) { + return; + } + + auto &sub_preds = current_group()->cpnd.sub_predicates; + auto second_last = sub_preds.end() - 2; + if (second_last->type == Predicate::Type::And) { + // if we are in an OR group and second to last predicate group is + // an AND group then move the last predicate inside + second_last->cpnd.sub_predicates.push_back(std::move(sub_preds.back())); + sub_preds.pop_back(); + } + else { + // otherwise combine last two into a new AND group + Predicate pred(Predicate::Type::And); + pred.cpnd.sub_predicates.insert(pred.cpnd.sub_predicates.begin(), second_last, sub_preds.end()); + sub_preds.erase(second_last, sub_preds.end()); + sub_preds.emplace_back(std::move(pred)); } } }; @@ -152,68 +205,21 @@ struct action : nothing< Rule > {}; #define DEBUG_PRINT_TOKEN(string) #endif -template<> struct action< and_ext > +template<> struct action< and_op > { static void apply( const input & in, ParserState & state ) { DEBUG_PRINT_TOKEN(""); - assert(state.current_is_compound()); - - // if we were put into an OR group we need to rearrange - auto ¤t = state.current(); - if (current.type == Predicate::Type::Or) { - auto &sub_preds = current.cpnd.sub_predicates; - auto &second_last = sub_preds[sub_preds.size()-2]; - if (second_last.type == Predicate::Type::And) { - // if we are in an OR group and second to last predicate group is - // an AND group then move the last predicate inside - second_last.cpnd.sub_predicates.push_back(std::move(sub_preds.back())); - sub_preds.pop_back(); - } - else { - // otherwise combine last two into a new AND group - Predicate pred(Predicate::Type::And); - pred.cpnd.sub_predicates.emplace_back(std::move(second_last)); - pred.cpnd.sub_predicates.emplace_back(std::move(sub_preds.back())); - sub_preds.pop_back(); - sub_preds.pop_back(); - sub_preds.push_back(std::move(pred)); - } - } + state.next_type = Predicate::Type::And; } }; -template<> struct action< or_ext > +template<> struct action< or_op > { static void apply( const input & in, ParserState & state ) { DEBUG_PRINT_TOKEN(""); - assert(state.current_is_compound()); - - // if already an OR group do nothing - auto ¤t = state.current(); - if (current.type == Predicate::Type::Or) { - return; - } - - // if only two predicates in the group, then convert to OR - auto &sub_preds = state.current().cpnd.sub_predicates; - assert(sub_preds.size() > 1); - if (sub_preds.size() == 2) { - current.type = Predicate::Type::Or; - return; - } - - // split the current group into to groups which are ORed together - Predicate pred1(Predicate::Type::And), pred2(Predicate::Type::And); - std::vector::iterator second_last = sub_preds.end() - 2; - pred1.cpnd.sub_predicates.insert(pred1.cpnd.sub_predicates.begin(), sub_preds.begin(), second_last); - pred2.cpnd.sub_predicates.insert(pred2.cpnd.sub_predicates.begin(), second_last, sub_preds.end()); - - current.type = Predicate::Type::Or; - sub_preds.clear(); - sub_preds.emplace_back(std::move(pred1)); - sub_preds.emplace_back(std::move(pred2)); + state.next_type = Predicate::Type::Or; } }; @@ -222,7 +228,7 @@ template<> struct action< or_ext > template<> struct action< rule > { \ static void apply( const input & in, ParserState & state ) { \ DEBUG_PRINT_TOKEN(in.string()); \ - state.addExpression(Expression(type, in.string())); }}; + state.add_expression(Expression(type, in.string())); }}; EXPRESSION_ACTION(dq_string_content, Expression::Type::String) EXPRESSION_ACTION(sq_string_content, Expression::Type::String) @@ -238,7 +244,7 @@ template<> struct action< true_pred > static void apply( const input & in, ParserState & state ) { DEBUG_PRINT_TOKEN(in.string()); - state.current().cpnd.sub_predicates.emplace_back(Predicate::Type::True); + state.current_group()->cpnd.sub_predicates.emplace_back(Predicate::Type::True); } }; @@ -247,7 +253,7 @@ template<> struct action< false_pred > static void apply( const input & in, ParserState & state ) { DEBUG_PRINT_TOKEN(in.string()); - state.current().cpnd.sub_predicates.emplace_back(Predicate::Type::False); + state.current_group()->cpnd.sub_predicates.emplace_back(Predicate::Type::False); } }; @@ -255,7 +261,7 @@ template<> struct action< false_pred > template<> struct action< rule > { \ static void apply( const input & in, ParserState & state ) { \ DEBUG_PRINT_TOKEN(in.string()); \ - state.current().cmpr.op = oper; }}; + state.last_predicate()->cmpr.op = oper; }}; OPERATOR_ACTION(eq, Predicate::Operator::Equal) OPERATOR_ACTION(noteq, Predicate::Operator::NotEqual) @@ -272,15 +278,8 @@ template<> struct action< one< '(' > > static void apply( const input & in, ParserState & state ) { DEBUG_PRINT_TOKEN(""); - - Predicate group(Predicate::Type::And); - if (state.negate_next) { - group.negate = true; - state.negate_next = false; - } - - state.current().cpnd.sub_predicates.emplace_back(std::move(group)); - state.predicate_stack.push_back(&state.current().cpnd.sub_predicates.back()); + state.add_predicate_to_current_group(Predicate::Type::And); + state.group_stack.push_back(state.last_predicate()); } }; @@ -289,7 +288,7 @@ template<> struct action< group_pred > static void apply( const input & in, ParserState & state ) { DEBUG_PRINT_TOKEN(""); - state.predicate_stack.pop_back(); + state.group_stack.pop_back(); } }; @@ -322,10 +321,12 @@ const std::string error_message_control< Rule >::error_message = "Invalid predic Predicate parse(const std::string &query) { + DEBUG_PRINT_TOKEN(query); + Predicate out_predicate(Predicate::Type::And); ParserState state; - state.predicate_stack.push_back(&out_predicate); + state.group_stack.push_back(&out_predicate); pegtl::parse< must< pred, eof >, action, error_message_control >(query, query, state); if (out_predicate.type == Predicate::Type::And && out_predicate.cpnd.sub_predicates.size() == 1) { diff --git a/parser/parser.hpp b/parser/parser.hpp index f443c5f1..4081f3d3 100644 --- a/parser/parser.hpp +++ b/parser/parser.hpp @@ -28,7 +28,7 @@ namespace realm { namespace parser { struct Expression { - enum class Type { Number, String, KeyPath, Argument, True, False } type; + enum class Type { Number, String, KeyPath, Argument, True, False, None } type = Type::None; std::string s; Expression() {} Expression(Type t, std::string s) : type(t), s(s) {} @@ -76,7 +76,7 @@ namespace realm { bool negate = false; - Predicate(Type t) : type(t) {} + Predicate(Type t, bool n = false) : type(t), negate(n) {} }; Predicate parse(const std::string &query); diff --git a/parser/queryTests.json b/parser/queryTests.json index 75daa28f..518759cc 100644 --- a/parser/queryTests.json +++ b/parser/queryTests.json @@ -267,7 +267,8 @@ ["ObjectSet", [0], "IntObject", "intCol == 0 && NOT intCol != 0"], ["ObjectSet", [1], "IntObject", "(intCol == 0 || intCol == 1) && intCol >= 1"], ["ObjectSet", [0, 1], "IntObject", "intCol == 0 || (intCol == 1 && intCol >= 1)"], - ["ObjectSet", [0, 1], "IntObject", "intCol == 0 || intCol == 1 && intCol >= 1"] + ["ObjectSet", [0, 1], "IntObject", "intCol == 0 || intCol == 1 && intCol >= 1"], + ["ObjectSet", [0, 1], "IntObject", "intCol == 1 && intCol >= 1 || intCol == 0"] ] } From a707a728cded9bf765bd6e330433a88a65ddf427 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 25 Nov 2015 12:54:13 -0800 Subject: [PATCH 044/207] add a few more tests --- parser/queryTests.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/parser/queryTests.json b/parser/queryTests.json index 518759cc..fb280f19 100644 --- a/parser/queryTests.json +++ b/parser/queryTests.json @@ -266,9 +266,12 @@ ["ObjectSet", [2, 3], "IntObject", "intCol >= 2 && intCol < 4"], ["ObjectSet", [0], "IntObject", "intCol == 0 && NOT intCol != 0"], ["ObjectSet", [1], "IntObject", "(intCol == 0 || intCol == 1) && intCol >= 1"], + ["ObjectSet", [1], "IntObject", "intCol >= 1 && (intCol == 0 || intCol == 1)"], ["ObjectSet", [0, 1], "IntObject", "intCol == 0 || (intCol == 1 && intCol >= 1)"], + ["ObjectSet", [0, 1], "IntObject", "(intCol == 1 && intCol >= 1) || intCol == 0"], ["ObjectSet", [0, 1], "IntObject", "intCol == 0 || intCol == 1 && intCol >= 1"], - ["ObjectSet", [0, 1], "IntObject", "intCol == 1 && intCol >= 1 || intCol == 0"] + ["ObjectSet", [0, 1], "IntObject", "intCol == 1 && intCol >= 1 || intCol == 0"], + ["ObjectSet", [0, 1], "IntObject", "intCol == 1 || intCol == 0 && intCol <= 0 && intCol >= 0"] ] } From e89259c746e8f1060ef7990ba7bd74388b695541 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 25 Nov 2015 12:57:56 -0800 Subject: [PATCH 045/207] more tests --- parser/queryTests.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/parser/queryTests.json b/parser/queryTests.json index fb280f19..15f7fefa 100644 --- a/parser/queryTests.json +++ b/parser/queryTests.json @@ -265,11 +265,13 @@ ["ObjectSet", [0], "IntObject", "intCol == 0 && intCol != 1"], ["ObjectSet", [2, 3], "IntObject", "intCol >= 2 && intCol < 4"], ["ObjectSet", [0], "IntObject", "intCol == 0 && NOT intCol != 0"], + ["ObjectSet", [0, 3], "IntObject", "intCol == 0 || NOT (intCol == 1 || intCol == 2)"], ["ObjectSet", [1], "IntObject", "(intCol == 0 || intCol == 1) && intCol >= 1"], ["ObjectSet", [1], "IntObject", "intCol >= 1 && (intCol == 0 || intCol == 1)"], ["ObjectSet", [0, 1], "IntObject", "intCol == 0 || (intCol == 1 && intCol >= 1)"], ["ObjectSet", [0, 1], "IntObject", "(intCol == 1 && intCol >= 1) || intCol == 0"], ["ObjectSet", [0, 1], "IntObject", "intCol == 0 || intCol == 1 && intCol >= 1"], + ["ObjectSet", [0, 1, 2],"IntObject", "intCol == 0 || intCol == 1 || intCol <= 2"], ["ObjectSet", [0, 1], "IntObject", "intCol == 1 && intCol >= 1 || intCol == 0"], ["ObjectSet", [0, 1], "IntObject", "intCol == 1 || intCol == 0 && intCol <= 0 && intCol >= 0"] ] From be2a3fab47e79ad2b418f0f4c6229cf6d8c5b3d0 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 25 Nov 2015 18:32:13 -0800 Subject: [PATCH 046/207] don't merge predicate to negated and group --- parser/parser.cpp | 5 ++--- parser/queryTests.json | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/parser/parser.cpp b/parser/parser.cpp index b9b0373d..f478d1c5 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -179,9 +179,8 @@ struct ParserState auto &sub_preds = current_group()->cpnd.sub_predicates; auto second_last = sub_preds.end() - 2; - if (second_last->type == Predicate::Type::And) { - // if we are in an OR group and second to last predicate group is - // an AND group then move the last predicate inside + if (second_last->type == Predicate::Type::And && !second_last->negate) { + // make a new and group populated with the last two predicates second_last->cpnd.sub_predicates.push_back(std::move(sub_preds.back())); sub_preds.pop_back(); } diff --git a/parser/queryTests.json b/parser/queryTests.json index 15f7fefa..630694e8 100644 --- a/parser/queryTests.json +++ b/parser/queryTests.json @@ -273,7 +273,8 @@ ["ObjectSet", [0, 1], "IntObject", "intCol == 0 || intCol == 1 && intCol >= 1"], ["ObjectSet", [0, 1, 2],"IntObject", "intCol == 0 || intCol == 1 || intCol <= 2"], ["ObjectSet", [0, 1], "IntObject", "intCol == 1 && intCol >= 1 || intCol == 0"], - ["ObjectSet", [0, 1], "IntObject", "intCol == 1 || intCol == 0 && intCol <= 0 && intCol >= 0"] + ["ObjectSet", [0, 1], "IntObject", "intCol == 1 || intCol == 0 && intCol <= 0 && intCol >= 0"], + ["ObjectSet", [0, 1], "IntObject", "intCol == 0 || NOT (intCol == 3 && intCol >= 0) && intCol == 1"] ] } From 6ac6f3989408b54c6baefc19d1badaa35bfde4e2 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 25 Nov 2015 18:34:36 -0800 Subject: [PATCH 047/207] use current_group() helper --- parser/parser.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/parser/parser.cpp b/parser/parser.cpp index f478d1c5..2753fbd8 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -154,20 +154,20 @@ struct ParserState void apply_or() { - Predicate &group = *group_stack.back(); - if (group.type == Predicate::Type::Or) { + Predicate *group = current_group(); + if (group->type == Predicate::Type::Or) { return; } // convert to OR - group.type = Predicate::Type::Or; - if (group.cpnd.sub_predicates.size() > 2) { + group->type = Predicate::Type::Or; + if (group->cpnd.sub_predicates.size() > 2) { // split the current group into an AND group ORed with the last subpredicate Predicate new_sub(Predicate::Type::And); - new_sub.cpnd.sub_predicates = std::move(group.cpnd.sub_predicates); + new_sub.cpnd.sub_predicates = std::move(group->cpnd.sub_predicates); - group.cpnd.sub_predicates = { new_sub, std::move(new_sub.cpnd.sub_predicates.back()) }; - group.cpnd.sub_predicates[0].cpnd.sub_predicates.pop_back(); + group->cpnd.sub_predicates = { new_sub, std::move(new_sub.cpnd.sub_predicates.back()) }; + group->cpnd.sub_predicates[0].cpnd.sub_predicates.pop_back(); } } From 1faf3d21d4502ef7370a3495d9498373cc0eab86 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 25 Nov 2015 19:10:59 -0800 Subject: [PATCH 048/207] make None the first enum type --- parser/parser.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parser/parser.hpp b/parser/parser.hpp index 4081f3d3..377eda5d 100644 --- a/parser/parser.hpp +++ b/parser/parser.hpp @@ -28,7 +28,7 @@ namespace realm { namespace parser { struct Expression { - enum class Type { Number, String, KeyPath, Argument, True, False, None } type = Type::None; + enum class Type { None, Number, String, KeyPath, Argument, True, False } type = Type::None; std::string s; Expression() {} Expression(Type t, std::string s) : type(t), s(s) {} From c9405da94fc849d53e3390964acbace2b956e4ef Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 25 Nov 2015 19:57:15 -0800 Subject: [PATCH 049/207] integrate new results class --- object_accessor.hpp | 1 + results.cpp | 29 +++++++++++++---------------- results.hpp | 41 +++++++++++++++++++++++++++++------------ shared_realm.hpp | 4 ---- 4 files changed, 43 insertions(+), 32 deletions(-) diff --git a/object_accessor.hpp b/object_accessor.hpp index dfae7c16..ffa3edb6 100644 --- a/object_accessor.hpp +++ b/object_accessor.hpp @@ -7,6 +7,7 @@ #include #include "shared_realm.hpp" +#include "schema.hpp" #include "list.hpp" namespace realm { diff --git a/results.cpp b/results.cpp index f361e6f5..2f466476 100644 --- a/results.cpp +++ b/results.cpp @@ -20,19 +20,21 @@ using namespace realm; #define REALM_FALLTHROUGH #endif -Results::Results(SharedRealm r, Query q, SortOrder s) +Results::Results(SharedRealm r, const ObjectSchema &o, Query q, SortOrder s) : m_realm(std::move(r)) , m_query(std::move(q)) , m_table(m_query.get_table().get()) , m_sort(std::move(s)) , m_mode(Mode::Query) +, object_schema(o) { } -Results::Results(SharedRealm r, Table& table) +Results::Results(SharedRealm r, const ObjectSchema &o, Table& table) : m_realm(std::move(r)) , m_table(&table) , m_mode(Mode::Table) +, object_schema(o) { } @@ -146,9 +148,9 @@ size_t Results::index_of(Row const& row) throw DetatchedAccessorException{}; } if (m_table && row.get_table() != m_table) { - throw IncorrectTableException{ - ObjectStore::object_type_for_table_name(m_table->get_name()), - ObjectStore::object_type_for_table_name(row.get_table()->get_name())}; + throw IncorrectTableException(object_schema.name, + ObjectStore::object_type_for_table_name(row.get_table()->get_name()), + "Attempting to get the index of a Row of the wrong type"); } return index_of(row.get_index()); } @@ -298,23 +300,18 @@ TableView Results::get_tableview() REALM_UNREACHABLE(); } -StringData Results::get_object_type() const noexcept -{ - return ObjectStore::object_type_for_table_name(m_table->get_name()); -} - Results Results::sort(realm::SortOrder&& sort) const { - return Results(m_realm, get_query(), std::move(sort)); + return Results(m_realm, object_schema, get_query(), std::move(sort)); } Results Results::filter(Query&& q) const { - return Results(m_realm, get_query().and_query(std::move(q)), get_sort()); + return Results(m_realm, object_schema, get_query().and_query(std::move(q)), get_sort()); } -Results::UnsupportedColumnTypeException::UnsupportedColumnTypeException(size_t column, const Table* table) { - column_index = column; - column_name = table->get_column_name(column); - column_type = table->get_column_type(column); +Results::UnsupportedColumnTypeException::UnsupportedColumnTypeException(size_t column, const Table* table) : + column_index(column), column_name(table->get_column_name(column)), column_type(table->get_column_type(column)), + std::runtime_error((std::string)"Operation not supported on '" + table->get_column_name(column).data() + "' columns") +{ } diff --git a/results.hpp b/results.hpp index bf9761a3..53ee6815 100644 --- a/results.hpp +++ b/results.hpp @@ -32,8 +32,8 @@ public: // or a wrapper around a query and a sort order which creates and updates // the tableview as needed Results() = default; - Results(SharedRealm r, Table& table); - Results(SharedRealm r, Query q, SortOrder s = {}); + Results(SharedRealm r, const ObjectSchema &o, Table& table); + Results(SharedRealm r, const ObjectSchema &o, Query q, SortOrder s = {}); // Results is copyable and moveable Results(Results const&) = default; @@ -41,6 +41,12 @@ public: Results& operator=(Results const&) = default; Results& operator=(Results&&) = default; + // Get the Realm + SharedRealm get_realm() const { return m_realm; } + + // Object schema describing the vendored object type + ObjectSchema object_schema; + // Get a query which will match the same rows as is contained in this Results // Returned query will not be valid if the current mode is Empty Query get_query() const; @@ -52,7 +58,7 @@ public: TableView get_tableview(); // Get the object type which will be returned by get() - StringData get_object_type() const noexcept; + StringData get_object_type() const noexcept { return object_schema.name; } // Get the size of this results // Can be either O(1) or O(N) depending on the state of things @@ -104,25 +110,36 @@ public: // The Results object has been invalidated (due to the Realm being invalidated) // All non-noexcept functions can throw this - struct InvalidatedException {}; + struct InvalidatedException : public std::runtime_error + { + InvalidatedException() : std::runtime_error("Access to invalidated Results objects") {} + }; // The input index parameter was out of bounds - struct OutOfBoundsIndexException { - size_t requested; - size_t valid_count; + struct OutOfBoundsIndexException : public std::out_of_range + { + OutOfBoundsIndexException(size_t r, size_t c) : requested(r), valid_count(c), + std::out_of_range((std::string)"Requested index " + std::to_string(r) + + " greater than max " + std::to_string(c)) {} + const size_t requested; + const size_t valid_count; }; // The input Row object is not attached - struct DetatchedAccessorException { }; + struct DetatchedAccessorException : public std::runtime_error { + DetatchedAccessorException() : std::runtime_error("Atempting to access an invalid object") {} + }; // The input Row object belongs to a different table - struct IncorrectTableException { - StringData expected; - StringData actual; + struct IncorrectTableException : public std::runtime_error { + IncorrectTableException(StringData e, StringData a, const std::string &error) : + expected(e), actual(a), std::runtime_error(error) {} + const StringData expected; + const StringData actual; }; // The requested aggregate operation is not supported for the column type - struct UnsupportedColumnTypeException { + struct UnsupportedColumnTypeException : public std::runtime_error { size_t column_index; StringData column_name; DataType column_type; diff --git a/shared_realm.hpp b/shared_realm.hpp index 3a31d878..0503282a 100644 --- a/shared_realm.hpp +++ b/shared_realm.hpp @@ -105,10 +105,6 @@ namespace realm { // Realm after closing it will produce undefined behavior. void close(); - // Close this Realm and remove it from the cache. Continuing to use a - // Realm after closing it will produce undefined behavior. - void close(); - ~Realm(); private: From 08688753661d700c099282fac845dd8533286b36 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Fri, 27 Nov 2015 18:26:58 -0800 Subject: [PATCH 050/207] tests for keypath queries --- parser/queryTests.json | 45 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/parser/queryTests.json b/parser/queryTests.json index 630694e8..bc1d4bc3 100644 --- a/parser/queryTests.json +++ b/parser/queryTests.json @@ -229,8 +229,12 @@ "objectTests" : { "schema" : [ - { "name": "IntObject", "properties": [{ "name": "intCol", "type": "int" }] }, - { "name": "LinkObject", "properties": [{ "name": "linkCol", "type": "IntObject" }] } + { "name": "IntObject", "properties": [ + { "name": "intCol", "type": "int" } + ]}, + { "name": "LinkObject", "properties": [ + { "name": "linkCol", "type": "object", "objectType": "IntObject" } + ]} ], "objects": [ { "type": "LinkObject", "value": [[1]] }, @@ -276,6 +280,43 @@ ["ObjectSet", [0, 1], "IntObject", "intCol == 1 || intCol == 0 && intCol <= 0 && intCol >= 0"], ["ObjectSet", [0, 1], "IntObject", "intCol == 0 || NOT (intCol == 3 && intCol >= 0) && intCol == 1"] ] +}, + +"keyPathTests" : { + "schema" : [ + { + "name": "BasicTypesObject", + "properties": [ + { "name": "intCol", "type": "int" }, + { "name": "floatCol", "type": "float" }, + { "name": "doubleCol", "type": "double" }, + { "name": "stringCol", "type": "string" }, + { "name": "dateCol", "type": "date" }, + { "name": "dataCol", "type": "data" } + ] + }, + { + "name": "LinkTypesObject", + "primaryKey": "primaryKey", + "properties": [ + { "name": "primaryKey", "type": "int" }, + { "name": "basicLink", "type": "object", "objectType": "BasicTypesObject" }, + { "name": "linkLink", "type": "object", "objectType": "LinkTypesObject" } + ] + }], + "objects": [ + { "type": "LinkTypesObject", "value": [0, [1, 0.1, 0.001, "1", 1, [1, 10, 100]], null] }, + { "type": "LinkTypesObject", "value": [1, null, [2, [1, 0.1, 0.001, "1", 1, [1, 10, 100]], null]] }, + { "type": "LinkTypesObject", "value": [3, null, [4, [2, 0.2, 0.002, "2", 2, [2, 20, 200]], null]] } + ], + "tests": [ + ["ObjectSet", [0, 2], "LinkTypesObject", "basicLink.intCol == 1"], + ["ObjectSet", [1], "LinkTypesObject", "linkLink.basicLink.intCol == 1"], + ["ObjectSet", [1, 3], "LinkTypesObject", "linkLink.basicLink.intCol > 0"], + ["ObjectSet", [0, 2], "LinkTypesObject", "basicLink.floatCol == 0.1"], + ["ObjectSet", [1], "LinkTypesObject", "linkLink.basicLink.floatCol == 0.1"], + ["ObjectSet", [1, 3], "LinkTypesObject", "linkLink.basicLink.floatCol > 0"] + ] } } From 253a572ca741bb88d4fc33b0a8a4d76a4487b1df Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Wed, 16 Dec 2015 13:04:43 -0800 Subject: [PATCH 051/207] Fix crash caused by accessing invalid ObjectSchema These references would eventually become invalid. The quickest fix is for them to no longer be references. The longer-term fix might be to only store the object type and dynamically retrieve the ObjectSchema only when necessary. Fixes #181 --- list.hpp | 2 +- object_accessor.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/list.hpp b/list.hpp index fdde4c62..934c56ce 100644 --- a/list.hpp +++ b/list.hpp @@ -27,7 +27,7 @@ namespace realm { public: List(SharedRealm &r, const ObjectSchema &s, LinkViewRef l) : m_realm(r), object_schema(s), m_link_view(l) {} - const ObjectSchema &object_schema; + const ObjectSchema object_schema; SharedRealm realm() { return m_realm; } size_t size(); diff --git a/object_accessor.hpp b/object_accessor.hpp index ffa3edb6..2f52619c 100644 --- a/object_accessor.hpp +++ b/object_accessor.hpp @@ -27,7 +27,7 @@ namespace realm { template static inline Object create(ContextType ctx, SharedRealm realm, ObjectSchema &object_schema, ValueType value, bool try_update); - const ObjectSchema &object_schema; + const ObjectSchema object_schema; SharedRealm realm() { return m_realm; } Row row() { return m_row; } From dc67be30fc597298a9837a2cad10e1fb715a1010 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 17 Dec 2015 15:59:34 -0800 Subject: [PATCH 052/207] Schema stored in Realm::Config should be const --- object_accessor.hpp | 6 +++--- parser/query_builder.cpp | 12 ++++++------ parser/query_builder.hpp | 2 +- shared_realm.cpp | 14 ++++++-------- shared_realm.hpp | 4 ++-- 5 files changed, 18 insertions(+), 20 deletions(-) diff --git a/object_accessor.hpp b/object_accessor.hpp index 2f52619c..5d290d43 100644 --- a/object_accessor.hpp +++ b/object_accessor.hpp @@ -25,7 +25,7 @@ namespace realm { // create an Object from a native representation template - static inline Object create(ContextType ctx, SharedRealm realm, ObjectSchema &object_schema, ValueType value, bool try_update); + static inline Object create(ContextType ctx, SharedRealm realm, const ObjectSchema &object_schema, ValueType value, bool try_update); const ObjectSchema object_schema; SharedRealm realm() { return m_realm; } @@ -243,7 +243,7 @@ namespace realm { } template - inline Object Object::create(ContextType ctx, SharedRealm realm, ObjectSchema &object_schema, ValueType value, bool try_update) + inline Object Object::create(ContextType ctx, SharedRealm realm, const ObjectSchema &object_schema, ValueType value, bool try_update) { using Accessor = NativeAccessor; @@ -283,7 +283,7 @@ namespace realm { // populate Object object(realm, object_schema, table->get(row_index)); - for (Property &prop : object_schema.properties) { + for (const Property &prop : object_schema.properties) { if (created || !prop.is_primary) { if (Accessor::dict_has_value_for_key(ctx, value, prop.name)) { object.set_property_value_impl(ctx, prop, Accessor::dict_value_for_key(ctx, value, prop.name), try_update); diff --git a/parser/query_builder.cpp b/parser/query_builder.cpp index d7f26e07..6f74f3b2 100644 --- a/parser/query_builder.cpp +++ b/parser/query_builder.cpp @@ -66,11 +66,11 @@ KeyPath key_path_from_string(const std::string &s) { struct PropertyExpression { - Property *prop = nullptr; + const Property *prop = nullptr; std::vector indexes; std::function
table_getter; - PropertyExpression(Query &query, Schema &schema, Schema::iterator desc, const std::string &key_path_string) + PropertyExpression(Query &query, const Schema &schema, Schema::const_iterator desc, const std::string &key_path_string) { KeyPath key_path = key_path_from_string(key_path_string); for (size_t index = 0; index < key_path.size(); index++) { @@ -375,7 +375,7 @@ auto value_of_type_for_query(TableGetter&& tables, Value&& value, Arguments &arg } template -void do_add_comparison_to_query(Query &query, Schema &schema, ObjectSchema &object_schema, Predicate::Operator op, +void do_add_comparison_to_query(Query &query, const Schema &schema, const ObjectSchema &object_schema, Predicate::Operator op, PropertyExpression &expr, A &lhs, B &rhs, Arguments &args) { auto type = expr.prop->type; @@ -418,7 +418,7 @@ void do_add_comparison_to_query(Query &query, Schema &schema, ObjectSchema &obje } } -void add_comparison_to_query(Query &query, Predicate &pred, Arguments &args, Schema &schema, const std::string &type) +void add_comparison_to_query(Query &query, Predicate &pred, Arguments &args, const Schema &schema, const std::string &type) { Predicate::Comparison &cmpr = pred.cmpr; auto t0 = cmpr.expr[0].type, t1 = cmpr.expr[1].type; @@ -436,7 +436,7 @@ void add_comparison_to_query(Query &query, Predicate &pred, Arguments &args, Sch } } -void update_query_with_predicate(Query &query, Predicate &pred, Arguments &arguments, Schema &schema, const std::string &type) +void update_query_with_predicate(Query &query, Predicate &pred, Arguments &arguments, const Schema &schema, const std::string &type) { if (pred.negate) { query.Not(); @@ -484,7 +484,7 @@ void update_query_with_predicate(Query &query, Predicate &pred, Arguments &argum } } -void apply_predicate(Query &query, Predicate &predicate, Arguments &arguments, Schema &schema, std::string objectType) +void apply_predicate(Query &query, Predicate &predicate, Arguments &arguments, const Schema &schema, std::string objectType) { update_query_with_predicate(query, predicate, arguments, schema, objectType); diff --git a/parser/query_builder.hpp b/parser/query_builder.hpp index 2d7f9d7c..713ef6ac 100644 --- a/parser/query_builder.hpp +++ b/parser/query_builder.hpp @@ -30,7 +30,7 @@ namespace realm { namespace query_builder { class Arguments; - void apply_predicate(Query &query, parser::Predicate &predicate, Arguments &arguments, Schema &schema, std::string objectType); + void apply_predicate(Query &query, parser::Predicate &predicate, Arguments &arguments, const Schema &schema, std::string objectType); class Arguments { diff --git a/shared_realm.cpp b/shared_realm.cpp index d2919f0c..1dd3f068 100644 --- a/shared_realm.cpp +++ b/shared_realm.cpp @@ -165,7 +165,7 @@ SharedRealm Realm::get_shared_realm(Config config) throw UnitializedRealmException("Can't open an un-initialized Realm without a Schema"); } target_schema->validate(); - ObjectStore::verify_schema(*realm->m_config.schema, *target_schema, true); + ObjectStore::verify_schema(*realm->m_config.schema, const_cast(*target_schema), true); realm->m_config.schema = std::move(target_schema); } else { @@ -180,13 +180,13 @@ SharedRealm Realm::get_shared_realm(Config config) return realm; } -bool Realm::update_schema(std::unique_ptr schema, uint64_t version) +bool Realm::update_schema(std::unique_ptr schema, uint64_t version) { schema->validate(); bool needs_update = !m_config.read_only && (m_config.schema_version != version || ObjectStore::needs_update(*m_config.schema, *schema)); if (!needs_update) { - ObjectStore::verify_schema(*m_config.schema, *schema, m_config.read_only); + ObjectStore::verify_schema(*m_config.schema, const_cast(*schema), m_config.read_only); m_config.schema = std::move(schema); m_config.schema_version = version; return false; @@ -199,9 +199,6 @@ bool Realm::update_schema(std::unique_ptr schema, uint64_t version) old_config.read_only = true; old_config.schema = std::move(old_schema); - m_config.schema = std::move(schema); - m_config.schema_version = version; - auto migration_function = [&](Group*, Schema&) { SharedRealm old_realm(new Realm(old_config)); auto updated_realm = shared_from_this(); @@ -214,8 +211,9 @@ bool Realm::update_schema(std::unique_ptr schema, uint64_t version) // update and migrate begin_transaction(); bool changed = ObjectStore::update_realm_with_schema(read_group(), *old_config.schema, - version, *m_config.schema, - migration_function); + version, const_cast(*schema), migration_function); + m_config.schema = std::move(schema); + m_config.schema_version = version; commit_transaction(); return changed; } diff --git a/shared_realm.hpp b/shared_realm.hpp index 0503282a..49d35cf0 100644 --- a/shared_realm.hpp +++ b/shared_realm.hpp @@ -50,7 +50,7 @@ namespace realm { bool cache = true; std::vector encryption_key; - std::unique_ptr schema; + std::unique_ptr schema; uint64_t schema_version = ObjectStore::NotVersioned; MigrationFunction migration_function; @@ -78,7 +78,7 @@ namespace realm { // on the Config, and the resulting Schema and version with updated // column mappings are set on the realms config upon success. // returns if any changes were made - bool update_schema(std::unique_ptr schema, uint64_t version); + bool update_schema(std::unique_ptr schema, uint64_t version); static uint64_t get_schema_version(Config const& config); From 143564d0b998b9edd1a90ecd30b626844af6f62f Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 17 Dec 2015 17:32:51 -0800 Subject: [PATCH 053/207] results should store const ObjectSchema --- list.hpp | 2 +- object_accessor.hpp | 2 +- results.cpp | 9 +++++++++ results.hpp | 4 ++-- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/list.hpp b/list.hpp index 934c56ce..fdde4c62 100644 --- a/list.hpp +++ b/list.hpp @@ -27,7 +27,7 @@ namespace realm { public: List(SharedRealm &r, const ObjectSchema &s, LinkViewRef l) : m_realm(r), object_schema(s), m_link_view(l) {} - const ObjectSchema object_schema; + const ObjectSchema &object_schema; SharedRealm realm() { return m_realm; } size_t size(); diff --git a/object_accessor.hpp b/object_accessor.hpp index 5d290d43..ae1025f0 100644 --- a/object_accessor.hpp +++ b/object_accessor.hpp @@ -27,7 +27,7 @@ namespace realm { template static inline Object create(ContextType ctx, SharedRealm realm, const ObjectSchema &object_schema, ValueType value, bool try_update); - const ObjectSchema object_schema; + const ObjectSchema &object_schema; SharedRealm realm() { return m_realm; } Row row() { return m_row; } diff --git a/results.cpp b/results.cpp index 2f466476..e879c6b7 100644 --- a/results.cpp +++ b/results.cpp @@ -38,6 +38,15 @@ Results::Results(SharedRealm r, const ObjectSchema &o, Table& table) { } +Results& Results::operator=(Results const& r) +{ + m_realm = r.m_realm; + const_cast(object_schema) = r.object_schema; + m_query = r.get_query(); + m_sort = r.get_sort(); + return *this; +} + void Results::validate_read() const { if (m_realm) diff --git a/results.hpp b/results.hpp index 53ee6815..115ca636 100644 --- a/results.hpp +++ b/results.hpp @@ -38,14 +38,14 @@ public: // Results is copyable and moveable Results(Results const&) = default; Results(Results&&) = default; - Results& operator=(Results const&) = default; Results& operator=(Results&&) = default; + Results& operator=(Results const&); // Get the Realm SharedRealm get_realm() const { return m_realm; } // Object schema describing the vendored object type - ObjectSchema object_schema; + const ObjectSchema &object_schema; // Get a query which will match the same rows as is contained in this Results // Returned query will not be valid if the current mode is Empty From 480f4effb24788e6a53194f41f9c04ab2a005d3f Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 17 Dec 2015 18:40:26 -0800 Subject: [PATCH 054/207] fix for copy assignment --- results.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/results.cpp b/results.cpp index e879c6b7..623b8c6b 100644 --- a/results.cpp +++ b/results.cpp @@ -41,9 +41,11 @@ Results::Results(SharedRealm r, const ObjectSchema &o, Table& table) Results& Results::operator=(Results const& r) { m_realm = r.m_realm; - const_cast(object_schema) = r.object_schema; m_query = r.get_query(); + m_table = r.m_table; m_sort = r.get_sort(); + m_mode = Mode::Query; + const_cast(object_schema) = r.object_schema; return *this; } From e78e33cd98463dacbe7bbace5eeb6a01cd5a9120 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Fri, 18 Dec 2015 14:37:41 -0800 Subject: [PATCH 055/207] pr feedback --- results.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/results.cpp b/results.cpp index 623b8c6b..b2a63789 100644 --- a/results.cpp +++ b/results.cpp @@ -41,9 +41,9 @@ Results::Results(SharedRealm r, const ObjectSchema &o, Table& table) Results& Results::operator=(Results const& r) { m_realm = r.m_realm; - m_query = r.get_query(); m_table = r.m_table; - m_sort = r.get_sort(); + m_sort = r.m_sort; + m_query = r.get_query(); m_mode = Mode::Query; const_cast(object_schema) = r.object_schema; return *this; From f32de945ad8e15d4ba5127991d11f82ee98c5234 Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Wed, 9 Dec 2015 14:06:25 -0800 Subject: [PATCH 056/207] Update to Realm Core 0.95.5 --- impl/transact_log_handler.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/impl/transact_log_handler.cpp b/impl/transact_log_handler.cpp index 2deba2fd..ac74b515 100644 --- a/impl/transact_log_handler.cpp +++ b/impl/transact_log_handler.cpp @@ -293,10 +293,12 @@ public: // Things that just mark the field as modified bool set_int(size_t col, size_t row, int_fast64_t) { return mark_dirty(row, col); } + bool set_int_unique(size_t col, size_t row, int_fast64_t) { return mark_dirty(row, col); } bool set_bool(size_t col, size_t row, bool) { return mark_dirty(row, col); } bool set_float(size_t col, size_t row, float) { return mark_dirty(row, col); } bool set_double(size_t col, size_t row, double) { return mark_dirty(row, col); } bool set_string(size_t col, size_t row, StringData) { return mark_dirty(row, col); } + bool set_string_unique(size_t col, size_t row, StringData) { return mark_dirty(row, col); } bool set_binary(size_t col, size_t row, BinaryData) { return mark_dirty(row, col); } bool set_date_time(size_t col, size_t row, DateTime) { return mark_dirty(row, col); } bool set_table(size_t col, size_t row) { return mark_dirty(row, col); } From 0286dea7a4dbebb8739554a4ceeb510653a1e85e Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Fri, 20 Nov 2015 14:05:18 -0800 Subject: [PATCH 057/207] Add methods to create snapshot of List and Results The Results class was updated to match the style of List and include a flag (m_live) that determines if it should sync updates. If an object in the static Results is deleted, then it will return null. --- list.cpp | 5 +++++ list.hpp | 2 ++ results.cpp | 17 +++++++++++++++-- results.hpp | 4 ++++ 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/list.cpp b/list.cpp index 1cbafb8d..80d07bc4 100644 --- a/list.cpp +++ b/list.cpp @@ -59,6 +59,11 @@ void List::remove(size_t row_ndx) { m_link_view->remove(row_ndx); } +Query List::get_query() { + verify_attached(); + return m_link_view->get_target_table().where(m_link_view); +} + void List::verify_valid_row(size_t row_ndx, bool insertion) { size_t size = m_link_view->size(); if (row_ndx > size || (!insertion && row_ndx == size)) { diff --git a/list.hpp b/list.hpp index fdde4c62..85ec6221 100644 --- a/list.hpp +++ b/list.hpp @@ -47,6 +47,8 @@ namespace realm { template void set(ContextType ctx, ValueType value, size_t list_ndx); + Query get_query(); + void verify_valid_row(size_t row_ndx, bool insertion = false); void verify_attached(); void verify_in_tranaction(); diff --git a/results.cpp b/results.cpp index b2a63789..45578796 100644 --- a/results.cpp +++ b/results.cpp @@ -64,6 +64,17 @@ void Results::validate_write() const throw InvalidTransactionException("Must be in a write transaction"); } +void Results::set_live(bool live) +{ + if (!live && m_mode == Mode::Table) { + m_query = m_table->where(); + m_mode = Mode::Query; + } + + update_tableview(); + m_live = live; +} + size_t Results::size() { validate_read(); @@ -91,7 +102,7 @@ RowExpr Results::get(size_t row_ndx) case Mode::TableView: update_tableview(); if (row_ndx < m_table_view.size()) - return m_table_view.get(row_ndx); + return (!m_live && !m_table_view.is_row_attached(row_ndx)) ? RowExpr() : m_table_view.get(row_ndx); break; } @@ -147,7 +158,9 @@ void Results::update_tableview() m_mode = Mode::TableView; break; case Mode::TableView: - m_table_view.sync_if_needed(); + if (m_live) { + m_table_view.sync_if_needed(); + } break; } } diff --git a/results.hpp b/results.hpp index 115ca636..7d8563ff 100644 --- a/results.hpp +++ b/results.hpp @@ -60,6 +60,9 @@ public: // Get the object type which will be returned by get() StringData get_object_type() const noexcept { return object_schema.name; } + // Set whether the TableView should sync if needed before accessing results + void set_live(bool live); + // Get the size of this results // Can be either O(1) or O(N) depending on the state of things size_t size(); @@ -153,6 +156,7 @@ private: TableView m_table_view; Table* m_table = nullptr; SortOrder m_sort; + bool m_live = true; Mode m_mode = Mode::Empty; From b8d40950a723b4f84176ae454c20b09b916c692c Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 4 Jan 2016 15:54:03 -0800 Subject: [PATCH 058/207] remove invalid usage of const Schema --- results.cpp | 22 ++++++++++------------ shared_realm.cpp | 4 ++-- shared_realm.hpp | 4 ++-- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/results.cpp b/results.cpp index ea591276..32b00dda 100644 --- a/results.cpp +++ b/results.cpp @@ -171,9 +171,10 @@ size_t Results::index_of(Row const& row) throw DetatchedAccessorException{}; } if (m_table && row.get_table() != m_table) { - throw IncorrectTableException{ - ObjectStore::object_type_for_table_name(m_table->get_name()), - ObjectStore::object_type_for_table_name(row.get_table()->get_name())}; + throw IncorrectTableException(object_schema.name, + ObjectStore::object_type_for_table_name(row.get_table()->get_name()), + "Attempting to get the index of a Row of the wrong type" + ); } return index_of(row.get_index()); } @@ -323,11 +324,6 @@ TableView Results::get_tableview() REALM_UNREACHABLE(); } -StringData Results::get_object_type() const noexcept -{ - return ObjectStore::object_type_for_table_name(m_table->get_name()); -} - Results Results::sort(realm::SortOrder&& sort) const { return Results(m_realm, object_schema, get_query(), std::move(sort)); @@ -338,8 +334,10 @@ Results Results::filter(Query&& q) const return Results(m_realm, object_schema, get_query().and_query(std::move(q)), get_sort()); } -Results::UnsupportedColumnTypeException::UnsupportedColumnTypeException(size_t column, const Table* table) { - column_index = column; - column_name = table->get_column_name(column); - column_type = table->get_column_type(column); +Results::UnsupportedColumnTypeException::UnsupportedColumnTypeException(size_t column, const Table* table) +: column_index(column) +, column_name(table->get_column_name(column)) +, column_type(table->get_column_type(column)) +, std::runtime_error((std::string)"Operation not supported on '" + table->get_column_name(column).data() + "' columns") +{ } diff --git a/shared_realm.cpp b/shared_realm.cpp index aeeeaa28..c43f27db 100644 --- a/shared_realm.cpp +++ b/shared_realm.cpp @@ -193,13 +193,13 @@ SharedRealm Realm::get_shared_realm(Config config) return realm; } -bool Realm::update_schema(std::unique_ptr schema, uint64_t version) +bool Realm::update_schema(std::unique_ptr schema, uint64_t version) { schema->validate(); bool needs_update = !m_config.read_only && (m_config.schema_version != version || ObjectStore::needs_update(*m_config.schema, *schema)); if (!needs_update) { - ObjectStore::verify_schema(*m_config.schema, const_cast(*schema), m_config.read_only); + ObjectStore::verify_schema(*m_config.schema, *schema, m_config.read_only); m_config.schema = std::move(schema); m_config.schema_version = version; return false; diff --git a/shared_realm.hpp b/shared_realm.hpp index ffb86937..85e42224 100644 --- a/shared_realm.hpp +++ b/shared_realm.hpp @@ -52,7 +52,7 @@ namespace realm { bool disable_format_upgrade = false; std::vector encryption_key; - std::unique_ptr schema; + std::unique_ptr schema; uint64_t schema_version = ObjectStore::NotVersioned; MigrationFunction migration_function; @@ -80,7 +80,7 @@ namespace realm { // on the Config, and the resulting Schema and version with updated // column mappings are set on the realms config upon success. // returns if any changes were made - bool update_schema(std::unique_ptr schema, uint64_t version); + bool update_schema(std::unique_ptr schema, uint64_t version); static uint64_t get_schema_version(Config const& config); From c25d08eb3428185236fae5af91227c3425c288bb Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 4 Jan 2016 16:04:01 -0800 Subject: [PATCH 059/207] store ObjectSchema as a pointer rather than a reference --- list.hpp | 5 +++-- object_accessor.hpp | 6 +++--- results.cpp | 21 +++++---------------- results.hpp | 7 ++++--- 4 files changed, 15 insertions(+), 24 deletions(-) diff --git a/list.hpp b/list.hpp index ef84846a..e7c6465c 100644 --- a/list.hpp +++ b/list.hpp @@ -25,9 +25,9 @@ namespace realm { class List { public: - List(SharedRealm &r, const ObjectSchema &s, LinkViewRef l) : m_realm(r), object_schema(s), m_link_view(l) {} + List(SharedRealm &r, const ObjectSchema &s, LinkViewRef l) : m_realm(r), m_object_schema(&s), m_link_view(l) {} - const ObjectSchema &object_schema; + const ObjectSchema &object_schema() const { return *m_object_schema; } SharedRealm realm() { return m_realm; } size_t size(); @@ -53,6 +53,7 @@ namespace realm { private: SharedRealm m_realm; + const ObjectSchema *m_object_schema; LinkViewRef m_link_view; }; } diff --git a/object_accessor.hpp b/object_accessor.hpp index ae1025f0..eed939e1 100644 --- a/object_accessor.hpp +++ b/object_accessor.hpp @@ -308,19 +308,19 @@ namespace realm { template void List::add(ContextType ctx, ValueType value) { - add(NativeAccessor::to_object_index(ctx, m_realm, value, object_schema.name, false)); + add(NativeAccessor::to_object_index(ctx, m_realm, value, object_schema().name, false)); } template void List::insert(ContextType ctx, ValueType value, size_t list_ndx) { - insert(list_ndx, NativeAccessor::to_object_index(ctx, m_realm, value, object_schema.name, false)); + insert(list_ndx, NativeAccessor::to_object_index(ctx, m_realm, value, object_schema().name, false)); } template void List::set(ContextType ctx, ValueType value, size_t list_ndx) { - set(list_ndx, NativeAccessor::to_object_index(ctx, m_realm, value, object_schema.name, false)); + set(list_ndx, NativeAccessor::to_object_index(ctx, m_realm, value, object_schema().name, false)); } } diff --git a/results.cpp b/results.cpp index 32b00dda..e8d4bfd2 100644 --- a/results.cpp +++ b/results.cpp @@ -40,7 +40,7 @@ Results::Results(SharedRealm r, const ObjectSchema &o, Query q, SortOrder s) , m_table(m_query.get_table().get()) , m_sort(std::move(s)) , m_mode(Mode::Query) -, object_schema(o) +, m_object_schema(&o) { } @@ -48,21 +48,10 @@ Results::Results(SharedRealm r, const ObjectSchema &o, Table& table) : m_realm(std::move(r)) , m_table(&table) , m_mode(Mode::Table) -, object_schema(o) +, m_object_schema(&o) { } -Results& Results::operator=(Results const& r) -{ - m_realm = r.m_realm; - m_table = r.m_table; - m_sort = r.m_sort; - m_query = r.get_query(); - m_mode = Mode::Query; - const_cast(object_schema) = r.object_schema; - return *this; -} - void Results::validate_read() const { if (m_realm) @@ -171,7 +160,7 @@ size_t Results::index_of(Row const& row) throw DetatchedAccessorException{}; } if (m_table && row.get_table() != m_table) { - throw IncorrectTableException(object_schema.name, + throw IncorrectTableException(m_object_schema->name, ObjectStore::object_type_for_table_name(row.get_table()->get_name()), "Attempting to get the index of a Row of the wrong type" ); @@ -326,12 +315,12 @@ TableView Results::get_tableview() Results Results::sort(realm::SortOrder&& sort) const { - return Results(m_realm, object_schema, get_query(), std::move(sort)); + return Results(m_realm, object_schema(), get_query(), std::move(sort)); } Results Results::filter(Query&& q) const { - return Results(m_realm, object_schema, get_query().and_query(std::move(q)), get_sort()); + return Results(m_realm, object_schema(), get_query().and_query(std::move(q)), get_sort()); } Results::UnsupportedColumnTypeException::UnsupportedColumnTypeException(size_t column, const Table* table) diff --git a/results.hpp b/results.hpp index 9866cae8..42dbda8f 100644 --- a/results.hpp +++ b/results.hpp @@ -53,13 +53,13 @@ public: Results(Results const&) = default; Results(Results&&) = default; Results& operator=(Results&&) = default; - Results& operator=(Results const&); + Results& operator=(Results const&) = default; // Get the Realm SharedRealm get_realm() const { return m_realm; } // Object schema describing the vendored object type - const ObjectSchema &object_schema; + const ObjectSchema &object_schema() const { return *m_object_schema; } // Get a query which will match the same rows as is contained in this Results // Returned query will not be valid if the current mode is Empty @@ -72,7 +72,7 @@ public: TableView get_tableview(); // Get the object type which will be returned by get() - StringData get_object_type() const noexcept { return object_schema.name; } + StringData get_object_type() const noexcept { return object_schema().name; } // Get the size of this results // Can be either O(1) or O(N) depending on the state of things @@ -163,6 +163,7 @@ public: private: SharedRealm m_realm; + const ObjectSchema *m_object_schema; Query m_query; TableView m_table_view; Table* m_table = nullptr; From 1e36beb263891bccda0d2e99fcdb2bc139545dac Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 4 Jan 2016 16:21:28 -0800 Subject: [PATCH 060/207] store ObjectSchema as a pointer rather than a reference --- object_accessor.hpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/object_accessor.hpp b/object_accessor.hpp index eed939e1..dc50240d 100644 --- a/object_accessor.hpp +++ b/object_accessor.hpp @@ -14,7 +14,7 @@ namespace realm { class Object { public: - Object(SharedRealm r, const ObjectSchema &s, Row o) : m_realm(r), object_schema(s), m_row(o) {} + Object(SharedRealm r, const ObjectSchema &s, Row o) : m_realm(r), m_object_schema(&s), m_row(o) {} // property getter/setter template @@ -27,12 +27,13 @@ namespace realm { template static inline Object create(ContextType ctx, SharedRealm realm, const ObjectSchema &object_schema, ValueType value, bool try_update); - const ObjectSchema &object_schema; SharedRealm realm() { return m_realm; } + const ObjectSchema &object_schema() { return *m_object_schema; } Row row() { return m_row; } private: SharedRealm m_realm; + const ObjectSchema *m_object_schema; Row m_row; template @@ -119,10 +120,10 @@ namespace realm { template inline void Object::set_property_value(ContextType ctx, std::string prop_name, ValueType value, bool try_update) { - const Property *prop = object_schema.property_for_name(prop_name); + const Property *prop = m_object_schema->property_for_name(prop_name); if (!prop) { - throw InvalidPropertyException(object_schema.name, prop_name, - "Setting invalid property '" + prop_name + "' on object '" + object_schema.name + "'."); + throw InvalidPropertyException(m_object_schema->name, prop_name, + "Setting invalid property '" + prop_name + "' on object '" + m_object_schema->name + "'."); } set_property_value_impl(ctx, *prop, value, try_update); }; @@ -130,10 +131,10 @@ namespace realm { template inline ValueType Object::get_property_value(ContextType ctx, std::string prop_name) { - const Property *prop = object_schema.property_for_name(prop_name); + const Property *prop = m_object_schema->property_for_name(prop_name); if (!prop) { - throw InvalidPropertyException(object_schema.name, prop_name, - "Getting invalid property '" + prop_name + "' on object '" + object_schema.name + "'."); + throw InvalidPropertyException(m_object_schema->name, prop_name, + "Getting invalid property '" + prop_name + "' on object '" + m_object_schema->name + "'."); } return get_property_value_impl(ctx, *prop); }; From 7964aff43126d4dbd2bfa8eaf69628b6e09fdcfa Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 4 Jan 2016 16:27:37 -0800 Subject: [PATCH 061/207] remove unnecessary const cast --- shared_realm.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared_realm.cpp b/shared_realm.cpp index c43f27db..7c48bf55 100644 --- a/shared_realm.cpp +++ b/shared_realm.cpp @@ -171,7 +171,7 @@ SharedRealm Realm::get_shared_realm(Config config) throw UnitializedRealmException("Can't open an un-initialized Realm without a Schema"); } target_schema->validate(); - ObjectStore::verify_schema(*realm->m_config.schema, const_cast(*target_schema), true); + ObjectStore::verify_schema(*realm->m_config.schema, *target_schema, true); realm->m_config.schema = std::move(target_schema); } else { From 7c116c0629092e76d38fd84faecc9e079f90bd5f Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 4 Jan 2016 16:37:07 -0800 Subject: [PATCH 062/207] pr fixes --- results.cpp | 4 ++-- shared_realm.cpp | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/results.cpp b/results.cpp index e8d4bfd2..437cfff7 100644 --- a/results.cpp +++ b/results.cpp @@ -324,9 +324,9 @@ Results Results::filter(Query&& q) const } Results::UnsupportedColumnTypeException::UnsupportedColumnTypeException(size_t column, const Table* table) -: column_index(column) +: std::runtime_error((std::string)"Operation not supported on '" + table->get_column_name(column).data() + "' columns") +, column_index(column) , column_name(table->get_column_name(column)) , column_type(table->get_column_type(column)) -, std::runtime_error((std::string)"Operation not supported on '" + table->get_column_name(column).data() + "' columns") { } diff --git a/shared_realm.cpp b/shared_realm.cpp index 7c48bf55..fd85e8f9 100644 --- a/shared_realm.cpp +++ b/shared_realm.cpp @@ -212,6 +212,9 @@ bool Realm::update_schema(std::unique_ptr schema, uint64_t version) old_config.read_only = true; old_config.schema = std::move(old_schema); + m_config.schema = std::move(schema); + m_config.schema_version = version; + auto migration_function = [&](Group*, Schema&) { SharedRealm old_realm(new Realm(old_config)); auto updated_realm = shared_from_this(); @@ -224,9 +227,8 @@ bool Realm::update_schema(std::unique_ptr schema, uint64_t version) // update and migrate begin_transaction(); bool changed = ObjectStore::update_realm_with_schema(read_group(), *old_config.schema, - version, const_cast(*schema), migration_function); - m_config.schema = std::move(schema); - m_config.schema_version = version; + version, *m_config.schema, + migration_function); commit_transaction(); return changed; } From 60b3b5d2fb5972ebc9cc6d832505843882355083 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 4 Jan 2016 16:51:51 -0800 Subject: [PATCH 063/207] remove unnedded constructors/destructor --- parser/parser.cpp | 2 +- parser/parser.hpp | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/parser/parser.cpp b/parser/parser.cpp index 2753fbd8..7d425ee5 100644 --- a/parser/parser.cpp +++ b/parser/parser.cpp @@ -227,7 +227,7 @@ template<> struct action< or_op > template<> struct action< rule > { \ static void apply( const input & in, ParserState & state ) { \ DEBUG_PRINT_TOKEN(in.string()); \ - state.add_expression(Expression(type, in.string())); }}; + state.add_expression(Expression{type, in.string()}); }}; EXPRESSION_ACTION(dq_string_content, Expression::Type::String) EXPRESSION_ACTION(sq_string_content, Expression::Type::String) diff --git a/parser/parser.hpp b/parser/parser.hpp index 377eda5d..bad15381 100644 --- a/parser/parser.hpp +++ b/parser/parser.hpp @@ -30,8 +30,6 @@ namespace realm { { enum class Type { None, Number, String, KeyPath, Argument, True, False } type = Type::None; std::string s; - Expression() {} - Expression(Type t, std::string s) : type(t), s(s) {} }; struct Predicate @@ -63,7 +61,6 @@ namespace realm { { Operator op = Operator::None; Expression expr[2]; - ~Comparison() {} }; struct Compound From ff532b47c4072fe43f29c3f4de3ba3cea9a4cd96 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 4 Jan 2016 18:13:09 -0800 Subject: [PATCH 064/207] bdash pr fixes --- list.hpp | 2 +- object_accessor.hpp | 8 ++-- parser/parser.hpp | 98 ++++++++++++++++++++-------------------- parser/query_builder.cpp | 16 +++---- parser/query_builder.hpp | 86 +++++++++++++++++------------------ results.cpp | 4 +- results.hpp | 8 ++-- 7 files changed, 111 insertions(+), 111 deletions(-) diff --git a/list.hpp b/list.hpp index e7c6465c..0440f003 100644 --- a/list.hpp +++ b/list.hpp @@ -27,7 +27,7 @@ namespace realm { public: List(SharedRealm &r, const ObjectSchema &s, LinkViewRef l) : m_realm(r), m_object_schema(&s), m_link_view(l) {} - const ObjectSchema &object_schema() const { return *m_object_schema; } + const ObjectSchema &get_object_schema() const { return *m_object_schema; } SharedRealm realm() { return m_realm; } size_t size(); diff --git a/object_accessor.hpp b/object_accessor.hpp index dc50240d..bad38f9e 100644 --- a/object_accessor.hpp +++ b/object_accessor.hpp @@ -28,7 +28,7 @@ namespace realm { static inline Object create(ContextType ctx, SharedRealm realm, const ObjectSchema &object_schema, ValueType value, bool try_update); SharedRealm realm() { return m_realm; } - const ObjectSchema &object_schema() { return *m_object_schema; } + const ObjectSchema &get_object_schema() { return *m_object_schema; } Row row() { return m_row; } private: @@ -309,19 +309,19 @@ namespace realm { template void List::add(ContextType ctx, ValueType value) { - add(NativeAccessor::to_object_index(ctx, m_realm, value, object_schema().name, false)); + add(NativeAccessor::to_object_index(ctx, m_realm, value, get_object_schema().name, false)); } template void List::insert(ContextType ctx, ValueType value, size_t list_ndx) { - insert(list_ndx, NativeAccessor::to_object_index(ctx, m_realm, value, object_schema().name, false)); + insert(list_ndx, NativeAccessor::to_object_index(ctx, m_realm, value, get_object_schema().name, false)); } template void List::set(ContextType ctx, ValueType value, size_t list_ndx) { - set(list_ndx, NativeAccessor::to_object_index(ctx, m_realm, value, object_schema().name, false)); + set(list_ndx, NativeAccessor::to_object_index(ctx, m_realm, value, get_object_schema().name, false)); } } diff --git a/parser/parser.hpp b/parser/parser.hpp index bad15381..681847f0 100644 --- a/parser/parser.hpp +++ b/parser/parser.hpp @@ -23,64 +23,64 @@ #include namespace realm { - class Schema; +class Schema; - namespace parser { - struct Expression - { - enum class Type { None, Number, String, KeyPath, Argument, True, False } type = Type::None; - std::string s; - }; +namespace parser { +struct Expression +{ + enum class Type { None, Number, String, KeyPath, Argument, True, False } type = Type::None; + std::string s; +}; - struct Predicate - { - enum class Type - { - Comparison, - Or, - And, - True, - False - } type = Type::And; +struct Predicate +{ + enum class Type + { + Comparison, + Or, + And, + True, + False + } type = Type::And; - enum class Operator - { - None, - Equal, - NotEqual, - LessThan, - LessThanOrEqual, - GreaterThan, - GreaterThanOrEqual, - BeginsWith, - EndsWith, - Contains - }; + enum class Operator + { + None, + Equal, + NotEqual, + LessThan, + LessThanOrEqual, + GreaterThan, + GreaterThanOrEqual, + BeginsWith, + EndsWith, + Contains + }; - struct Comparison - { - Operator op = Operator::None; - Expression expr[2]; - }; + struct Comparison + { + Operator op = Operator::None; + Expression expr[2]; + }; - struct Compound - { - std::vector sub_predicates; - }; + struct Compound + { + std::vector sub_predicates; + }; - Comparison cmpr; - Compound cpnd; + Comparison cmpr; + Compound cpnd; - bool negate = false; + bool negate = false; - Predicate(Type t, bool n = false) : type(t), negate(n) {} - }; + Predicate(Type t, bool n = false) : type(t), negate(n) {} +}; - Predicate parse(const std::string &query); - - void analyzeGrammar(); - bool testGrammar(); - } +Predicate parse(const std::string &query); + +void analyzeGrammar(); +bool testGrammar(); +} } #endif // REALM_PARSER_HPP diff --git a/parser/query_builder.cpp b/parser/query_builder.cpp index 6f74f3b2..e3834ee1 100644 --- a/parser/query_builder.cpp +++ b/parser/query_builder.cpp @@ -230,7 +230,7 @@ void add_binary_constraint_to_query(realm::Query &query, void add_link_constraint_to_query(realm::Query &query, Predicate::Operator op, - PropertyExpression &prop_expr, + const PropertyExpression &prop_expr, size_t row_index) { precondition(prop_expr.indexes.empty(), "KeyPath queries not supported for object comparisons."); switch (op) { @@ -260,12 +260,12 @@ void add_link_constraint_to_query(realm::Query &query, } } -auto link_argument(PropertyExpression &propExpr, parser::Expression &argExpr, Arguments &args) +auto link_argument(const PropertyExpression &propExpr, const parser::Expression &argExpr, Arguments &args) { return args.object_index_for_argument(std::stoi(argExpr.s)); } -auto link_argument(parser::Expression &argExpr, PropertyExpression &propExpr, Arguments &args) +auto link_argument(const parser::Expression &argExpr, const PropertyExpression &propExpr, Arguments &args) { return args.object_index_for_argument(std::stoi(argExpr.s)); } @@ -376,7 +376,7 @@ auto value_of_type_for_query(TableGetter&& tables, Value&& value, Arguments &arg template void do_add_comparison_to_query(Query &query, const Schema &schema, const ObjectSchema &object_schema, Predicate::Operator op, - PropertyExpression &expr, A &lhs, B &rhs, Arguments &args) + const PropertyExpression &expr, A &lhs, B &rhs, Arguments &args) { auto type = expr.prop->type; switch (type) { @@ -418,9 +418,9 @@ void do_add_comparison_to_query(Query &query, const Schema &schema, const Object } } -void add_comparison_to_query(Query &query, Predicate &pred, Arguments &args, const Schema &schema, const std::string &type) +void add_comparison_to_query(Query &query, const Predicate &pred, Arguments &args, const Schema &schema, const std::string &type) { - Predicate::Comparison &cmpr = pred.cmpr; + const Predicate::Comparison &cmpr = pred.cmpr; auto t0 = cmpr.expr[0].type, t1 = cmpr.expr[1].type; auto object_schema = schema.find(type); if (t0 == parser::Expression::Type::KeyPath && t1 != parser::Expression::Type::KeyPath) { @@ -436,7 +436,7 @@ void add_comparison_to_query(Query &query, Predicate &pred, Arguments &args, con } } -void update_query_with_predicate(Query &query, Predicate &pred, Arguments &arguments, const Schema &schema, const std::string &type) +void update_query_with_predicate(Query &query, const Predicate &pred, Arguments &arguments, const Schema &schema, const std::string &type) { if (pred.negate) { query.Not(); @@ -484,7 +484,7 @@ void update_query_with_predicate(Query &query, Predicate &pred, Arguments &argum } } -void apply_predicate(Query &query, Predicate &predicate, Arguments &arguments, const Schema &schema, std::string objectType) +void apply_predicate(Query &query, const Predicate &predicate, Arguments &arguments, const Schema &schema, const std::string &objectType) { update_query_with_predicate(query, predicate, arguments, schema, objectType); diff --git a/parser/query_builder.hpp b/parser/query_builder.hpp index 713ef6ac..e5f519dc 100644 --- a/parser/query_builder.hpp +++ b/parser/query_builder.hpp @@ -24,57 +24,57 @@ #include "object_accessor.hpp" namespace realm { - class Query; - class Schema; +class Query; +class Schema; - namespace query_builder { - class Arguments; +namespace query_builder { +class Arguments; - void apply_predicate(Query &query, parser::Predicate &predicate, Arguments &arguments, const Schema &schema, std::string objectType); +void apply_predicate(Query &query, const parser::Predicate &predicate, Arguments &arguments, const Schema &schema, const std::string &objectType); - class Arguments - { - public: - virtual bool bool_for_argument(size_t argument_index) = 0; - virtual long long long_for_argument(size_t argument_index) = 0; - virtual float float_for_argument(size_t argument_index) = 0; - virtual double double_for_argument(size_t argument_index) = 0; - virtual std::string string_for_argument(size_t argument_index) = 0; - virtual std::string binary_for_argument(size_t argument_index) = 0; - virtual DateTime datetime_for_argument(size_t argument_index) = 0; - virtual size_t object_index_for_argument(size_t argument_index) = 0; - virtual bool is_argument_null(size_t argument_index) = 0; - }; +class Arguments +{ + public: + virtual bool bool_for_argument(size_t argument_index) = 0; + virtual long long long_for_argument(size_t argument_index) = 0; + virtual float float_for_argument(size_t argument_index) = 0; + virtual double double_for_argument(size_t argument_index) = 0; + virtual std::string string_for_argument(size_t argument_index) = 0; + virtual std::string binary_for_argument(size_t argument_index) = 0; + virtual DateTime datetime_for_argument(size_t argument_index) = 0; + virtual size_t object_index_for_argument(size_t argument_index) = 0; + virtual bool is_argument_null(size_t argument_index) = 0; +}; - template - class ArgumentConverter : public Arguments - { - public: - ArgumentConverter(ContextType context, std::vector arguments) : m_arguments(arguments), m_ctx(context) {}; +template +class ArgumentConverter : public Arguments +{ + public: + ArgumentConverter(ContextType context, std::vector arguments) : m_arguments(arguments), m_ctx(context) {}; - using Accessor = realm::NativeAccessor; - virtual bool bool_for_argument(size_t argument_index) { return Accessor::to_bool(m_ctx, argument_at(argument_index)); } - virtual long long long_for_argument(size_t argument_index) { return Accessor::to_long(m_ctx, argument_at(argument_index)); } - virtual float float_for_argument(size_t argument_index) { return Accessor::to_float(m_ctx, argument_at(argument_index)); } - virtual double double_for_argument(size_t argument_index) { return Accessor::to_double(m_ctx, argument_at(argument_index)); } - virtual std::string string_for_argument(size_t argument_index) { return Accessor::to_string(m_ctx, argument_at(argument_index)); } - virtual std::string binary_for_argument(size_t argument_index) { return Accessor::to_binary(m_ctx, argument_at(argument_index)); } - virtual DateTime datetime_for_argument(size_t argument_index) { return Accessor::to_datetime(m_ctx, argument_at(argument_index)); } - virtual size_t object_index_for_argument(size_t argument_index) { return Accessor::to_existing_object_index(m_ctx, argument_at(argument_index)); } - virtual bool is_argument_null(size_t argument_index) { return Accessor::is_null(m_ctx, argument_at(argument_index)); } + using Accessor = realm::NativeAccessor; + virtual bool bool_for_argument(size_t argument_index) { return Accessor::to_bool(m_ctx, argument_at(argument_index)); } + virtual long long long_for_argument(size_t argument_index) { return Accessor::to_long(m_ctx, argument_at(argument_index)); } + virtual float float_for_argument(size_t argument_index) { return Accessor::to_float(m_ctx, argument_at(argument_index)); } + virtual double double_for_argument(size_t argument_index) { return Accessor::to_double(m_ctx, argument_at(argument_index)); } + virtual std::string string_for_argument(size_t argument_index) { return Accessor::to_string(m_ctx, argument_at(argument_index)); } + virtual std::string binary_for_argument(size_t argument_index) { return Accessor::to_binary(m_ctx, argument_at(argument_index)); } + virtual DateTime datetime_for_argument(size_t argument_index) { return Accessor::to_datetime(m_ctx, argument_at(argument_index)); } + virtual size_t object_index_for_argument(size_t argument_index) { return Accessor::to_existing_object_index(m_ctx, argument_at(argument_index)); } + virtual bool is_argument_null(size_t argument_index) { return Accessor::is_null(m_ctx, argument_at(argument_index)); } - private: - std::vector m_arguments; - ContextType m_ctx; + private: + std::vector m_arguments; + ContextType m_ctx; - ValueType &argument_at(size_t index) { - if (index >= m_arguments.size()) { - throw std::out_of_range((std::string)"Argument index " + std::to_string(index) + " out of range 0.." + std::to_string(m_arguments.size()-1)); - } - return m_arguments[index]; - } - }; + ValueType &argument_at(size_t index) { + if (index >= m_arguments.size()) { + throw std::out_of_range((std::string)"Argument index " + std::to_string(index) + " out of range 0.." + std::to_string(m_arguments.size()-1)); + } + return m_arguments[index]; } +}; +} } #endif // REALM_QUERY_BUILDER_HPP diff --git a/results.cpp b/results.cpp index 437cfff7..0839f1f5 100644 --- a/results.cpp +++ b/results.cpp @@ -315,12 +315,12 @@ TableView Results::get_tableview() Results Results::sort(realm::SortOrder&& sort) const { - return Results(m_realm, object_schema(), get_query(), std::move(sort)); + return Results(m_realm, get_object_schema(), get_query(), std::move(sort)); } Results Results::filter(Query&& q) const { - return Results(m_realm, object_schema(), get_query().and_query(std::move(q)), get_sort()); + return Results(m_realm, get_object_schema(), get_query().and_query(std::move(q)), get_sort()); } Results::UnsupportedColumnTypeException::UnsupportedColumnTypeException(size_t column, const Table* table) diff --git a/results.hpp b/results.hpp index 42dbda8f..ec6b7bee 100644 --- a/results.hpp +++ b/results.hpp @@ -46,8 +46,8 @@ public: // or a wrapper around a query and a sort order which creates and updates // the tableview as needed Results() = default; - Results(SharedRealm r, const ObjectSchema &o, Table& table); - Results(SharedRealm r, const ObjectSchema &o, Query q, SortOrder s = {}); + Results(SharedRealm r, const ObjectSchema& o, Table& table); + Results(SharedRealm r, const ObjectSchema& o, Query q, SortOrder s = {}); // Results is copyable and moveable Results(Results const&) = default; @@ -59,7 +59,7 @@ public: SharedRealm get_realm() const { return m_realm; } // Object schema describing the vendored object type - const ObjectSchema &object_schema() const { return *m_object_schema; } + const ObjectSchema &get_object_schema() const { return *m_object_schema; } // Get a query which will match the same rows as is contained in this Results // Returned query will not be valid if the current mode is Empty @@ -72,7 +72,7 @@ public: TableView get_tableview(); // Get the object type which will be returned by get() - StringData get_object_type() const noexcept { return object_schema().name; } + StringData get_object_type() const noexcept { return get_object_schema().name; } // Get the size of this results // Can be either O(1) or O(N) depending on the state of things From 3cd60002baad7943bf931023d4c1fe9f1224942c Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 30 Nov 2015 13:59:58 -0800 Subject: [PATCH 065/207] add symlinks to binding source --- tests/react-test-app/android/app/build.gradle | 1 + .../android/app/src/main/jni/js_init.cpp | 1 + .../android/app/src/main/jni/js_init.h | 1 + .../android/app/src/main/jni/js_list.cpp | 1 + .../android/app/src/main/jni/js_list.hpp | 1 + .../android/app/src/main/jni/js_object.cpp | 1 + .../android/app/src/main/jni/js_object.hpp | 1 + .../android/app/src/main/jni/js_realm.cpp | 1 + .../android/app/src/main/jni/js_realm.hpp | 1 + .../android/app/src/main/jni/js_results.cpp | 1 + .../android/app/src/main/jni/js_results.hpp | 1 + .../android/app/src/main/jni/js_schema.cpp | 1 + .../android/app/src/main/jni/js_schema.hpp | 1 + .../android/app/src/main/jni/js_util.cpp | 1 + .../android/app/src/main/jni/js_util.hpp | 1 + .../android/app/src/main/jni/platform.hpp | 1 + .../app/src/main/jni/realm-react-android.c | 63 +++++++++++++++++++ .../android/app/src/main/jni/rpc.cpp | 1 + .../android/app/src/main/jni/rpc.hpp | 1 + tests/react-test-app/android/build.gradle | 2 +- 20 files changed, 82 insertions(+), 1 deletion(-) create mode 120000 tests/react-test-app/android/app/src/main/jni/js_init.cpp create mode 120000 tests/react-test-app/android/app/src/main/jni/js_init.h create mode 120000 tests/react-test-app/android/app/src/main/jni/js_list.cpp create mode 120000 tests/react-test-app/android/app/src/main/jni/js_list.hpp create mode 120000 tests/react-test-app/android/app/src/main/jni/js_object.cpp create mode 120000 tests/react-test-app/android/app/src/main/jni/js_object.hpp create mode 120000 tests/react-test-app/android/app/src/main/jni/js_realm.cpp create mode 120000 tests/react-test-app/android/app/src/main/jni/js_realm.hpp create mode 120000 tests/react-test-app/android/app/src/main/jni/js_results.cpp create mode 120000 tests/react-test-app/android/app/src/main/jni/js_results.hpp create mode 120000 tests/react-test-app/android/app/src/main/jni/js_schema.cpp create mode 120000 tests/react-test-app/android/app/src/main/jni/js_schema.hpp create mode 120000 tests/react-test-app/android/app/src/main/jni/js_util.cpp create mode 120000 tests/react-test-app/android/app/src/main/jni/js_util.hpp create mode 120000 tests/react-test-app/android/app/src/main/jni/platform.hpp create mode 100644 tests/react-test-app/android/app/src/main/jni/realm-react-android.c create mode 120000 tests/react-test-app/android/app/src/main/jni/rpc.cpp create mode 120000 tests/react-test-app/android/app/src/main/jni/rpc.hpp diff --git a/tests/react-test-app/android/app/build.gradle b/tests/react-test-app/android/app/build.gradle index a3ec8725..fbd478e3 100644 --- a/tests/react-test-app/android/app/build.gradle +++ b/tests/react-test-app/android/app/build.gradle @@ -60,6 +60,7 @@ android { versionCode 1 versionName "1.0" ndk { + moduleName = "realm-react-android" abiFilters "armeabi-v7a", "x86" } } diff --git a/tests/react-test-app/android/app/src/main/jni/js_init.cpp b/tests/react-test-app/android/app/src/main/jni/js_init.cpp new file mode 120000 index 00000000..5dbc34bd --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/js_init.cpp @@ -0,0 +1 @@ +../../../../../../../src/js_init.cpp \ No newline at end of file diff --git a/tests/react-test-app/android/app/src/main/jni/js_init.h b/tests/react-test-app/android/app/src/main/jni/js_init.h new file mode 120000 index 00000000..904b6881 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/js_init.h @@ -0,0 +1 @@ +../../../../../../../src/js_init.h \ No newline at end of file diff --git a/tests/react-test-app/android/app/src/main/jni/js_list.cpp b/tests/react-test-app/android/app/src/main/jni/js_list.cpp new file mode 120000 index 00000000..5a9624cd --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/js_list.cpp @@ -0,0 +1 @@ +../../../../../../../src/js_list.cpp \ No newline at end of file diff --git a/tests/react-test-app/android/app/src/main/jni/js_list.hpp b/tests/react-test-app/android/app/src/main/jni/js_list.hpp new file mode 120000 index 00000000..66870d89 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/js_list.hpp @@ -0,0 +1 @@ +../../../../../../../src/js_list.hpp \ No newline at end of file diff --git a/tests/react-test-app/android/app/src/main/jni/js_object.cpp b/tests/react-test-app/android/app/src/main/jni/js_object.cpp new file mode 120000 index 00000000..77cd10b6 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/js_object.cpp @@ -0,0 +1 @@ +../../../../../../../src/js_object.cpp \ No newline at end of file diff --git a/tests/react-test-app/android/app/src/main/jni/js_object.hpp b/tests/react-test-app/android/app/src/main/jni/js_object.hpp new file mode 120000 index 00000000..ee70b698 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/js_object.hpp @@ -0,0 +1 @@ +../../../../../../../src/js_object.hpp \ No newline at end of file diff --git a/tests/react-test-app/android/app/src/main/jni/js_realm.cpp b/tests/react-test-app/android/app/src/main/jni/js_realm.cpp new file mode 120000 index 00000000..bfb79f5b --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/js_realm.cpp @@ -0,0 +1 @@ +../../../../../../../src/js_realm.cpp \ No newline at end of file diff --git a/tests/react-test-app/android/app/src/main/jni/js_realm.hpp b/tests/react-test-app/android/app/src/main/jni/js_realm.hpp new file mode 120000 index 00000000..6ee625aa --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/js_realm.hpp @@ -0,0 +1 @@ +../../../../../../../src/js_realm.hpp \ No newline at end of file diff --git a/tests/react-test-app/android/app/src/main/jni/js_results.cpp b/tests/react-test-app/android/app/src/main/jni/js_results.cpp new file mode 120000 index 00000000..adc6fb57 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/js_results.cpp @@ -0,0 +1 @@ +../../../../../../../src/js_results.cpp \ No newline at end of file diff --git a/tests/react-test-app/android/app/src/main/jni/js_results.hpp b/tests/react-test-app/android/app/src/main/jni/js_results.hpp new file mode 120000 index 00000000..0f6d3f19 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/js_results.hpp @@ -0,0 +1 @@ +../../../../../../../src/js_results.hpp \ No newline at end of file diff --git a/tests/react-test-app/android/app/src/main/jni/js_schema.cpp b/tests/react-test-app/android/app/src/main/jni/js_schema.cpp new file mode 120000 index 00000000..f28579e2 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/js_schema.cpp @@ -0,0 +1 @@ +../../../../../../../src/js_schema.cpp \ No newline at end of file diff --git a/tests/react-test-app/android/app/src/main/jni/js_schema.hpp b/tests/react-test-app/android/app/src/main/jni/js_schema.hpp new file mode 120000 index 00000000..9094c9e2 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/js_schema.hpp @@ -0,0 +1 @@ +../../../../../../../src/js_schema.hpp \ No newline at end of file diff --git a/tests/react-test-app/android/app/src/main/jni/js_util.cpp b/tests/react-test-app/android/app/src/main/jni/js_util.cpp new file mode 120000 index 00000000..31806c26 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/js_util.cpp @@ -0,0 +1 @@ +../../../../../../../src/js_util.cpp \ No newline at end of file diff --git a/tests/react-test-app/android/app/src/main/jni/js_util.hpp b/tests/react-test-app/android/app/src/main/jni/js_util.hpp new file mode 120000 index 00000000..868d03f3 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/js_util.hpp @@ -0,0 +1 @@ +../../../../../../../src/js_util.hpp \ No newline at end of file diff --git a/tests/react-test-app/android/app/src/main/jni/platform.hpp b/tests/react-test-app/android/app/src/main/jni/platform.hpp new file mode 120000 index 00000000..ec9bfa50 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/platform.hpp @@ -0,0 +1 @@ +../../../../../../../src/platform.hpp \ No newline at end of file diff --git a/tests/react-test-app/android/app/src/main/jni/realm-react-android.c b/tests/react-test-app/android/app/src/main/jni/realm-react-android.c new file mode 100644 index 00000000..48288b6d --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/realm-react-android.c @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include +#include + +/* This is a trivial JNI example where we use a native method + * to return a new VM String. See the corresponding Java source + * file located at: + * + * apps/samples/hello-jni/project/src/com/example/hellojni/HelloJni.java + */ +jstring +Java_com_nhachicha_reactjni_module_JniToastModule_stringFromJNI( JNIEnv* env, + jobject thiz ) +{ +#if defined(__arm__) + #if defined(__ARM_ARCH_7A__) + #if defined(__ARM_NEON__) + #if defined(__ARM_PCS_VFP) + #define ABI "armeabi-v7a/NEON (hard-float)" + #else + #define ABI "armeabi-v7a/NEON" + #endif + #else + #if defined(__ARM_PCS_VFP) + #define ABI "armeabi-v7a (hard-float)" + #else + #define ABI "armeabi-v7a" + #endif + #endif + #else + #define ABI "armeabi" + #endif +#elif defined(__i386__) + #define ABI "x86" +#elif defined(__x86_64__) + #define ABI "x86_64" +#elif defined(__mips64) /* mips64el-* toolchain defines __mips__ too */ + #define ABI "mips64" +#elif defined(__mips__) + #define ABI "mips" +#elif defined(__aarch64__) + #define ABI "arm64-v8a" +#else +#define ABI "unknown" +#endif + + return (*env)->NewStringUTF(env, "Hello from JNI ! Compiled with ABI " ABI "."); +} diff --git a/tests/react-test-app/android/app/src/main/jni/rpc.cpp b/tests/react-test-app/android/app/src/main/jni/rpc.cpp new file mode 120000 index 00000000..14753a54 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/rpc.cpp @@ -0,0 +1 @@ +../../../../../../../src/rpc.cpp \ No newline at end of file diff --git a/tests/react-test-app/android/app/src/main/jni/rpc.hpp b/tests/react-test-app/android/app/src/main/jni/rpc.hpp new file mode 120000 index 00000000..5009073c --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/rpc.hpp @@ -0,0 +1 @@ +../../../../../../../src/rpc.hpp \ No newline at end of file diff --git a/tests/react-test-app/android/build.gradle b/tests/react-test-app/android/build.gradle index ccdfc4e3..53ea195b 100644 --- a/tests/react-test-app/android/build.gradle +++ b/tests/react-test-app/android/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.3.1' + classpath 'com.android.tools.build:gradle:1.5.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From d7ca354a271b9abf0ad39a563d74dcbf07b8f7f2 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 30 Nov 2015 14:32:39 -0800 Subject: [PATCH 066/207] add makefile --- .../android/app/src/main/jni/Android.mk | 33 +++++++++++++++++++ tests/react-test-app/android/build.gradle | 2 +- 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 tests/react-test-app/android/app/src/main/jni/Android.mk diff --git a/tests/react-test-app/android/app/src/main/jni/Android.mk b/tests/react-test-app/android/app/src/main/jni/Android.mk new file mode 100644 index 00000000..7264683c --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/Android.mk @@ -0,0 +1,33 @@ +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := librealmreact + +LOCAL_SRC_FILES := \ + js_list.cpp \ + js_results.cpp \ + js_init.cpp \ + js_realm.cpp \ + js_util.cpp \ + realm-react-android.c \ + js_object.cpp \ + js_schema.cpp \ + rpc.cpp \ + +LOCAL_C_INCLUDES := $(LOCAL_PATH)/.. +LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES) + +LOCAL_CFLAGS := \ + +LOCAL_CFLAGS += -Wall -Werror -fexceptions +CXX11_FLAGS := -std=c++11 +LOCAL_CFLAGS += $(CXX11_FLAGS) +LOCAL_EXPORT_CPPFLAGS := $(CXX11_FLAGS) + +LOCAL_SHARED_LIBRARIES := libjsc + +include $(BUILD_STATIC_LIBRARY) + +$(call import-module,jsc) + diff --git a/tests/react-test-app/android/build.gradle b/tests/react-test-app/android/build.gradle index 53ea195b..ccdfc4e3 100644 --- a/tests/react-test-app/android/build.gradle +++ b/tests/react-test-app/android/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.5.0' + classpath 'com.android.tools.build:gradle:1.3.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From 5b06c41163d3c1180337b4985ba3c7a0f47e9dbd Mon Sep 17 00:00:00 2001 From: Nabil Hachicha Date: Tue, 1 Dec 2015 22:27:48 +0000 Subject: [PATCH 067/207] adding module deps + fixing gradle --- src/js_list.cpp | 2 +- tests/react-test-app/android/app/build.gradle | 310 +++++++++-- .../android/app/gradle.properties | 14 + .../react-test-app/android/app/release.gradle | 128 +++++ .../android/app/src/main/AndroidManifest.xml | 9 +- .../java/com/reacttests/MainActivity.java | 126 ++--- .../android/app/src/main/jni/Android.mk | 12 +- .../android/app/src/main/jni/Application.mk | 15 + .../src/main/jni/first-party/fb/Android.mk | 30 + .../src/main/jni/first-party/fb/Countable.h | 47 ++ .../main/jni/first-party/fb/ProgramLocation.h | 50 ++ .../app/src/main/jni/first-party/fb/RefPtr.h | 274 ++++++++++ .../jni/first-party/fb/StaticInitialized.h | 40 ++ .../src/main/jni/first-party/fb/ThreadLocal.h | 118 ++++ .../src/main/jni/first-party/fb/assert.cpp | 41 ++ .../jni/first-party/fb/include/fb/assert.h | 34 ++ .../main/jni/first-party/fb/include/fb/log.h | 361 ++++++++++++ .../app/src/main/jni/first-party/fb/log.cpp | 100 ++++ .../src/main/jni/first-party/fb/noncopyable.h | 21 + .../src/main/jni/first-party/fb/nonmovable.h | 21 + .../app/src/main/jni/first-party/jni/ALog.h | 83 +++ .../src/main/jni/first-party/jni/Android.mk | 35 ++ .../main/jni/first-party/jni/Countable.cpp | 69 +++ .../src/main/jni/first-party/jni/Countable.h | 33 ++ .../app/src/main/jni/first-party/jni/Doxyfile | 18 + .../main/jni/first-party/jni/Environment.cpp | 89 +++ .../main/jni/first-party/jni/Environment.h | 61 +++ .../jni/first-party/jni/GlobalReference.h | 91 ++++ .../main/jni/first-party/jni/LocalReference.h | 37 ++ .../main/jni/first-party/jni/LocalString.cpp | 242 ++++++++ .../main/jni/first-party/jni/LocalString.h | 61 +++ .../src/main/jni/first-party/jni/OnLoad.cpp | 23 + .../main/jni/first-party/jni/Registration.h | 27 + .../jni/first-party/jni/WeakReference.cpp | 43 ++ .../main/jni/first-party/jni/WeakReference.h | 53 ++ .../src/main/jni/first-party/jni/fbjni.cpp | 206 +++++++ .../app/src/main/jni/first-party/jni/fbjni.h | 23 + .../main/jni/first-party/jni/fbjni/Common.h | 68 +++ .../first-party/jni/fbjni/CoreClasses-inl.h | 443 +++++++++++++++ .../jni/first-party/jni/fbjni/CoreClasses.h | 510 +++++++++++++++++ .../jni/first-party/jni/fbjni/Exceptions.cpp | 399 ++++++++++++++ .../jni/first-party/jni/fbjni/Exceptions.h | 130 +++++ .../main/jni/first-party/jni/fbjni/Hybrid.cpp | 76 +++ .../main/jni/first-party/jni/fbjni/Hybrid.h | 277 ++++++++++ .../main/jni/first-party/jni/fbjni/Meta-inl.h | 352 ++++++++++++ .../src/main/jni/first-party/jni/fbjni/Meta.h | 302 ++++++++++ .../jni/fbjni/ReferenceAllocators-inl.h | 123 +++++ .../jni/fbjni/ReferenceAllocators.h | 60 ++ .../first-party/jni/fbjni/References-inl.h | 387 +++++++++++++ .../jni/first-party/jni/fbjni/References.cpp | 41 ++ .../jni/first-party/jni/fbjni/References.h | 515 ++++++++++++++++++ .../first-party/jni/fbjni/Registration-inl.h | 193 +++++++ .../jni/first-party/jni/fbjni/Registration.h | 87 +++ .../jni/first-party/jni/fbjni/TypeTraits.h | 149 +++++ .../main/jni/first-party/jni/jni_helpers.cpp | 195 +++++++ .../main/jni/first-party/jni/jni_helpers.h | 137 +++++ .../src/main/jni/third-party/boost/Android.mk | 11 + .../third-party/double-conversion/Android.mk | 23 + .../src/main/jni/third-party/folly/Android.mk | 41 ++ .../src/main/jni/third-party/glog/Android.mk | 32 ++ .../src/main/jni/third-party/glog/config.h | 179 ++++++ .../src/main/jni/third-party/jsc/Android.mk | 6 + tests/react-test-app/android/build.gradle | 1 + 63 files changed, 7551 insertions(+), 133 deletions(-) create mode 100644 tests/react-test-app/android/app/gradle.properties create mode 100644 tests/react-test-app/android/app/release.gradle create mode 100644 tests/react-test-app/android/app/src/main/jni/Application.mk create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/fb/Android.mk create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/fb/Countable.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/fb/ProgramLocation.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/fb/RefPtr.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/fb/StaticInitialized.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/fb/ThreadLocal.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/fb/assert.cpp create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/fb/include/fb/assert.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/fb/include/fb/log.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/fb/log.cpp create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/fb/noncopyable.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/fb/nonmovable.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/ALog.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/Android.mk create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/Countable.cpp create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/Countable.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/Doxyfile create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/Environment.cpp create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/Environment.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/GlobalReference.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/LocalReference.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/LocalString.cpp create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/LocalString.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/OnLoad.cpp create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/Registration.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/WeakReference.cpp create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/WeakReference.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni.cpp create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Common.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/CoreClasses-inl.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/CoreClasses.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Exceptions.cpp create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Exceptions.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Hybrid.cpp create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Hybrid.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Meta-inl.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Meta.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/ReferenceAllocators-inl.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/ReferenceAllocators.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/References-inl.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/References.cpp create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/References.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Registration-inl.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Registration.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/TypeTraits.h create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/jni_helpers.cpp create mode 100644 tests/react-test-app/android/app/src/main/jni/first-party/jni/jni_helpers.h create mode 100644 tests/react-test-app/android/app/src/main/jni/third-party/boost/Android.mk create mode 100644 tests/react-test-app/android/app/src/main/jni/third-party/double-conversion/Android.mk create mode 100644 tests/react-test-app/android/app/src/main/jni/third-party/folly/Android.mk create mode 100644 tests/react-test-app/android/app/src/main/jni/third-party/glog/Android.mk create mode 100644 tests/react-test-app/android/app/src/main/jni/third-party/glog/config.h create mode 100644 tests/react-test-app/android/app/src/main/jni/third-party/jsc/Android.mk diff --git a/src/js_list.cpp b/src/js_list.cpp index 9b01c15e..e3bce0c9 100644 --- a/src/js_list.cpp +++ b/src/js_list.cpp @@ -6,7 +6,7 @@ #include "js_object.hpp" #include "js_util.hpp" #include "object_accessor.hpp" - +laksdlasd using RJSAccessor = realm::NativeAccessor; using namespace realm; diff --git a/tests/react-test-app/android/app/build.gradle b/tests/react-test-app/android/app/build.gradle index fbd478e3..7608946c 100644 --- a/tests/react-test-app/android/app/build.gradle +++ b/tests/react-test-app/android/app/build.gradle @@ -1,79 +1,271 @@ -apply plugin: "com.android.application" +// Copyright 2015-present Facebook. All Rights Reserved. -/** - * The react.gradle file registers two tasks: bundleDebugJsAndAssets and bundleReleaseJsAndAssets. - * These basically call `react-native bundle` with the correct arguments during the Android build - * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the - * bundle directly from the development server. Below you can see all the possible configurations - * and their defaults. If you decide to add a configuration block, make sure to add it before the - * `apply from: "react.gradle"` line. - * - * project.ext.react = [ - * // the name of the generated asset file containing your JS bundle - * bundleAssetName: "index.android.bundle", - * - * // the entry file for bundle generation - * entryFile: "index.android.js", - * - * // whether to bundle JS and assets in debug mode - * bundleInDebug: false, - * - * // whether to bundle JS and assets in release mode - * bundleInRelease: true, - * - * // the root of your project, i.e. where "package.json" lives - * root: "../../", - * - * // where to put the JS bundle asset in debug mode - * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", - * - * // where to put the JS bundle asset in release mode - * jsBundleDirRelease: "$buildDir/intermediates/assets/release", - * - * // where to put drawable resources / React Native assets, e.g. the ones you use via - * // require('./image.png')), in debug mode - * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", - * - * // where to put drawable resources / React Native assets, e.g. the ones you use via - * // require('./image.png')), in release mode - * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", - * - * // by default the gradle tasks are skipped if none of the JS files or assets change; this means - * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to - * // date; if you have any other folders that you want to ignore for performance reasons (gradle - * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ - * // for example, you might want to remove it from here. - * inputExcludes: ["android/**", "ios/**"] - * ] - */ +apply plugin: 'com.android.library' +apply plugin: 'maven' -apply from: "react.gradle" +apply plugin: 'de.undercouch.download' + +import de.undercouch.gradle.tasks.download.Download +import org.apache.tools.ant.taskdefs.condition.Os +import org.apache.tools.ant.filters.ReplaceTokens + +// We download various C++ open-source dependencies into downloads. +// We then copy both the downloaded code and our custom makefiles and headers into third-party-ndk. +// After that we build native code from src/main/jni with module path pointing at third-party-ndk. + +def downloadsDir = new File("$buildDir/downloads") +def thirdPartyNdkDir = new File("$buildDir/third-party-ndk") + +task createNativeDepsDirectories { + downloadsDir.mkdirs() + thirdPartyNdkDir.mkdirs() +} + +task downloadBoost(dependsOn: createNativeDepsDirectories, type: Download) { + // Use ZIP version as it's faster this way to selectively extract some parts of the archive + src 'https://downloads.sourceforge.net/project/boost/boost/1.57.0/boost_1_57_0.zip' + onlyIfNewer true + overwrite false + dest new File(downloadsDir, 'boost_1_57_0.zip') +} + +task prepareBoost(dependsOn: downloadBoost) { + inputs.files downloadBoost.dest, 'src/main/jni/third-party/boost/Android.mk' + outputs.dir "$thirdPartyNdkDir/boost" + doLast { + copy { + from { zipTree(downloadBoost.dest) } + from 'src/main/jni/third-party/boost/Android.mk' + include 'boost_1_57_0/boost/**/*.hpp', 'Android.mk' + into "$thirdPartyNdkDir/boost" + } + } +} + +task downloadDoubleConversion(dependsOn: createNativeDepsDirectories, type: Download) { + src 'https://github.com/google/double-conversion/archive/v1.1.1.tar.gz' + onlyIfNewer true + overwrite false + dest new File(downloadsDir, 'double-conversion-1.1.1.tar.gz') +} + +task prepareDoubleConversion(dependsOn: downloadDoubleConversion, type: Copy) { + from tarTree(downloadDoubleConversion.dest) + from 'src/main/jni/third-party/double-conversion/Android.mk' + include 'double-conversion-1.1.1/src/**/*', 'Android.mk' + filesMatching('*/src/**/*', {fname -> fname.path = "double-conversion/${fname.name}"}) + includeEmptyDirs = false + into "$thirdPartyNdkDir/double-conversion" +} + +task downloadFolly(dependsOn: createNativeDepsDirectories, type: Download) { + src 'https://github.com/facebook/folly/archive/v0.50.0.tar.gz' + onlyIfNewer true + overwrite false + dest new File(downloadsDir, 'folly-0.50.0.tar.gz'); +} + +task prepareFolly(dependsOn: downloadFolly, type: Copy) { + from tarTree(downloadFolly.dest) + from 'src/main/jni/third-party/folly/Android.mk' + include 'folly-0.50.0/folly/**/*', 'Android.mk' + eachFile {fname -> fname.path = (fname.path - "folly-0.50.0/")} + includeEmptyDirs = false + into "$thirdPartyNdkDir/folly" +} + +task downloadGlog(dependsOn: createNativeDepsDirectories, type: Download) { + src 'https://github.com/google/glog/archive/v0.3.3.tar.gz' + onlyIfNewer true + overwrite false + dest new File(downloadsDir, 'glog-0.3.3.tar.gz') +} + +// Prepare glog sources to be compiled, this task will perform steps that normally shoudl've been +// executed by automake. This way we can avoid dependencies on make/automake +task prepareGlog(dependsOn: downloadGlog, type: Copy) { + from tarTree(downloadGlog.dest) + from 'src/main/jni/third-party/glog/' + include 'glog-0.3.3/src/**/*', 'Android.mk', 'config.h' + includeEmptyDirs = false + filesMatching('**/*.h.in') { + filter(ReplaceTokens, tokens: [ + ac_cv_have_unistd_h: '1', + ac_cv_have_stdint_h: '1', + ac_cv_have_systypes_h: '1', + ac_cv_have_inttypes_h: '1', + ac_cv_have_libgflags: '0', + ac_google_start_namespace: 'namespace google {', + ac_cv_have_uint16_t: '1', + ac_cv_have_u_int16_t: '1', + ac_cv_have___uint16: '0', + ac_google_end_namespace: '}', + ac_cv_have___builtin_expect: '1', + ac_google_namespace: 'google', + ac_cv___attribute___noinline: '__attribute__ ((noinline))', + ac_cv___attribute___noreturn: '__attribute__ ((noreturn))', + ac_cv___attribute___printf_4_5: '__attribute__((__format__ (__printf__, 4, 5)))' + ]) + it.path = (it.name - '.in') + } + into "$thirdPartyNdkDir/glog" +} + +task downloadJSCHeaders(type: Download) { + def jscAPIBaseURL = 'https://svn.webkit.org/repository/webkit/!svn/bc/174650/trunk/Source/JavaScriptCore/API/' + def jscHeaderFiles = ['JSBase.h', 'JSContextRef.h', 'JSObjectRef.h', 'JSRetainPtr.h', 'JSStringRef.h', 'JSValueRef.h', 'WebKitAvailability.h', 'JavaScriptCore.h', 'JavaScript.h', 'JSStringRefCF.h'] + def output = new File(downloadsDir, 'jsc') + output.mkdirs() + src(jscHeaderFiles.collect { headerName -> "$jscAPIBaseURL$headerName" }) + onlyIfNewer true + overwrite false + dest output +} + +// Create Android.mk library module based on so files from mvn + include headers fetched from webkit.org +task prepareJSC(dependsOn: downloadJSCHeaders) << { + copy { + from zipTree(configurations.compile.fileCollection { dep -> dep.name == 'android-jsc' }.singleFile) + from {downloadJSCHeaders.dest} + from 'src/main/jni/third-party/jsc/Android.mk' + include 'jni/**/*.so', '*.h', 'Android.mk' + filesMatching('*.h', { fname -> fname.path = "JavaScriptCore/${fname.path}"}) + into "$thirdPartyNdkDir/jsc"; + } +} + +def getNdkBuildName() { + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + return "ndk-build.cmd" + } else { + return "ndk-build" + } +} + +def findNdkBuildFullPath() { + // we allow to provide full path to ndk-build tool + if (hasProperty('ndk.command')) { + return property('ndk.command') + } + // or just a path to the containing directory + if (hasProperty('ndk.path')) { + def ndkDir = property('ndk.path') + return new File(ndkDir, getNdkBuildName()).getAbsolutePath() + } + if (System.getenv('ANDROID_NDK') != null) { + def ndkDir = System.getenv('ANDROID_NDK') + return new File(ndkDir, getNdkBuildName()).getAbsolutePath() + } + def ndkDir = android.hasProperty('plugin') ? android.plugin.ndkFolder : + plugins.getPlugin('com.android.library').sdkHandler.getNdkFolder() + if (ndkDir) { + return new File(ndkDir, getNdkBuildName()).getAbsolutePath() + } + return null +} + +def getNdkBuildFullPath() { + def ndkBuildFullPath = findNdkBuildFullPath() + if (ndkBuildFullPath == null) { + throw new GradleScriptException( + "ndk-build binary cannot be found, check if you've set " + + "\$ANDROID_NDK environment variable correctly or if ndk.dir is " + + "setup in local.properties", + null) + } + if (!new File(ndkBuildFullPath).canExecute()) { + throw new GradleScriptException( + "ndk-build binary " + ndkBuildFullPath + " doesn't exist or isn't executable.\n" + + "Check that the \$ANDROID_NDK environment variable, or ndk.dir in local.proerties, is set correctly.\n" + + "(On Windows, make sure you escape backslashes in local.properties or use forward slashes, e.g. C:\\\\ndk or C:/ndk rather than C:\\ndk)", + null) + } + return ndkBuildFullPath +} + +task buildReactNdkLib(dependsOn: [prepareJSC, prepareBoost, prepareDoubleConversion, prepareFolly, prepareGlog], type: Exec) { + inputs.file('src/main/jni') + outputs.dir("$buildDir/react-ndk/all") + commandLine getNdkBuildFullPath(), + 'NDK_PROJECT_PATH=null', + "NDK_APPLICATION_MK=$projectDir/src/main/jni/Application.mk", + 'NDK_OUT=' + temporaryDir, + "NDK_LIBS_OUT=$buildDir/react-ndk/all", + "THIRD_PARTY_NDK_DIR=$buildDir/third-party-ndk", + '-C', file('src/main/jni').absolutePath, + '-B', + 'NDK_LOG=1', + '--jobs', Runtime.runtime.availableProcessors() +} + +task cleanReactNdkLib(type: Exec) { + commandLine getNdkBuildFullPath(), + '-C', file('src/main/jni').absolutePath, + 'clean' +} + +task packageReactNdkLibs(dependsOn: buildReactNdkLib, type: Copy) { + from "$buildDir/react-ndk/all" + exclude '**/libjsc.so' + into "$buildDir/react-ndk/exported" +} android { compileSdkVersion 23 buildToolsVersion "23.0.1" defaultConfig { - applicationId "com.reacttests" minSdkVersion 16 targetSdkVersion 22 versionCode 1 versionName "1.0" + ndk { - moduleName = "realm-react-android" - abiFilters "armeabi-v7a", "x86" + moduleName "reactnativejni" } + + buildConfigField 'boolean', 'IS_INTERNAL_BUILD', 'false' } - buildTypes { - release { - minifyEnabled false // Set this to true to enable Proguard - proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" - } + + sourceSets.main { + jni.srcDirs = [] + jniLibs.srcDir "$buildDir/react-ndk/exported" + res.srcDirs = ['src/main/res/devsupport', 'src/main/res/shell'] + } + + tasks.withType(JavaCompile) { + compileTask -> compileTask.dependsOn packageReactNdkLibs + } + + clean.dependsOn cleanReactNdkLib + + lintOptions { + abortOnError false } } dependencies { - compile fileTree(dir: "libs", include: ["*.jar"]) - compile "com.android.support:appcompat-v7:23.0.1" - compile "com.facebook.react:react-native:0.14.+" + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.android.support:appcompat-v7:23.0.1' + compile 'com.android.support:recyclerview-v7:23.0.1' + compile 'com.facebook.fresco:fresco:0.8.1' + compile 'com.facebook.fresco:imagepipeline-okhttp:0.8.1' + compile 'com.facebook.stetho:stetho:1.2.0' + compile 'com.facebook.stetho:stetho-okhttp:1.2.0' + compile 'com.fasterxml.jackson.core:jackson-core:2.2.3' + compile 'com.google.code.findbugs:jsr305:3.0.0' + compile 'com.squareup.okhttp:okhttp:2.5.0' + compile 'com.squareup.okhttp:okhttp-ws:2.5.0' + compile 'com.squareup.okio:okio:1.6.0' + compile 'org.webkit:android-jsc:r174650' + + testCompile "junit:junit:${JUNIT_VERSION}" + testCompile "org.powermock:powermock-api-mockito:${POWERMOCK_VERSION}" + testCompile "org.powermock:powermock-module-junit4-rule:${POWERMOCK_VERSION}" + testCompile "org.powermock:powermock-classloading-xstream:${POWERMOCK_VERSION}" + testCompile "org.mockito:mockito-core:${MOCKITO_CORE_VERSION}" + testCompile "org.easytesting:fest-assert-core:${FEST_ASSERT_CORE_VERSION}" + testCompile("org.robolectric:robolectric:${ROBOLECTRIC_VERSION}") } + +apply from: 'release.gradle' diff --git a/tests/react-test-app/android/app/gradle.properties b/tests/react-test-app/android/app/gradle.properties new file mode 100644 index 00000000..e038115d --- /dev/null +++ b/tests/react-test-app/android/app/gradle.properties @@ -0,0 +1,14 @@ +VERSION_NAME=0.12.0-SNAPSHOT +GROUP=com.facebook.react + +POM_NAME=ReactNative +POM_ARTIFACT_ID=react-native +POM_PACKAGING=aar + +android.useDeprecatedNdk=true + +MOCKITO_CORE_VERSION=1.+ +POWERMOCK_VERSION=1.6.2 +ROBOLECTRIC_VERSION=3.0 +JUNIT_VERSION=4.12 +FEST_ASSERT_CORE_VERSION=2.0M10 diff --git a/tests/react-test-app/android/app/release.gradle b/tests/react-test-app/android/app/release.gradle new file mode 100644 index 00000000..9b8cd35a --- /dev/null +++ b/tests/react-test-app/android/app/release.gradle @@ -0,0 +1,128 @@ +// Copyright 2015-present Facebook. All Rights Reserved. + +apply plugin: 'maven' +apply plugin: 'signing' + +// Gradle tasks for publishing to maven +// 1) To install in local maven repo use :installArchives task +// 2) To upload artifact to maven central use: :uploadArchives (you'd need to have the permission to do that) + +def isReleaseBuild() { + return VERSION_NAME.contains('SNAPSHOT') == false +} + +def getRepositoryUrl() { + return hasProperty('repositoryUrl') ? property('repositoryUrl') : 'https://oss.sonatype.org/service/local/staging/deploy/maven2/' +} + +def getRepositoryUsername() { + return hasProperty('repositoryUsername') ? property('repositoryUsername') : '' +} + +def getRepositoryPassword() { + return hasProperty('repositoryPassword') ? property('repositoryPassword') : '' +} + +def configureReactNativePom(def pom) { + pom.project { + name POM_NAME + artifactId POM_ARTIFACT_ID + packaging POM_PACKAGING + description 'A framework for building native apps with React' + url 'https://github.com/facebook/react-native' + + scm { + url 'https://github.com/facebook/react-native.git' + connection 'scm:git:https://github.com/facebook/react-native.git' + developerConnection 'scm:git:git@github.com:facebook/react-native.git' + } + + licenses { + license { + name 'BSD License' + url 'https://github.com/facebook/react-native/blob/master/LICENSE' + distribution 'repo' + } + } + + developers { + developer { + id 'facebook' + name 'Facebook' + } + } + } +} + +afterEvaluate { project -> + + task androidJavadoc(type: Javadoc) { + source = android.sourceSets.main.java.srcDirs + classpath += files(android.bootClasspath) + classpath += files(project.getConfigurations().getByName('compile').asList()) + include '**/*.java' + exclude '**/ReactBuildConfig.java' + } + + task androidJavadocJar(type: Jar, dependsOn: androidJavadoc) { + classifier = 'javadoc' + from androidJavadoc.destinationDir + } + + task androidSourcesJar(type: Jar) { + classifier = 'sources' + from android.sourceSets.main.java.srcDirs + include '**/*.java' + } + + android.libraryVariants.all { variant -> + def name = variant.name.capitalize() + task "jar${name}"(type: Jar, dependsOn: variant.javaCompile) { + from variant.javaCompile.destinationDir + } + } + + artifacts { + archives androidSourcesJar + // TODO Make Javadoc generation work with Java 1.8, currently only works with 1.7 + // archives androidJavadocJar + } + + version = VERSION_NAME + group = GROUP + + signing { + required { isReleaseBuild() && gradle.taskGraph.hasTask('uploadArchives') } + sign configurations.archives + } + + uploadArchives { + configuration = configurations.archives + repositories.mavenDeployer { + beforeDeployment { + MavenDeployment deployment -> signing.signPom(deployment) + } + + repository(url: getRepositoryUrl()) { + authentication( + userName: getRepositoryUsername(), + password: getRepositoryPassword()) + + } + + configureReactNativePom pom + } + } + + task installArchives(type: Upload) { + configuration = configurations.archives + repositories.mavenDeployer { + beforeDeployment { + MavenDeployment deployment -> signing.signPom(deployment) + } + + repository url: "file://${System.properties['user.home']}/.m2/repository" + configureReactNativePom pom + } + } +} diff --git a/tests/react-test-app/android/app/src/main/AndroidManifest.xml b/tests/react-test-app/android/app/src/main/AndroidManifest.xml index 6e884d83..3f4f9d4e 100644 --- a/tests/react-test-app/android/app/src/main/AndroidManifest.xml +++ b/tests/react-test-app/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,4 @@ - @@ -19,4 +19,11 @@ + --> + + + + + + diff --git a/tests/react-test-app/android/app/src/main/java/com/reacttests/MainActivity.java b/tests/react-test-app/android/app/src/main/java/com/reacttests/MainActivity.java index ebd573a3..6dc85930 100644 --- a/tests/react-test-app/android/app/src/main/java/com/reacttests/MainActivity.java +++ b/tests/react-test-app/android/app/src/main/java/com/reacttests/MainActivity.java @@ -1,78 +1,78 @@ -package com.reacttests; +// package com.reacttests; -import android.app.Activity; -import android.os.Bundle; -import android.view.KeyEvent; +// import android.app.Activity; +// import android.os.Bundle; +// import android.view.KeyEvent; -import com.facebook.react.LifecycleState; -import com.facebook.react.ReactInstanceManager; -import com.facebook.react.ReactRootView; -import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; -import com.facebook.react.shell.MainReactPackage; -import com.facebook.soloader.SoLoader; +// import com.facebook.react.LifecycleState; +// import com.facebook.react.ReactInstanceManager; +// import com.facebook.react.ReactRootView; +// import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; +// import com.facebook.react.shell.MainReactPackage; +// import com.facebook.soloader.SoLoader; -public class MainActivity extends Activity implements DefaultHardwareBackBtnHandler { +// public class MainActivity extends Activity implements DefaultHardwareBackBtnHandler { - private ReactInstanceManager mReactInstanceManager; - private ReactRootView mReactRootView; +// private ReactInstanceManager mReactInstanceManager; +// private ReactRootView mReactRootView; - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mReactRootView = new ReactRootView(this); +// @Override +// protected void onCreate(Bundle savedInstanceState) { +// super.onCreate(savedInstanceState); +// mReactRootView = new ReactRootView(this); - mReactInstanceManager = ReactInstanceManager.builder() - .setApplication(getApplication()) - .setBundleAssetName("index.android.bundle") - .setJSMainModuleName("index.android") - .addPackage(new MainReactPackage()) - .setUseDeveloperSupport(BuildConfig.DEBUG) - .setInitialLifecycleState(LifecycleState.RESUMED) - .build(); +// mReactInstanceManager = ReactInstanceManager.builder() +// .setApplication(getApplication()) +// .setBundleAssetName("index.android.bundle") +// .setJSMainModuleName("index.android") +// .addPackage(new MainReactPackage()) +// .setUseDeveloperSupport(BuildConfig.DEBUG) +// .setInitialLifecycleState(LifecycleState.RESUMED) +// .build(); - mReactRootView.startReactApplication(mReactInstanceManager, "ReactTests", null); +// mReactRootView.startReactApplication(mReactInstanceManager, "ReactTests", null); - setContentView(mReactRootView); - } +// setContentView(mReactRootView); +// } - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) { - mReactInstanceManager.showDevOptionsDialog(); - return true; - } - return super.onKeyUp(keyCode, event); - } +// @Override +// public boolean onKeyUp(int keyCode, KeyEvent event) { +// if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) { +// mReactInstanceManager.showDevOptionsDialog(); +// return true; +// } +// return super.onKeyUp(keyCode, event); +// } - @Override - public void onBackPressed() { - if (mReactInstanceManager != null) { - mReactInstanceManager.onBackPressed(); - } else { - super.onBackPressed(); - } - } +// @Override +// public void onBackPressed() { +// if (mReactInstanceManager != null) { +// mReactInstanceManager.onBackPressed(); +// } else { +// super.onBackPressed(); +// } +// } - @Override - public void invokeDefaultOnBackPressed() { - super.onBackPressed(); - } +// @Override +// public void invokeDefaultOnBackPressed() { +// super.onBackPressed(); +// } - @Override - protected void onPause() { - super.onPause(); +// @Override +// protected void onPause() { +// super.onPause(); - if (mReactInstanceManager != null) { - mReactInstanceManager.onPause(); - } - } +// if (mReactInstanceManager != null) { +// mReactInstanceManager.onPause(); +// } +// } - @Override - protected void onResume() { - super.onResume(); +// @Override +// protected void onResume() { +// super.onResume(); - if (mReactInstanceManager != null) { - mReactInstanceManager.onResume(this); - } - } -} +// if (mReactInstanceManager != null) { +// mReactInstanceManager.onResume(this); +// } +// } +// } diff --git a/tests/react-test-app/android/app/src/main/jni/Android.mk b/tests/react-test-app/android/app/src/main/jni/Android.mk index 7264683c..0227515b 100644 --- a/tests/react-test-app/android/app/src/main/jni/Android.mk +++ b/tests/react-test-app/android/app/src/main/jni/Android.mk @@ -2,7 +2,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_MODULE := librealmreact +LOCAL_MODULE := nabil LOCAL_SRC_FILES := \ js_list.cpp \ @@ -10,15 +10,9 @@ LOCAL_SRC_FILES := \ js_init.cpp \ js_realm.cpp \ js_util.cpp \ - realm-react-android.c \ js_object.cpp \ js_schema.cpp \ - rpc.cpp \ - -LOCAL_C_INCLUDES := $(LOCAL_PATH)/.. -LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES) - -LOCAL_CFLAGS := \ + rpc.cpp \ LOCAL_CFLAGS += -Wall -Werror -fexceptions CXX11_FLAGS := -std=c++11 @@ -27,7 +21,7 @@ LOCAL_EXPORT_CPPFLAGS := $(CXX11_FLAGS) LOCAL_SHARED_LIBRARIES := libjsc -include $(BUILD_STATIC_LIBRARY) +include $(BUILD_SHARED_LIBRARY) $(call import-module,jsc) diff --git a/tests/react-test-app/android/app/src/main/jni/Application.mk b/tests/react-test-app/android/app/src/main/jni/Application.mk new file mode 100644 index 00000000..f403e5ba --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/Application.mk @@ -0,0 +1,15 @@ +APP_BUILD_SCRIPT := Android.mk + +APP_ABI := armeabi-v7a x86 +APP_PLATFORM := android-9 + +APP_MK_DIR := $(dir $(lastword $(MAKEFILE_LIST))) + +NDK_MODULE_PATH := $(APP_MK_DIR)$(HOST_DIRSEP)$(THIRD_PARTY_NDK_DIR)$(HOST_DIRSEP)$(APP_MK_DIR)first-party + +APP_STL := gnustl_shared + +# Make sure every shared lib includes a .note.gnu.build-id header +APP_LDFLAGS := -Wl,--build-id + +NDK_TOOLCHAIN_VERSION := 4.8 diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/fb/Android.mk b/tests/react-test-app/android/app/src/main/jni/first-party/fb/Android.mk new file mode 100644 index 00000000..3361c433 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/fb/Android.mk @@ -0,0 +1,30 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + assert.cpp \ + log.cpp \ + +LOCAL_C_INCLUDES := $(LOCAL_PATH)/.. $(LOCAL_PATH)/include +LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/.. $(LOCAL_PATH)/include + +LOCAL_CFLAGS := -DLOG_TAG=\"libfb\" +LOCAL_CFLAGS += -Wall -Werror +# include/utils/threads.h has unused parameters +LOCAL_CFLAGS += -Wno-unused-parameter +ifeq ($(TOOLCHAIN_PERMISSIVE),true) + LOCAL_CFLAGS += -Wno-error=unused-but-set-variable +endif +LOCAL_CFLAGS += -DHAVE_POSIX_CLOCKS + +CXX11_FLAGS := -std=c++11 +LOCAL_CFLAGS += $(CXX11_FLAGS) + +LOCAL_EXPORT_CPPFLAGS := $(CXX11_FLAGS) + +LOCAL_LDLIBS := -llog -ldl -landroid +LOCAL_EXPORT_LDLIBS := -llog + +LOCAL_MODULE := libfb + +include $(BUILD_SHARED_LIBRARY) \ No newline at end of file diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/fb/Countable.h b/tests/react-test-app/android/app/src/main/jni/first-party/fb/Countable.h new file mode 100644 index 00000000..1e402a3f --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/fb/Countable.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once +#include +#include +#include +#include +#include + +namespace facebook { + +class Countable : public noncopyable, public nonmovable { +public: + // RefPtr expects refcount to start at 0 + Countable() : m_refcount(0) {} + virtual ~Countable() + { + FBASSERT(m_refcount == 0); + } + +private: + void ref() { + ++m_refcount; + } + + void unref() { + if (0 == --m_refcount) { + delete this; + } + } + + bool hasOnlyOneRef() const { + return m_refcount == 1; + } + + template friend class RefPtr; + std::atomic m_refcount; +}; + +} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/fb/ProgramLocation.h b/tests/react-test-app/android/app/src/main/jni/first-party/fb/ProgramLocation.h new file mode 100644 index 00000000..36f7737f --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/fb/ProgramLocation.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once +#include +#include +#include + +namespace facebook { + +#define FROM_HERE facebook::ProgramLocation(__FUNCTION__, __FILE__, __LINE__) + +class ProgramLocation { +public: + ProgramLocation() : m_functionName("Unspecified"), m_fileName("Unspecified"), m_lineNumber(0) {} + + ProgramLocation(const char* functionName, const char* fileName, int line) : + m_functionName(functionName), + m_fileName(fileName), + m_lineNumber(line) + {} + + const char* functionName() const { return m_functionName; } + const char* fileName() const { return m_fileName; } + int lineNumber() const { return m_lineNumber; } + + std::string asFormattedString() const { + std::stringstream str; + str << "Function " << m_functionName << " in file " << m_fileName << ":" << m_lineNumber; + return str.str(); + } + + bool operator==(const ProgramLocation& other) const { + // Assumes that the strings are static + return (m_functionName == other.m_functionName) && (m_fileName == other.m_fileName) && m_lineNumber == other.m_lineNumber; + } + +private: + const char* m_functionName; + const char* m_fileName; + int m_lineNumber; +}; + +} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/fb/RefPtr.h b/tests/react-test-app/android/app/src/main/jni/first-party/fb/RefPtr.h new file mode 100644 index 00000000..d21fe697 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/fb/RefPtr.h @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once +#include +#include + +namespace facebook { + +// Reference counting smart pointer. This is designed to work with the +// Countable class or other implementations in the future. It is designed in a +// way to be both efficient and difficult to misuse. Typical usage is very +// simple once you learn the patterns (and the compiler will help!): +// +// By default, the internal pointer is null. +// RefPtr ref; +// +// Object creation requires explicit construction: +// RefPtr ref = createNew(...); +// +// Or if the constructor is not public: +// RefPtr ref = adoptRef(new Foo(...)); +// +// But you can implicitly create from nullptr: +// RefPtr maybeRef = cond ? ref : nullptr; +// +// Move/Copy Construction/Assignment are straightforward: +// RefPtr ref2 = ref; +// ref = std::move(ref2); +// +// Destruction automatically drops the RefPtr's reference as expected. +// +// Upcasting is implicit but downcasting requires an explicit cast: +// struct Bar : public Foo {}; +// RefPtr barRef = static_cast>(ref); +// ref = barRef; +// +template +class RefPtr { +public: + constexpr RefPtr() : + m_ptr(nullptr) + {} + + // Allow implicit construction from a pointer only from nullptr + constexpr RefPtr(std::nullptr_t ptr) : + m_ptr(nullptr) + {} + + RefPtr(const RefPtr& ref) : + m_ptr(ref.m_ptr) + { + refIfNecessary(m_ptr); + } + + // Only allow implicit upcasts. A downcast will result in a compile error + // unless you use static_cast (which will end up invoking the explicit + // operator below). + template + RefPtr(const RefPtr& ref, typename std::enable_if::value, U>::type* = nullptr) : + m_ptr(ref.get()) + { + refIfNecessary(m_ptr); + } + + RefPtr(RefPtr&& ref) : + m_ptr(nullptr) + { + *this = std::move(ref); + } + + // Only allow implicit upcasts. A downcast will result in a compile error + // unless you use static_cast (which will end up invoking the explicit + // operator below). + template + RefPtr(RefPtr&& ref, typename std::enable_if::value, U>::type* = nullptr) : + m_ptr(nullptr) + { + *this = std::move(ref); + } + + ~RefPtr() { + unrefIfNecessary(m_ptr); + m_ptr = nullptr; + } + + RefPtr& operator=(const RefPtr& ref) { + if (m_ptr != ref.m_ptr) { + unrefIfNecessary(m_ptr); + m_ptr = ref.m_ptr; + refIfNecessary(m_ptr); + } + return *this; + } + + // The STL assumes rvalue references are unique and for simplicity's sake, we + // make the same assumption here, that &ref != this. + RefPtr& operator=(RefPtr&& ref) { + unrefIfNecessary(m_ptr); + m_ptr = ref.m_ptr; + ref.m_ptr = nullptr; + return *this; + } + + template + RefPtr& operator=(RefPtr&& ref) { + unrefIfNecessary(m_ptr); + m_ptr = ref.m_ptr; + ref.m_ptr = nullptr; + return *this; + } + + void reset() { + unrefIfNecessary(m_ptr); + m_ptr = nullptr; + } + + T* get() const { + return m_ptr; + } + + T* operator->() const { + return m_ptr; + } + + T& operator*() const { + return *m_ptr; + } + + template + explicit operator RefPtr () const; + + explicit operator bool() const { + return m_ptr ? true : false; + } + + bool isTheLastRef() const { + FBASSERT(m_ptr); + return m_ptr->hasOnlyOneRef(); + } + + // Creates a strong reference from a raw pointer, assuming that is already + // referenced from some other RefPtr. This should be used sparingly. + static inline RefPtr assumeAlreadyReffed(T* ptr) { + return RefPtr(ptr, ConstructionMode::External); + } + + // Creates a strong reference from a raw pointer, assuming that it points to a + // freshly-created object. See the documentation for RefPtr for usage. + static inline RefPtr adoptRef(T* ptr) { + return RefPtr(ptr, ConstructionMode::Adopted); + } + +private: + enum class ConstructionMode { + Adopted, + External + }; + + RefPtr(T* ptr, ConstructionMode mode) : + m_ptr(ptr) + { + FBASSERTMSGF(ptr, "Got null pointer in %s construction mode", mode == ConstructionMode::Adopted ? "adopted" : "external"); + ptr->ref(); + if (mode == ConstructionMode::Adopted) { + FBASSERT(ptr->hasOnlyOneRef()); + } + } + + static inline void refIfNecessary(T* ptr) { + if (ptr) { + ptr->ref(); + } + } + static inline void unrefIfNecessary(T* ptr) { + if (ptr) { + ptr->unref(); + } + } + + template friend class RefPtr; + + T* m_ptr; +}; + +// Creates a strong reference from a raw pointer, assuming that is already +// referenced from some other RefPtr and that it is non-null. This should be +// used sparingly. +template +static inline RefPtr assumeAlreadyReffed(T* ptr) { + return RefPtr::assumeAlreadyReffed(ptr); +} + +// As above, but tolerant of nullptr. +template +static inline RefPtr assumeAlreadyReffedOrNull(T* ptr) { + return ptr ? RefPtr::assumeAlreadyReffed(ptr) : nullptr; +} + +// Creates a strong reference from a raw pointer, assuming that it points to a +// freshly-created object. See the documentation for RefPtr for usage. +template +static inline RefPtr adoptRef(T* ptr) { + return RefPtr::adoptRef(ptr); +} + +template +static inline RefPtr createNew(Args&&... arguments) { + return RefPtr::adoptRef(new T(std::forward(arguments)...)); +} + +template template +RefPtr::operator RefPtr() const { + static_assert(std::is_base_of::value, "Invalid static cast"); + return assumeAlreadyReffedOrNull(static_cast(m_ptr)); +} + +template +inline bool operator==(const RefPtr& a, const RefPtr& b) { + return a.get() == b.get(); +} + +template +inline bool operator!=(const RefPtr& a, const RefPtr& b) { + return a.get() != b.get(); +} + +template +inline bool operator==(const RefPtr& ref, U* ptr) { + return ref.get() == ptr; +} + +template +inline bool operator!=(const RefPtr& ref, U* ptr) { + return ref.get() != ptr; +} + +template +inline bool operator==(U* ptr, const RefPtr& ref) { + return ref.get() == ptr; +} + +template +inline bool operator!=(U* ptr, const RefPtr& ref) { + return ref.get() != ptr; +} + +template +inline bool operator==(const RefPtr& ref, std::nullptr_t ptr) { + return ref.get() == ptr; +} + +template +inline bool operator!=(const RefPtr& ref, std::nullptr_t ptr) { + return ref.get() != ptr; +} + +template +inline bool operator==(std::nullptr_t ptr, const RefPtr& ref) { + return ref.get() == ptr; +} + +template +inline bool operator!=(std::nullptr_t ptr, const RefPtr& ref) { + return ref.get() != ptr; +} + +} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/fb/StaticInitialized.h b/tests/react-test-app/android/app/src/main/jni/first-party/fb/StaticInitialized.h new file mode 100644 index 00000000..6d943972 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/fb/StaticInitialized.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once +#include +#include + +namespace facebook { + +// Class that lets you declare a global but does not add a static constructor +// to the binary. Eventually I'd like to have this auto-initialize in a +// multithreaded environment but for now it's easiest just to use manual +// initialization. +template +class StaticInitialized { +public: + constexpr StaticInitialized() : + m_instance(nullptr) + {} + + template + void initialize(Args&&... arguments) { + FBASSERT(!m_instance); + m_instance = new T(std::forward(arguments)...); + } + + T* operator->() const { + return m_instance; + } +private: + T* m_instance; +}; + +} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/fb/ThreadLocal.h b/tests/react-test-app/android/app/src/main/jni/first-party/fb/ThreadLocal.h new file mode 100644 index 00000000..d86a2f0d --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/fb/ThreadLocal.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include +#include + +#include + +namespace facebook { + +/////////////////////////////////////////////////////////////////////////////// + +/** + * A thread-local object is a "global" object within a thread. This is useful + * for writing apartment-threaded code, where nothing is actullay shared + * between different threads (hence no locking) but those variables are not + * on stack in local scope. To use it, just do something like this, + * + * ThreadLocal static_object; + * static_object->data_ = ...; + * static_object->doSomething(); + * + * ThreadLocal static_number; + * int value = *static_number; + * + * So, syntax-wise it's similar to pointers. T can be primitive types, and if + * it's a class, there has to be a default constructor. + */ +template +class ThreadLocal { +public: + /** + * Constructor that has to be called from a thread-neutral place. + */ + ThreadLocal() : + m_key(0), + m_cleanup(OnThreadExit) { + initialize(); + } + + /** + * As above but with a custom cleanup function + */ + typedef void (*CleanupFunction)(void* obj); + explicit ThreadLocal(CleanupFunction cleanup) : + m_key(0), + m_cleanup(cleanup) { + FBASSERT(cleanup); + initialize(); + } + + /** + * Access object's member or method through this operator overload. + */ + T *operator->() const { + return get(); + } + + T &operator*() const { + return *get(); + } + + T *get() const { + return (T*)pthread_getspecific(m_key); + } + + T* release() { + T* obj = get(); + pthread_setspecific(m_key, NULL); + return obj; + } + + void reset(T* other = NULL) { + T* old = (T*)pthread_getspecific(m_key); + if (old != other) { + FBASSERT(m_cleanup); + m_cleanup(old); + pthread_setspecific(m_key, other); + } + } + +private: + void initialize() { + int ret = pthread_key_create(&m_key, m_cleanup); + if (ret != 0) { + const char *msg = "(unknown error)"; + switch (ret) { + case EAGAIN: + msg = "PTHREAD_KEYS_MAX (1024) is exceeded"; + break; + case ENOMEM: + msg = "Out-of-memory"; + break; + } + (void) msg; + FBASSERTMSGF(0, "pthread_key_create failed: %d %s", ret, msg); + } + } + + static void OnThreadExit(void *obj) { + if (NULL != obj) { + delete (T*)obj; + } + } + + pthread_key_t m_key; + CleanupFunction m_cleanup; +}; + +} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/fb/assert.cpp b/tests/react-test-app/android/app/src/main/jni/first-party/fb/assert.cpp new file mode 100644 index 00000000..db9a4315 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/fb/assert.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include +#include + +#include +#include + +namespace facebook { + +#define ASSERT_BUF_SIZE 4096 +static char sAssertBuf[ASSERT_BUF_SIZE]; +static AssertHandler gAssertHandler; + +void assertInternal(const char* formatstr ...) { + va_list va_args; + va_start(va_args, formatstr); + vsnprintf(sAssertBuf, sizeof(sAssertBuf), formatstr, va_args); + va_end(va_args); + if (gAssertHandler != NULL) { + gAssertHandler(sAssertBuf); + } + FBLOG(LOG_FATAL, "fbassert", "%s", sAssertBuf); + // crash at this specific address so that we can find our crashes easier + *(int*)0xdeadb00c = 0; + // let the compiler know we won't reach the end of the function + __builtin_unreachable(); +} + +void setAssertHandler(AssertHandler assertHandler) { + gAssertHandler = assertHandler; +} + +} // namespace facebook diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/fb/include/fb/assert.h b/tests/react-test-app/android/app/src/main/jni/first-party/fb/include/fb/assert.h new file mode 100644 index 00000000..648ab2cd --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/fb/include/fb/assert.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#ifndef FBASSERT_H +#define FBASSERT_H + +namespace facebook { +#define ENABLE_FBASSERT 1 + +#if ENABLE_FBASSERT +#define FBASSERTMSGF(expr, msg, ...) !(expr) ? facebook::assertInternal("Assert (%s:%d): " msg, __FILE__, __LINE__, ##__VA_ARGS__) : (void) 0 +#else +#define FBASSERTMSGF(expr, msg, ...) +#endif // ENABLE_FBASSERT + +#define FBASSERT(expr) FBASSERTMSGF(expr, "%s", #expr) + +#define FBCRASH(msg, ...) facebook::assertInternal("Fatal error (%s:%d): " msg, __FILE__, __LINE__, ##__VA_ARGS__) +#define FBUNREACHABLE() facebook::assertInternal("This code should be unreachable (%s:%d)", __FILE__, __LINE__) + +void assertInternal(const char* formatstr, ...) __attribute__((noreturn)); + +// This allows storing the assert message before the current process terminates due to a crash +typedef void (*AssertHandler)(const char* message); +void setAssertHandler(AssertHandler assertHandler); + +} // namespace facebook +#endif // FBASSERT_H diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/fb/include/fb/log.h b/tests/react-test-app/android/app/src/main/jni/first-party/fb/include/fb/log.h new file mode 100644 index 00000000..4e08b558 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/fb/include/fb/log.h @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2005 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * FB Wrapper for logging functions. + * + * The android logging API uses the macro "LOG()" for its logic, which means + * that it conflicts with random other places that use LOG for their own + * purposes and doesn't work right half the places you include it + * + * FBLOG uses exactly the same semantics (FBLOGD for debug etc) but because of + * the FB prefix it's strictly better. FBLOGV also gets stripped out based on + * whether NDEBUG is set, but can be overridden by FBLOG_NDEBUG + * + * Most of the rest is a copy of with minor changes. + */ + +// +// C/C++ logging functions. See the logging documentation for API details. +// +// We'd like these to be available from C code (in case we import some from +// somewhere), so this has a C interface. +// +// The output will be correct when the log file is shared between multiple +// threads and/or multiple processes so long as the operating system +// supports O_APPEND. These calls have mutex-protected data structures +// and so are NOT reentrant. Do not use LOG in a signal handler. +// +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef ANDROID +#include +#else + // These declarations are needed for our internal use even on non-Android builds. + // (they are borrowed from ) + + /* + * Android log priority values, in ascending priority order. + */ + typedef enum android_LogPriority { + ANDROID_LOG_UNKNOWN = 0, + ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */ + ANDROID_LOG_VERBOSE, + ANDROID_LOG_DEBUG, + ANDROID_LOG_INFO, + ANDROID_LOG_WARN, + ANDROID_LOG_ERROR, + ANDROID_LOG_FATAL, + ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */ + } android_LogPriority; + + /* + * Send a simple string to the log. + */ + int __android_log_write(int prio, const char *tag, const char *text); + + /* + * Send a formatted string to the log, used like printf(fmt,...) + */ + int __android_log_print(int prio, const char *tag, const char *fmt, ...) +#if defined(__GNUC__) + __attribute__ ((format(printf, 3, 4))) +#endif + ; + +#endif + +// --------------------------------------------------------------------- + +/* + * Normally we strip FBLOGV (VERBOSE messages) from release builds. + * You can modify this (for example with "#define FBLOG_NDEBUG 0" + * at the top of your source file) to change that behavior. + */ +#ifndef FBLOG_NDEBUG +#ifdef NDEBUG +#define FBLOG_NDEBUG 1 +#else +#define FBLOG_NDEBUG 0 +#endif +#endif + +/* + * This is the local tag used for the following simplified + * logging macros. You can change this preprocessor definition + * before using the other macros to change the tag. + */ +#ifndef LOG_TAG +#define LOG_TAG NULL +#endif + +// --------------------------------------------------------------------- + +/* + * Simplified macro to send a verbose log message using the current LOG_TAG. + */ +#ifndef FBLOGV +#if FBLOG_NDEBUG +#define FBLOGV(...) ((void)0) +#else +#define FBLOGV(...) ((void)FBLOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) +#endif +#endif + +#define CONDITION(cond) (__builtin_expect((cond)!=0, 0)) + +#ifndef FBLOGV_IF +#if FBLOG_NDEBUG +#define FBLOGV_IF(cond, ...) ((void)0) +#else +#define FBLOGV_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)FBLOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif +#endif + +/* + * Simplified macro to send a debug log message using the current LOG_TAG. + */ +#ifndef FBLOGD +#define FBLOGD(...) ((void)FBLOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef FBLOGD_IF +#define FBLOGD_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)FBLOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif + +/* + * Simplified macro to send an info log message using the current LOG_TAG. + */ +#ifndef FBLOGI +#define FBLOGI(...) ((void)FBLOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef FBLOGI_IF +#define FBLOGI_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)FBLOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif + +/* + * Simplified macro to send a warning log message using the current LOG_TAG. + */ +#ifndef FBLOGW +#define FBLOGW(...) ((void)FBLOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef FBLOGW_IF +#define FBLOGW_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)FBLOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif + +/* + * Simplified macro to send an error log message using the current LOG_TAG. + */ +#ifndef FBLOGE +#define FBLOGE(...) ((void)FBLOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef FBLOGE_IF +#define FBLOGE_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)FBLOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif + +// --------------------------------------------------------------------- + +/* + * Conditional based on whether the current LOG_TAG is enabled at + * verbose priority. + */ +#ifndef IF_FBLOGV +#if FBLOG_NDEBUG +#define IF_FBLOGV() if (false) +#else +#define IF_FBLOGV() IF_FBLOG(LOG_VERBOSE, LOG_TAG) +#endif +#endif + +/* + * Conditional based on whether the current LOG_TAG is enabled at + * debug priority. + */ +#ifndef IF_FBLOGD +#define IF_FBLOGD() IF_FBLOG(LOG_DEBUG, LOG_TAG) +#endif + +/* + * Conditional based on whether the current LOG_TAG is enabled at + * info priority. + */ +#ifndef IF_FBLOGI +#define IF_FBLOGI() IF_FBLOG(LOG_INFO, LOG_TAG) +#endif + +/* + * Conditional based on whether the current LOG_TAG is enabled at + * warn priority. + */ +#ifndef IF_FBLOGW +#define IF_FBLOGW() IF_FBLOG(LOG_WARN, LOG_TAG) +#endif + +/* + * Conditional based on whether the current LOG_TAG is enabled at + * error priority. + */ +#ifndef IF_FBLOGE +#define IF_FBLOGE() IF_FBLOG(LOG_ERROR, LOG_TAG) +#endif + + +// --------------------------------------------------------------------- + +/* + * Log a fatal error. If the given condition fails, this stops program + * execution like a normal assertion, but also generating the given message. + * It is NOT stripped from release builds. Note that the condition test + * is -inverted- from the normal assert() semantics. + */ +#define FBLOG_ALWAYS_FATAL_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)fb_printAssert(#cond, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) + +#define FBLOG_ALWAYS_FATAL(...) \ + ( ((void)fb_printAssert(NULL, LOG_TAG, __VA_ARGS__)) ) + +/* + * Versions of LOG_ALWAYS_FATAL_IF and LOG_ALWAYS_FATAL that + * are stripped out of release builds. + */ +#if FBLOG_NDEBUG + +#define FBLOG_FATAL_IF(cond, ...) ((void)0) +#define FBLOG_FATAL(...) ((void)0) + +#else + +#define FBLOG_FATAL_IF(cond, ...) FBLOG_ALWAYS_FATAL_IF(cond, __VA_ARGS__) +#define FBLOG_FATAL(...) FBLOG_ALWAYS_FATAL(__VA_ARGS__) + +#endif + +/* + * Assertion that generates a log message when the assertion fails. + * Stripped out of release builds. Uses the current LOG_TAG. + */ +#define FBLOG_ASSERT(cond, ...) FBLOG_FATAL_IF(!(cond), __VA_ARGS__) +//#define LOG_ASSERT(cond) LOG_FATAL_IF(!(cond), "Assertion failed: " #cond) + +// --------------------------------------------------------------------- + +/* + * Basic log message macro. + * + * Example: + * FBLOG(LOG_WARN, NULL, "Failed with error %d", errno); + * + * The second argument may be NULL or "" to indicate the "global" tag. + */ +#ifndef FBLOG +#define FBLOG(priority, tag, ...) \ + FBLOG_PRI(ANDROID_##priority, tag, __VA_ARGS__) +#endif + +#ifndef FBLOG_BY_DELIMS +#define FBLOG_BY_DELIMS(priority, tag, delims, msg, ...) \ + logPrintByDelims(ANDROID_##priority, tag, delims, msg, ##__VA_ARGS__) +#endif + +/* + * Log macro that allows you to specify a number for the priority. + */ +#ifndef FBLOG_PRI +#define FBLOG_PRI(priority, tag, ...) \ + fb_printLog(priority, tag, __VA_ARGS__) +#endif + +/* + * Log macro that allows you to pass in a varargs ("args" is a va_list). + */ +#ifndef FBLOG_PRI_VA +#define FBLOG_PRI_VA(priority, tag, fmt, args) \ + fb_vprintLog(priority, NULL, tag, fmt, args) +#endif + +/* + * Conditional given a desired logging priority and tag. + */ +#ifndef IF_FBLOG +#define IF_FBLOG(priority, tag) \ + if (fb_testLog(ANDROID_##priority, tag)) +#endif + +typedef void (*LogHandler)(int priority, const char* tag, const char* message); +void setLogHandler(LogHandler logHandler); + +/* + * =========================================================================== + * + * The stuff in the rest of this file should not be used directly. + */ +int fb_printLog(int prio, const char *tag, const char *fmt, ...) +#if defined(__GNUC__) + __attribute__ ((format(printf, 3, 4))) +#endif +; + +#define fb_vprintLog(prio, cond, tag, fmt...) \ + __android_log_vprint(prio, tag, fmt) + +#define fb_printAssert(cond, tag, fmt...) \ + __android_log_assert(cond, tag, fmt) + +#define fb_writeLog(prio, tag, text) \ + __android_log_write(prio, tag, text) + +#define fb_bWriteLog(tag, payload, len) \ + __android_log_bwrite(tag, payload, len) +#define fb_btWriteLog(tag, type, payload, len) \ + __android_log_btwrite(tag, type, payload, len) + +#define fb_testLog(prio, tag) (1) + +/* + * FB extensions + */ +void logPrintByDelims(int priority, const char* tag, const char* delims, + const char* msg, ...); + + +#ifdef __cplusplus +} +#endif + diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/fb/log.cpp b/tests/react-test-app/android/app/src/main/jni/first-party/fb/log.cpp new file mode 100644 index 00000000..b58b7ac9 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/fb/log.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include +#include +#include +#include + +#define LOG_BUFFER_SIZE 4096 +static LogHandler gLogHandler; + +void setLogHandler(LogHandler logHandler) { + gLogHandler = logHandler; +} + +int fb_printLog(int prio, const char *tag, const char *fmt, ...) { + char logBuffer[LOG_BUFFER_SIZE]; + + va_list va_args; + va_start(va_args, fmt); + int result = vsnprintf(logBuffer, sizeof(logBuffer), fmt, va_args); + va_end(va_args); + if (gLogHandler != NULL) { + gLogHandler(prio, tag, logBuffer); + } + __android_log_write(prio, tag, logBuffer); + return result; +} + +void logPrintByDelims(int priority, const char* tag, const char* delims, + const char* msg, ...) +{ + va_list ap; + char buf[32768]; + char* context; + char* tok; + + va_start(ap, msg); + vsnprintf(buf, sizeof(buf), msg, ap); + va_end(ap); + + tok = strtok_r(buf, delims, &context); + + if (!tok) { + return; + } + + do { + __android_log_write(priority, tag, tok); + } while ((tok = strtok_r(NULL, delims, &context))); +} + +#ifndef ANDROID + +// Implementations of the basic android logging functions for non-android platforms. + +static char logTagChar(int prio) { + switch (prio) { + default: + case ANDROID_LOG_UNKNOWN: + case ANDROID_LOG_DEFAULT: + case ANDROID_LOG_SILENT: + return ' '; + case ANDROID_LOG_VERBOSE: + return 'V'; + case ANDROID_LOG_DEBUG: + return 'D'; + case ANDROID_LOG_INFO: + return 'I'; + case ANDROID_LOG_WARN: + return 'W'; + case ANDROID_LOG_ERROR: + return 'E'; + case ANDROID_LOG_FATAL: + return 'F'; + } +} + +int __android_log_write(int prio, const char *tag, const char *text) { + return fprintf(stderr, "[%c/%.16s] %s\n", logTagChar(prio), tag, text); +} + +int __android_log_print(int prio, const char *tag, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + + int res = fprintf(stderr, "[%c/%.16s] ", logTagChar(prio), tag); + res += vfprintf(stderr, "%s\n", ap); + + va_end(ap); + return res; +} + +#endif diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/fb/noncopyable.h b/tests/react-test-app/android/app/src/main/jni/first-party/fb/noncopyable.h new file mode 100644 index 00000000..7212cc4d --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/fb/noncopyable.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +namespace facebook { + +struct noncopyable { + noncopyable(const noncopyable&) = delete; + noncopyable& operator=(const noncopyable&) = delete; +protected: + noncopyable() = default; +}; + +} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/fb/nonmovable.h b/tests/react-test-app/android/app/src/main/jni/first-party/fb/nonmovable.h new file mode 100644 index 00000000..37f006a4 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/fb/nonmovable.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +namespace facebook { + +struct nonmovable { + nonmovable(nonmovable&&) = delete; + nonmovable& operator=(nonmovable&&) = delete; +protected: + nonmovable() = default; +}; + +} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/ALog.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/ALog.h new file mode 100644 index 00000000..0ed1c5fd --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/ALog.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/** @file ALog.h + * + * Very simple android only logging. Define LOG_TAG to enable the macros. + */ + +#pragma once + +#ifdef __ANDROID__ + +#include + +namespace facebook { +namespace alog { + +template +inline void log(int level, const char* tag, const char* msg, ARGS... args) noexcept { + __android_log_print(level, tag, msg, args...); +} + +template +inline void log(int level, const char* tag, const char* msg) noexcept { + __android_log_write(level, tag, msg); +} + +template +inline void logv(const char* tag, const char* msg, ARGS... args) noexcept { + log(ANDROID_LOG_VERBOSE, tag, msg, args...); +} + +template +inline void logd(const char* tag, const char* msg, ARGS... args) noexcept { + log(ANDROID_LOG_DEBUG, tag, msg, args...); +} + +template +inline void logi(const char* tag, const char* msg, ARGS... args) noexcept { + log(ANDROID_LOG_INFO, tag, msg, args...); +} + +template +inline void logw(const char* tag, const char* msg, ARGS... args) noexcept { + log(ANDROID_LOG_WARN, tag, msg, args...); +} + +template +inline void loge(const char* tag, const char* msg, ARGS... args) noexcept { + log(ANDROID_LOG_ERROR, tag, msg, args...); +} + +template +inline void logf(const char* tag, const char* msg, ARGS... args) noexcept { + log(ANDROID_LOG_FATAL, tag, msg, args...); +} + + +#ifdef LOG_TAG +# define ALOGV(...) ::facebook::alog::logv(LOG_TAG, __VA_ARGS__) +# define ALOGD(...) ::facebook::alog::logd(LOG_TAG, __VA_ARGS__) +# define ALOGI(...) ::facebook::alog::logi(LOG_TAG, __VA_ARGS__) +# define ALOGW(...) ::facebook::alog::logw(LOG_TAG, __VA_ARGS__) +# define ALOGE(...) ::facebook::alog::loge(LOG_TAG, __VA_ARGS__) +# define ALOGF(...) ::facebook::alog::logf(LOG_TAG, __VA_ARGS__) +#endif + +}} + +#else +# define ALOGV(...) ((void)0) +# define ALOGD(...) ((void)0) +# define ALOGI(...) ((void)0) +# define ALOGW(...) ((void)0) +# define ALOGE(...) ((void)0) +# define ALOGF(...) ((void)0) +#endif diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/Android.mk b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Android.mk new file mode 100644 index 00000000..e77eaf1a --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Android.mk @@ -0,0 +1,35 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + Countable.cpp \ + Environment.cpp \ + fbjni.cpp \ + jni_helpers.cpp \ + LocalString.cpp \ + OnLoad.cpp \ + WeakReference.cpp \ + fbjni/Exceptions.cpp \ + fbjni/Hybrid.cpp \ + fbjni/References.cpp + +LOCAL_C_INCLUDES := $(LOCAL_PATH)/.. +LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/.. + +LOCAL_CFLAGS := -DLOG_TAG=\"fbjni\" -fexceptions -frtti +LOCAL_CFLAGS += -Wall -Werror + +CXX11_FLAGS := -std=gnu++11 +LOCAL_CFLAGS += $(CXX11_FLAGS) + +LOCAL_EXPORT_CPPFLAGS := $(CXX11_FLAGS) + +LOCAL_LDLIBS := -landroid + +LOCAL_SHARED_LIBRARIES := libfb + +LOCAL_MODULE := libfbjni + +include $(BUILD_SHARED_LIBRARY) + +$(call import-module,fb) diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/Countable.cpp b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Countable.cpp new file mode 100644 index 00000000..6ff7efe9 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Countable.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include +#include +#include +#include + +namespace facebook { +namespace jni { + +static jfieldID gCountableNativePtr; + +static RefPtr* rawCountableFromJava(JNIEnv* env, jobject obj) { + FBASSERT(obj); + return reinterpret_cast*>(env->GetLongField(obj, gCountableNativePtr)); +} + +const RefPtr& countableFromJava(JNIEnv* env, jobject obj) { + FBASSERT(obj); + return *rawCountableFromJava(env, obj); +} + +void setCountableForJava(JNIEnv* env, jobject obj, RefPtr&& countable) { + int oldValue = env->GetLongField(obj, gCountableNativePtr); + FBASSERTMSGF(oldValue == 0, "Cannot reinitialize object; expected nullptr, got %x", oldValue); + + FBASSERT(countable); + uintptr_t fieldValue = (uintptr_t) new RefPtr(std::move(countable)); + env->SetLongField(obj, gCountableNativePtr, fieldValue); +} + +/** + * NB: THREAD SAFETY (this comment also exists at Countable.java) + * + * This method deletes the corresponding native object on whatever thread the method is called + * on. In the common case when this is called by Countable#finalize(), this will be called on the + * system finalizer thread. If you manually call dispose on the Java object, the native object + * will be deleted synchronously on that thread. + */ +void dispose(JNIEnv* env, jobject obj) { + // Grab the pointer + RefPtr* countable = rawCountableFromJava(env, obj); + if (!countable) { + // That was easy. + return; + } + + // Clear out the old value to avoid double-frees + env->SetLongField(obj, gCountableNativePtr, 0); + + delete countable; +} + +void CountableOnLoad(JNIEnv* env) { + jclass countable = env->FindClass("com/facebook/jni/Countable"); + gCountableNativePtr = env->GetFieldID(countable, "mInstance", "J"); + registerNatives(env, countable, { + { "dispose", "()V", (void*) dispose }, + }); +} + +} } diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/Countable.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Countable.h new file mode 100644 index 00000000..69460d8a --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Countable.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once +#include +#include +#include + +namespace facebook { +namespace jni { + +const RefPtr& countableFromJava(JNIEnv* env, jobject obj); + +template RefPtr extractRefPtr(JNIEnv* env, jobject obj) { + return static_cast>(countableFromJava(env, obj)); +} + +template RefPtr extractPossiblyNullRefPtr(JNIEnv* env, jobject obj) { + return obj ? extractRefPtr(env, obj) : nullptr; +} + +void setCountableForJava(JNIEnv* env, jobject obj, RefPtr&& countable); + +void CountableOnLoad(JNIEnv* env); + +} } + diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/Doxyfile b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Doxyfile new file mode 100644 index 00000000..8b4df6a7 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Doxyfile @@ -0,0 +1,18 @@ +PROJECT_NAME = "Facebook JNI" +PROJECT_BRIEF = "Helper library to provide safe and convenient access to JNI with very low overhead" +JAVADOC_AUTOBRIEF = YES +EXTRACT_ALL = YES +RECURSIVE = YES +EXCLUDE = tests Asserts.h Countable.h GlobalReference.h LocalReference.h LocalString.h Registration.h WeakReference.h jni_helpers.h Environment.h +EXCLUDE_PATTERNS = *-inl.h *.cpp +GENERATE_HTML = YES +GENERATE_LATEX = NO +ENABLE_PREPROCESSING = YES +HIDE_UNDOC_MEMBERS = YES +HIDE_SCOPE_NAMES = YES +HIDE_FRIEND_COMPOUNDS = YES +HIDE_UNDOC_CLASSES = YES +SHOW_INCLUDE_FILES = NO +PREDEFINED = LOG_TAG=fbjni +EXAMPLE_PATH = samples +#ENABLED_SECTIONS = INTERNAL diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/Environment.cpp b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Environment.cpp new file mode 100644 index 00000000..02b88ce7 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Environment.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include +#include +#include +#include +#include + +namespace facebook { +namespace jni { + +static StaticInitialized> g_env; +static JavaVM* g_vm = nullptr; + +/* static */ +JNIEnv* Environment::current() { + JNIEnv* env = g_env->get(); + if ((env == nullptr) && (g_vm != nullptr)) { + if (g_vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) { + FBLOGE("Error retrieving JNI Environment, thread is probably not attached to JVM"); + env = nullptr; + } else { + g_env->reset(env); + } + } + return env; +} + +/* static */ +void Environment::detachCurrentThread() { + auto env = g_env->get(); + if (env) { + FBASSERT(g_vm); + g_vm->DetachCurrentThread(); + g_env->reset(); + } +} + +struct EnvironmentInitializer { + EnvironmentInitializer(JavaVM* vm) { + FBASSERT(!g_vm); + FBASSERT(vm); + g_vm = vm; + g_env.initialize([] (void*) {}); + } +}; + +/* static */ +void Environment::initialize(JavaVM* vm) { + static EnvironmentInitializer init(vm); +} + +/* static */ +JNIEnv* Environment::ensureCurrentThreadIsAttached() { + auto env = g_env->get(); + if (!env) { + FBASSERT(g_vm); + g_vm->AttachCurrentThread(&env, nullptr); + g_env->reset(env); + } + return env; +} + +ThreadScope::ThreadScope() + : attachedWithThisScope_(false) { + JNIEnv* env = nullptr; + if (g_vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_EDETACHED) { + return; + } + env = facebook::jni::Environment::ensureCurrentThreadIsAttached(); + FBASSERT(env); + attachedWithThisScope_ = true; +} + +ThreadScope::~ThreadScope() { + if (attachedWithThisScope_) { + Environment::detachCurrentThread(); + } +} + +} } + diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/Environment.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Environment.h new file mode 100644 index 00000000..4dc6966a --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Environment.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once +#include +#include + +namespace facebook { +namespace jni { + +// Keeps a thread-local reference to the current thread's JNIEnv. +struct Environment { + // May be null if this thread isn't attached to the JVM + static JNIEnv* current(); + static void initialize(JavaVM* vm); + static JNIEnv* ensureCurrentThreadIsAttached(); + static void detachCurrentThread(); +}; + +/** + * RAII Object that attaches a thread to the JVM. Failing to detach from a thread before it + * exits will cause a crash, as will calling Detach an extra time, and this guard class helps + * keep that straight. In addition, it remembers whether it performed the attach or not, so it + * is safe to nest it with itself or with non-fbjni code that manages the attachment correctly. + * + * Potential concerns: + * - Attaching to the JVM is fast (~100us on MotoG), but ideally you would attach while the + * app is not busy. + * - Having a thread detach at arbitrary points is not safe in Dalvik; you need to be sure that + * there is no Java code on the current stack or you run the risk of a crash like: + * ERROR: detaching thread with interp frames (count=18) + * (More detail at https://groups.google.com/forum/#!topic/android-ndk/2H8z5grNqjo) + * ThreadScope won't do a detach if the thread was already attached before the guard is + * instantiated, but there's probably some usage that could trip this up. + * - Newly attached C++ threads only get the bootstrap class loader -- i.e. java language + * classes, not any of our application's classes. This will be different behavior than threads + * that were initiated on the Java side. A workaround is to pass a global reference for a + * class or instance to the new thread; this bypasses the need for the class loader. + * (See http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html#attach_current_thread) + */ +class ThreadScope { + public: + ThreadScope(); + ThreadScope(ThreadScope&) = delete; + ThreadScope(ThreadScope&&) = default; + ThreadScope& operator=(ThreadScope&) = delete; + ThreadScope& operator=(ThreadScope&&) = delete; + ~ThreadScope(); + + private: + bool attachedWithThisScope_; +}; + +} } + diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/GlobalReference.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/GlobalReference.h new file mode 100644 index 00000000..55f537ab --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/GlobalReference.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include +#include + +#include + +#include + +namespace facebook { namespace jni { + +template +class GlobalReference { + static_assert(std::is_convertible::value, + "GlobalReference instantiated with type that is not " + "convertible to jobject"); + + public: + explicit GlobalReference(T globalReference) : + reference_(globalReference? Environment::current()->NewGlobalRef(globalReference) : nullptr) { + } + + ~GlobalReference() { + reset(); + } + + GlobalReference() : + reference_(nullptr) { + } + + // enable move constructor and assignment + GlobalReference(GlobalReference&& rhs) : + reference_(std::move(rhs.reference_)) { + rhs.reference_ = nullptr; + } + + GlobalReference& operator=(GlobalReference&& rhs) { + if (this != &rhs) { + reset(); + reference_ = std::move(rhs.reference_); + rhs.reference_ = nullptr; + } + return *this; + } + + GlobalReference(const GlobalReference& rhs) : + reference_{} { + reset(rhs.get()); + } + + GlobalReference& operator=(const GlobalReference& rhs) { + if (this == &rhs) { + return *this; + } + reset(rhs.get()); + return *this; + } + + explicit operator bool() const { + return (reference_ != nullptr); + } + + T get() const { + return reinterpret_cast(reference_); + } + + void reset(T globalReference = nullptr) { + if (reference_) { + Environment::current()->DeleteGlobalRef(reference_); + } + if (globalReference) { + reference_ = Environment::current()->NewGlobalRef(globalReference); + } else { + reference_ = nullptr; + } + } + + private: + jobject reference_; +}; + +}} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/LocalReference.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/LocalReference.h new file mode 100644 index 00000000..b5d9c54a --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/LocalReference.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include +#include + +#include + +#include + +namespace facebook { +namespace jni { + +template +struct LocalReferenceDeleter { + static_assert(std::is_convertible::value, + "LocalReferenceDeleter instantiated with type that is not convertible to jobject"); + void operator()(T localReference) { + if (localReference != nullptr) { + Environment::current()->DeleteLocalRef(localReference); + } + } + }; + +template +using LocalReference = + std::unique_ptr::type, LocalReferenceDeleter>; + +} } diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/LocalString.cpp b/tests/react-test-app/android/app/src/main/jni/first-party/jni/LocalString.cpp new file mode 100644 index 00000000..5fc8d0c8 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/LocalString.cpp @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include +#include +#include + +#include + +namespace facebook { +namespace jni { + +namespace { + +inline void encode3ByteUTF8(char32_t code, uint8_t* out) { + FBASSERTMSGF((code & 0xffff0000) == 0, "3 byte utf-8 encodings only valid for up to 16 bits"); + + out[0] = 0xE0 | (code >> 12); + out[1] = 0x80 | ((code >> 6) & 0x3F); + out[2] = 0x80 | (code & 0x3F); +} + +inline char32_t decode3ByteUTF8(const uint8_t* in) { + return (((in[0] & 0x0f) << 12) | + ((in[1] & 0x3f) << 6) | + ( in[2] & 0x3f)); +} + +inline void encode4ByteUTF8(char32_t code, std::string& out, size_t offset) { + FBASSERTMSGF((code & 0xfff80000) == 0, "4 byte utf-8 encodings only valid for up to 21 bits"); + + out[offset] = (char) (0xF0 | (code >> 18)); + out[offset + 1] = (char) (0x80 | ((code >> 12) & 0x3F)); + out[offset + 2] = (char) (0x80 | ((code >> 6) & 0x3F)); + out[offset + 3] = (char) (0x80 | (code & 0x3F)); +} + +template +inline bool isFourByteUTF8Encoding(const T* utf8) { + return ((*utf8 & 0xF8) == 0xF0); +} + +} + +namespace detail { + +size_t modifiedLength(const std::string& str) { + // Scan for supplementary characters + size_t j = 0; + for (size_t i = 0; i < str.size(); ) { + if (str[i] == 0) { + i += 1; + j += 2; + } else if (i + 4 > str.size() || + !isFourByteUTF8Encoding(&(str[i]))) { + // See the code in utf8ToModifiedUTF8 for what's happening here. + i += 1; + j += 1; + } else { + i += 4; + j += 6; + } + } + + return j; +} + +// returns modified utf8 length; *length is set to strlen(str) +size_t modifiedLength(const uint8_t* str, size_t* length) { + // NUL-terminated: Scan for length and supplementary characters + size_t i = 0; + size_t j = 0; + while (str[i] != 0) { + if (str[i + 1] == 0 || + str[i + 2] == 0 || + str[i + 3] == 0 || + !isFourByteUTF8Encoding(&(str[i]))) { + i += 1; + j += 1; + } else { + i += 4; + j += 6; + } + } + + *length = i; + return j; +} + +void utf8ToModifiedUTF8(const uint8_t* utf8, size_t len, uint8_t* modified, size_t modifiedBufLen) +{ + size_t j = 0; + for (size_t i = 0; i < len; ) { + FBASSERTMSGF(j < modifiedBufLen, "output buffer is too short"); + if (utf8[i] == 0) { + FBASSERTMSGF(j + 1 < modifiedBufLen, "output buffer is too short"); + modified[j] = 0xc0; + modified[j + 1] = 0x80; + i += 1; + j += 2; + continue; + } + + if (i + 4 > len || + !isFourByteUTF8Encoding(utf8 + i)) { + // If the input is too short for this to be a four-byte + // encoding, or it isn't one for real, just copy it on through. + modified[j] = utf8[i]; + i++; + j++; + continue; + } + + // Convert 4 bytes of input to 2 * 3 bytes of output + char32_t code = (((utf8[i] & 0x07) << 18) | + ((utf8[i + 1] & 0x3f) << 12) | + ((utf8[i + 2] & 0x3f) << 6) | + ( utf8[i + 3] & 0x3f)); + char32_t first; + char32_t second; + + if (code > 0x10ffff) { + // These could be valid utf-8, but cannot be represented as modified UTF-8, due to the 20-bit + // limit on that representation. Encode two replacement characters, so the expected output + // length lines up. + const char32_t kUnicodeReplacementChar = 0xfffd; + first = kUnicodeReplacementChar; + second = kUnicodeReplacementChar; + } else { + // split into surrogate pair + first = ((code - 0x010000) >> 10) | 0xd800; + second = ((code - 0x010000) & 0x3ff) | 0xdc00; + } + + // encode each as a 3 byte surrogate value + FBASSERTMSGF(j + 5 < modifiedBufLen, "output buffer is too short"); + encode3ByteUTF8(first, modified + j); + encode3ByteUTF8(second, modified + j + 3); + i += 4; + j += 6; + } + + FBASSERTMSGF(j < modifiedBufLen, "output buffer is too short"); + modified[j++] = '\0'; +} + +std::string modifiedUTF8ToUTF8(const uint8_t* modified, size_t len) { + // Converting from modified utf8 to utf8 will always shrink, so this will always be sufficient + std::string utf8(len, 0); + size_t j = 0; + for (size_t i = 0; i < len; ) { + // surrogate pair: 1101 10xx xxxx xxxx 1101 11xx xxxx xxxx + // encoded pair: 1110 1101 1010 xxxx 10xx xxxx 1110 1101 1011 xxxx 10xx xxxx + + if (len >= i + 6 && + modified[i] == 0xed && + (modified[i + 1] & 0xf0) == 0xa0 && + modified[i + 3] == 0xed && + (modified[i + 4] & 0xf0) == 0xb0) { + // Valid surrogate pair + char32_t pair1 = decode3ByteUTF8(modified + i); + char32_t pair2 = decode3ByteUTF8(modified + i + 3); + char32_t ch = 0x10000 + (((pair1 & 0x3ff) << 10) | + ( pair2 & 0x3ff)); + encode4ByteUTF8(ch, utf8, j); + i += 6; + j += 4; + continue; + } else if (len >= i + 2 && + modified[i] == 0xc0 && + modified[i + 1] == 0x80) { + utf8[j] = 0; + i += 2; + j += 1; + continue; + } + + // copy one byte. This might be a one, two, or three-byte encoding. It might be an invalid + // encoding of some sort, but garbage in garbage out is ok. + + utf8[j] = (char) modified[i]; + i++; + j++; + } + + utf8.resize(j); + + return utf8; +} + +} + +LocalString::LocalString(const std::string& str) +{ + size_t modlen = detail::modifiedLength(str); + if (modlen == str.size()) { + // no supplementary characters, build jstring from input buffer + m_string = Environment::current()->NewStringUTF(str.data()); + return; + } + auto modified = std::vector(modlen + 1); // allocate extra byte for \0 + detail::utf8ToModifiedUTF8( + reinterpret_cast(str.data()), str.size(), + reinterpret_cast(modified.data()), modified.size()); + m_string = Environment::current()->NewStringUTF(modified.data()); +} + +LocalString::LocalString(const char* str) +{ + size_t len; + size_t modlen = detail::modifiedLength(reinterpret_cast(str), &len); + if (modlen == len) { + // no supplementary characters, build jstring from input buffer + m_string = Environment::current()->NewStringUTF(str); + return; + } + auto modified = std::vector(modlen + 1); // allocate extra byte for \0 + detail::utf8ToModifiedUTF8( + reinterpret_cast(str), len, + reinterpret_cast(modified.data()), modified.size()); + m_string = Environment::current()->NewStringUTF(modified.data()); +} + +LocalString::~LocalString() { + Environment::current()->DeleteLocalRef(m_string); +} + +std::string fromJString(JNIEnv* env, jstring str) { + const char* modified = env->GetStringUTFChars(str, NULL); + jsize length = env->GetStringUTFLength(str); + std::string s = detail::modifiedUTF8ToUTF8(reinterpret_cast(modified), length); + env->ReleaseStringUTFChars(str, modified); + return s; +} + +} } diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/LocalString.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/LocalString.h new file mode 100644 index 00000000..a85efa48 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/LocalString.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once +#include +#include + +namespace facebook { +namespace jni { + +namespace detail { + +void utf8ToModifiedUTF8(const uint8_t* bytes, size_t len, uint8_t* modified, size_t modifiedLength); +size_t modifiedLength(const std::string& str); +size_t modifiedLength(const uint8_t* str, size_t* length); +std::string modifiedUTF8ToUTF8(const uint8_t* modified, size_t len); + +} + +// JNI represents strings encoded with modified version of UTF-8. The difference between UTF-8 and +// Modified UTF-8 is that the latter support only 1-byte, 2-byte, and 3-byte formats. Supplementary +// character (4 bytes in unicode) needs to be represented in the form of surrogate pairs. To create +// a Modified UTF-8 surrogate pair that Dalvik would understand we take 4-byte unicode character, +// encode it with UTF-16 which gives us two 2 byte chars (surrogate pair) and then we encode each +// pair as UTF-8. This result in 2 x 3 byte characters. To convert modified UTF-8 to standard +// UTF-8, this mus tbe reversed. +// +// The second difference is that Modified UTF-8 is encoding NUL byte in 2-byte format. +// +// In order to avoid complex error handling, only a minimum of validity checking is done to avoid +// crashing. If the input is invalid, the output may be invalid as well. +// +// Relevant links: +// - http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html +// - https://docs.oracle.com/javase/6/docs/api/java/io/DataInput.html#modified-utf-8 + +class LocalString { +public: + // Assumes UTF8 encoding and make a required convertion to modified UTF-8 when the string + // contains unicode supplementary characters. + explicit LocalString(const std::string& str); + explicit LocalString(const char* str); + jstring string() const { + return m_string; + } + ~LocalString(); +private: + jstring m_string; +}; + +// The string from JNI is converted to standard UTF-8 if the string contains supplementary +// characters. +std::string fromJString(JNIEnv* env, jstring str); + +} } diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/OnLoad.cpp b/tests/react-test-app/android/app/src/main/jni/first-party/jni/OnLoad.cpp new file mode 100644 index 00000000..a4c40409 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/OnLoad.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include +#include +#include +#include +#include + +using namespace facebook::jni; + +JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { + return facebook::jni::initialize(vm, [] { + CountableOnLoad(Environment::current()); + HybridDataOnLoad(); + }); +} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/Registration.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Registration.h new file mode 100644 index 00000000..243a9478 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/Registration.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once +#include +#include +#include + +namespace facebook { +namespace jni { + +static inline void registerNatives(JNIEnv* env, jclass cls, std::initializer_list methods) { + auto result = env->RegisterNatives(cls, methods.begin(), methods.size()); + FBASSERT(result == 0); +} + +static inline void registerNatives(JNIEnv* env, const char* cls, std::initializer_list list) { + registerNatives(env, env->FindClass(cls), list); +} + +} } diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/WeakReference.cpp b/tests/react-test-app/android/app/src/main/jni/first-party/jni/WeakReference.cpp new file mode 100644 index 00000000..d87ea33c --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/WeakReference.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include +#include + +namespace facebook { +namespace jni { + +WeakReference::WeakReference(jobject strongRef) : + m_weakReference(Environment::current()->NewWeakGlobalRef(strongRef)) +{ +} + +WeakReference::~WeakReference() { + auto env = Environment::current(); + FBASSERTMSGF(env, "Attempt to delete jni::WeakReference from non-JNI thread"); + env->DeleteWeakGlobalRef(m_weakReference); +} + +ResolvedWeakReference::ResolvedWeakReference(jobject weakRef) : + m_strongReference(Environment::current()->NewLocalRef(weakRef)) +{ +} + +ResolvedWeakReference::ResolvedWeakReference(const RefPtr& weakRef) : + m_strongReference(Environment::current()->NewLocalRef(weakRef->weakRef())) +{ +} + +ResolvedWeakReference::~ResolvedWeakReference() { + if (m_strongReference) + Environment::current()->DeleteLocalRef(m_strongReference); +} + +} } + diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/WeakReference.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/WeakReference.h new file mode 100644 index 00000000..2723155f --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/WeakReference.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once +#include +#include +#include +#include + +namespace facebook { +namespace jni { + +class WeakReference : public Countable { +public: + typedef RefPtr Ptr; + WeakReference(jobject strongRef); + ~WeakReference(); + jweak weakRef() { + return m_weakReference; + } + +private: + jweak m_weakReference; +}; + +// This class is intended to take a weak reference and turn it into a strong +// local reference. Consequently, it should only be allocated on the stack. +class ResolvedWeakReference : public noncopyable { +public: + ResolvedWeakReference(jobject weakRef); + ResolvedWeakReference(const RefPtr& weakRef); + ~ResolvedWeakReference(); + + operator jobject () { + return m_strongReference; + } + + explicit operator bool () { + return m_strongReference != nullptr; + } + +private: + jobject m_strongReference; +}; + +} } + diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni.cpp b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni.cpp new file mode 100644 index 00000000..a083371b --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni.cpp @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include "fbjni.h" + +#include +#include +#include + +namespace facebook { +namespace jni { + +template +static void log(Args... args) { +// TODO (7623232) Migrate to glog +#ifdef __ANDROID__ + facebook::alog::loge("fbjni", args...); +#endif +} + +jint initialize(JavaVM* vm, void(*init_fn)()) noexcept { + static std::once_flag init_flag; + static auto failed = false; + + std::call_once(init_flag, [vm] { + try { + Environment::initialize(vm); + internal::initExceptionHelpers(); + } catch (std::exception& ex) { + log("Failed to initialize fbjni: %s", ex.what()); + failed = true; + } catch (...) { + log("Failed to initialize fbjni"); + failed = true; + } + }); + + if (failed) { + return JNI_ERR; + } + + try { + init_fn(); + } catch (...) { + translatePendingCppExceptionToJavaException(); + // So Java will handle the translated exception, fall through and + // return a good version number. + } + return JNI_VERSION_1_6; +} + +alias_ref findClassStatic(const char* name) { + const auto env = internal::getEnv(); + auto cls = env->FindClass(name); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!cls); + auto leaking_ref = (jclass)env->NewGlobalRef(cls); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!leaking_ref); + return wrap_alias(leaking_ref); +} + +local_ref findClassLocal(const char* name) { + const auto env = internal::getEnv(); + auto cls = env->FindClass(name); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!cls); + return adopt_local(cls); +} + + +// jstring ///////////////////////////////////////////////////////////////////////////////////////// + +std::string JObjectWrapper::toStdString() const { + const auto env = internal::getEnv(); + auto modified = env->GetStringUTFChars(self(), nullptr); + auto length = env->GetStringUTFLength(self()); + auto string = detail::modifiedUTF8ToUTF8(reinterpret_cast(modified), length); + env->ReleaseStringUTFChars(self(), modified); + return string; +} + +local_ref make_jstring(const char* utf8) { + if (!utf8) { + return {}; + } + const auto env = internal::getEnv(); + size_t len; + size_t modlen = detail::modifiedLength(reinterpret_cast(utf8), &len); + jstring result; + if (modlen == len) { + // The only difference between utf8 and modifiedUTF8 is in encoding 4-byte UTF8 chars + // and '\0' that is encoded on 2 bytes. + // + // Since modifiedUTF8-encoded string can be no shorter than it's UTF8 conterpart we + // know that if those two strings are of the same length we don't need to do any + // conversion -> no 4-byte chars nor '\0'. + result = env->NewStringUTF(utf8); + } else { + auto modified = std::vector(modlen + 1); // allocate extra byte for \0 + detail::utf8ToModifiedUTF8( + reinterpret_cast(utf8), len, + reinterpret_cast(modified.data()), modified.size()); + result = env->NewStringUTF(modified.data()); + } + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + return adopt_local(result); +} + + +// PinnedPrimitiveArray /////////////////////////////////////////////////////////////////////////// + +// TODO(T7847300): Allow array to be specified as constant so that JNI_ABORT can be passed +// on release, as opposed to 0, which results in unnecessary copying. +#pragma push_macro("DEFINE_PRIMITIVE_METHODS") +#undef DEFINE_PRIMITIVE_METHODS +#define DEFINE_PRIMITIVE_METHODS(TYPE, NAME) \ +template<> \ +TYPE* PinnedPrimitiveArray::get() { \ + FACEBOOK_JNI_THROW_EXCEPTION_IF(array_.get() == nullptr); \ + const auto env = internal::getEnv(); \ + elements_ = env->Get ## NAME ## ArrayElements( \ + static_cast(array_.get()), &isCopy_); \ + size_ = array_->size(); \ + return elements_; \ +} \ +template<> \ +void PinnedPrimitiveArray::release() { \ + FACEBOOK_JNI_THROW_EXCEPTION_IF(array_.get() == nullptr); \ + const auto env = internal::getEnv(); \ + env->Release ## NAME ## ArrayElements( \ + static_cast(array_.get()), elements_, 0); \ + elements_ = nullptr; \ + size_ = 0; \ +} + +DEFINE_PRIMITIVE_METHODS(jboolean, Boolean) +DEFINE_PRIMITIVE_METHODS(jbyte, Byte) +DEFINE_PRIMITIVE_METHODS(jchar, Char) +DEFINE_PRIMITIVE_METHODS(jshort, Short) +DEFINE_PRIMITIVE_METHODS(jint, Int) +DEFINE_PRIMITIVE_METHODS(jlong, Long) +DEFINE_PRIMITIVE_METHODS(jfloat, Float) +DEFINE_PRIMITIVE_METHODS(jdouble, Double) +#pragma pop_macro("DEFINE_PRIMITIVE_METHODS") + + +#define DEFINE_PRIMITIVE_ARRAY_UTILS(TYPE, NAME) \ + \ +local_ref make_ ## TYPE ## _array(jsize size) { \ + auto array = internal::getEnv()->New ## NAME ## Array(size); \ + FACEBOOK_JNI_THROW_EXCEPTION_IF(!array); \ + return adopt_local(array); \ +} \ + \ +local_ref JArray ## NAME::newArray(size_t count) { \ + return make_ ## TYPE ## _array(count); \ +} \ + \ +j ## TYPE* JArray ## NAME::getRegion(jsize start, jsize length, j ## TYPE* buf) { \ + internal::getEnv()->Get ## NAME ## ArrayRegion(self(), start, length, buf); \ + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \ + return buf; \ +} \ + \ +std::unique_ptr JArray ## NAME::getRegion(jsize start, jsize length) { \ + auto buf = std::unique_ptr{new j ## TYPE[length]}; \ + internal::getEnv()->Get ## NAME ## ArrayRegion(self(), start, length, buf.get()); \ + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \ + return buf; \ +} \ + \ +void JArray ## NAME::setRegion(jsize start, jsize length, const j ## TYPE* buf) { \ + internal::getEnv()->Set ## NAME ## ArrayRegion(self(), start, length, buf); \ + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \ +} \ + \ +PinnedPrimitiveArray JArray ## NAME::pin() { \ + return PinnedPrimitiveArray{self()}; \ +} \ + +DEFINE_PRIMITIVE_ARRAY_UTILS(boolean, Boolean) +DEFINE_PRIMITIVE_ARRAY_UTILS(byte, Byte) +DEFINE_PRIMITIVE_ARRAY_UTILS(char, Char) +DEFINE_PRIMITIVE_ARRAY_UTILS(short, Short) +DEFINE_PRIMITIVE_ARRAY_UTILS(int, Int) +DEFINE_PRIMITIVE_ARRAY_UTILS(long, Long) +DEFINE_PRIMITIVE_ARRAY_UTILS(float, Float) +DEFINE_PRIMITIVE_ARRAY_UTILS(double, Double) + + +// Internal debug ///////////////////////////////////////////////////////////////////////////////// + +namespace internal { +ReferenceStats g_reference_stats; + +void facebook::jni::internal::ReferenceStats::reset() noexcept { + locals_deleted = globals_deleted = weaks_deleted = 0; +} +} + +}} + diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni.h new file mode 100644 index 00000000..7ea816b6 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include + +#include "Environment.h" +#include "ALog.h" +#include "fbjni/Common.h" +#include "fbjni/Exceptions.h" +#include "fbjni/ReferenceAllocators.h" +#include "fbjni/References.h" +#include "fbjni/Meta.h" +#include "fbjni/CoreClasses.h" +#include "fbjni/Hybrid.h" +#include "fbjni/Registration.h" diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Common.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Common.h new file mode 100644 index 00000000..479f4371 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Common.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/** @file Common.h + * + * Defining the stuff that don't deserve headers of their own... + */ + +#pragma once + +#include + +#include +#include + +/// @cond INTERNAL + +namespace facebook { +namespace jni { + +/** + * This needs to be called at library load time, typically in your JNI_OnLoad method. + * + * The intended use is to return the result of initialize() directly + * from JNI_OnLoad and to do nothing else there. Library specific + * initialization code should go in the function passed to initialize + * (which can be, and probably should be, a C++ lambda). This approach + * provides correct error handling and translation errors during + * initialization into Java exceptions when appropriate. + * + * Failure to call this will cause your code to crash in a remarkably + * unhelpful way (typically a segfault) while trying to handle an exception + * which occurs later. + */ +jint initialize(JavaVM*, void(*)()) noexcept; + +namespace internal { + +/** + * Retrieve a pointer the JNI environment of the current thread. + * + * @pre The current thread must be attached to the VM + */ +inline JNIEnv* getEnv() noexcept { + // TODO(T6594868) Benchmark against raw JNI access + return Environment::current(); +} + +// Define to get extremely verbose logging of references and to enable reference stats +#if defined(__ANDROID__) && defined(FBJNI_DEBUG_REFS) +template +inline void dbglog(Args... args) noexcept { + facebook::alog::logv("fbjni_ref", args...); +} +#else +template +inline void dbglog(Args...) noexcept {} +#endif + +}}} + +/// @endcond diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/CoreClasses-inl.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/CoreClasses-inl.h new file mode 100644 index 00000000..134acc87 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/CoreClasses-inl.h @@ -0,0 +1,443 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include +#include + +#include "Common.h" +#include "Exceptions.h" + +namespace facebook { +namespace jni { + +inline bool isSameObject(alias_ref lhs, alias_ref rhs) noexcept { + return internal::getEnv()->IsSameObject(lhs.get(), rhs.get()) != JNI_FALSE; +} + + +// jobject ///////////////////////////////////////////////////////////////////////////////////////// + +inline JObjectWrapper::JObjectWrapper(jobject reference) noexcept + : this_{reference} +{} + +inline JObjectWrapper::JObjectWrapper(const JObjectWrapper& other) noexcept + : this_{other.this_} { + internal::dbglog("wrapper copy from this=%p ref=%p other=%p", this, other.this_, &other); +} + +inline local_ref JObjectWrapper::getClass() const noexcept { + return adopt_local(internal::getEnv()->GetObjectClass(self())); +} + +inline bool JObjectWrapper::isInstanceOf(alias_ref cls) const noexcept { + return internal::getEnv()->IsInstanceOf(self(), cls.get()) != JNI_FALSE; +} + +template +inline T JObjectWrapper::getFieldValue(JField field) const noexcept { + return field.get(self()); +} + +template +inline local_ref JObjectWrapper::getFieldValue(JField field) noexcept { + return adopt_local(field.get(self())); +} + +template +inline void JObjectWrapper::setFieldValue(JField field, T value) noexcept { + field.set(self(), value); +} + +inline std::string JObjectWrapper::toString() const { + static auto method = findClassLocal("java/lang/Object")->getMethod("toString"); + + return method(self())->toStdString(); +} + +inline void JObjectWrapper::set(jobject reference) noexcept { + this_ = reference; +} + +inline jobject JObjectWrapper::get() const noexcept { + return this_; +} + +inline jobject JObjectWrapper::self() const noexcept { + return this_; +} + +inline void swap(JObjectWrapper& a, JObjectWrapper& b) noexcept { + using std::swap; + swap(a.this_, b.this_); +} + + +// jclass ////////////////////////////////////////////////////////////////////////////////////////// + +namespace detail { + +// This is not a real type. It is used so people won't accidentally +// use a void* to initialize a NativeMethod. +struct NativeMethodWrapper; + +}; + +struct NativeMethod { + const char* name; + std::string descriptor; + detail::NativeMethodWrapper* wrapper; +}; + +inline local_ref JObjectWrapper::getSuperclass() const noexcept { + return adopt_local(internal::getEnv()->GetSuperclass(self())); +} + +inline void JObjectWrapper::registerNatives(std::initializer_list methods) { + const auto env = internal::getEnv(); + + JNINativeMethod jnimethods[methods.size()]; + size_t i = 0; + for (auto it = methods.begin(); it < methods.end(); ++it, ++i) { + jnimethods[i].name = it->name; + jnimethods[i].signature = it->descriptor.c_str(); + jnimethods[i].fnPtr = reinterpret_cast(it->wrapper); + } + + auto result = env->RegisterNatives(self(), jnimethods, methods.size()); + FACEBOOK_JNI_THROW_EXCEPTION_IF(result != JNI_OK); +} + +inline bool JObjectWrapper::isAssignableFrom(alias_ref other) const noexcept { + const auto env = internal::getEnv(); + const auto result = env->IsAssignableFrom(self(), other.get()); + return result; +} + +template +inline JConstructor JObjectWrapper::getConstructor() const { + return getConstructor(jmethod_traits::constructor_descriptor().c_str()); +} + +template +inline JConstructor JObjectWrapper::getConstructor(const char* descriptor) const { + constexpr auto constructor_method_name = ""; + return getMethod(constructor_method_name, descriptor); +} + +template +inline JMethod JObjectWrapper::getMethod(const char* name) const { + return getMethod(name, jmethod_traits::descriptor().c_str()); +} + +template +inline JMethod JObjectWrapper::getMethod( + const char* name, + const char* descriptor) const { + const auto env = internal::getEnv(); + const auto method = env->GetMethodID(self(), name, descriptor); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!method); + return JMethod{method}; +} + +template +inline JStaticMethod JObjectWrapper::getStaticMethod(const char* name) const { + return getStaticMethod(name, jmethod_traits::descriptor().c_str()); +} + +template +inline JStaticMethod JObjectWrapper::getStaticMethod( + const char* name, + const char* descriptor) const { + const auto env = internal::getEnv(); + const auto method = env->GetStaticMethodID(self(), name, descriptor); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!method); + return JStaticMethod{method}; +} + +template +inline JNonvirtualMethod JObjectWrapper::getNonvirtualMethod(const char* name) const { + return getNonvirtualMethod(name, jmethod_traits::descriptor().c_str()); +} + +template +inline JNonvirtualMethod JObjectWrapper::getNonvirtualMethod( + const char* name, + const char* descriptor) const { + const auto env = internal::getEnv(); + const auto method = env->GetMethodID(self(), name, descriptor); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!method); + return JNonvirtualMethod{method}; +} + +template +inline JField(), T>> +JObjectWrapper::getField(const char* name) const { + return getField(name, jtype_traits::descriptor().c_str()); +} + +template +inline JField(), T>> JObjectWrapper::getField( + const char* name, + const char* descriptor) const { + const auto env = internal::getEnv(); + auto field = env->GetFieldID(self(), name, descriptor); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!field); + return JField{field}; +} + +template +inline JStaticField(), T>> JObjectWrapper::getStaticField( + const char* name) const { + return getStaticField(name, jtype_traits::descriptor().c_str()); +} + +template +inline JStaticField(), T>> JObjectWrapper::getStaticField( + const char* name, + const char* descriptor) const { + const auto env = internal::getEnv(); + auto field = env->GetStaticFieldID(self(), name, descriptor); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!field); + return JStaticField{field}; +} + +template +inline T JObjectWrapper::getStaticFieldValue(JStaticField field) const noexcept { + return field.get(self()); +} + +template +inline local_ref JObjectWrapper::getStaticFieldValue(JStaticField field) noexcept { + return adopt_local(field.get(self())); +} + +template +inline void JObjectWrapper::setStaticFieldValue(JStaticField field, T value) noexcept { + field.set(self(), value); +} + +template +inline local_ref JObjectWrapper::newObject( + JConstructor constructor, + Args... args) const { + const auto env = internal::getEnv(); + auto object = env->NewObject(self(), constructor.getId(), args...); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!object); + return adopt_local(static_cast(object)); +} + +inline jclass JObjectWrapper::self() const noexcept { + return static_cast(this_); +} + +inline void registerNatives(const char* name, std::initializer_list methods) { + findClassLocal(name)->registerNatives(methods); +} + + +// jstring ///////////////////////////////////////////////////////////////////////////////////////// + +inline local_ref make_jstring(const std::string& modifiedUtf8) { + return make_jstring(modifiedUtf8.c_str()); +} + +inline jstring JObjectWrapper::self() const noexcept { + return static_cast(this_); +} + + +// jthrowable ////////////////////////////////////////////////////////////////////////////////////// + +inline jthrowable JObjectWrapper::self() const noexcept { + return static_cast(this_); +} + + +// jtypeArray ////////////////////////////////////////////////////////////////////////////////////// +template +inline ElementProxy::ElementProxy( + JObjectWrapper<_jtypeArray*>* target, + size_t idx) + : target_{target}, idx_{idx} {} + +template +inline ElementProxy& ElementProxy::operator=(const T& o) { + target_->setElement(idx_, o); + return *this; +} + +template +inline ElementProxy& ElementProxy::operator=(alias_ref& o) { + target_->setElement(idx_, o.get()); + return *this; +} + +template +inline ElementProxy& ElementProxy::operator=(alias_ref&& o) { + target_->setElement(idx_, o.get()); + return *this; +} + +template +inline ElementProxy& ElementProxy::operator=(const ElementProxy& o) { + auto src = o.target_->getElement(o.idx_); + target_->setElement(idx_, src.get()); + return *this; +} + +template +inline ElementProxy::ElementProxy::operator const local_ref () const { + return target_->getElement(idx_); +} + +template +inline ElementProxy::ElementProxy::operator local_ref () { + return target_->getElement(idx_); +} + +template +local_ref> JObjectWrapper>::newArray(size_t size) { + static auto elementClass = findClassStatic(jtype_traits::base_name().c_str()); + const auto env = internal::getEnv(); + auto rawArray = env->NewObjectArray(size, elementClass.get(), nullptr); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!rawArray); + return adopt_local(static_cast>(rawArray)); +} + +template +inline void JObjectWrapper>::setElement(size_t idx, const T& value) { + const auto env = internal::getEnv(); + env->SetObjectArrayElement(static_cast(self()), idx, value); +} + +template +inline local_ref JObjectWrapper>::getElement(size_t idx) { + const auto env = internal::getEnv(); + auto rawElement = env->GetObjectArrayElement(static_cast(self()), idx); + return adopt_local(static_cast(rawElement)); +} + +template +inline size_t JObjectWrapper>::size() { + const auto env = internal::getEnv(); + return env->GetArrayLength(static_cast(self())); +} + +template +inline ElementProxy JObjectWrapper>::operator[](size_t index) { + return ElementProxy(this, index); +} + +template +inline jtypeArray JObjectWrapper>::self() const noexcept { + return static_cast>(this_); +} + + +// jarray ///////////////////////////////////////////////////////////////////////////////////////// + +inline size_t JObjectWrapper::size() const noexcept { + const auto env = internal::getEnv(); + return env->GetArrayLength(self()); +} + +inline jarray JObjectWrapper::self() const noexcept { + return static_cast(this_); +} + + +// PinnedPrimitiveArray /////////////////////////////////////////////////////////////////////////// + +template +inline PinnedPrimitiveArray::PinnedPrimitiveArray(alias_ref array) noexcept + : array_{array} { + get(); +} + +template +PinnedPrimitiveArray::PinnedPrimitiveArray(PinnedPrimitiveArray&& o) noexcept { + array_ = std::move(o.array_); + elements_ = o.elements_; + isCopy_ = o.isCopy_; + size_ = o.size_; + o.elements_ = nullptr; + o.isCopy_ = false; + o.size_ = 0; +} + +template +PinnedPrimitiveArray& +PinnedPrimitiveArray::operator=(PinnedPrimitiveArray&& o) noexcept { + array_ = std::move(o.array_); + elements_ = o.elements_; + isCopy_ = o.isCopy_; + size_ = o.size_; + o.elements_ = nullptr; + o.isCopy_ = false; + o.size_ = 0; + return *this; +} + +template +inline T& PinnedPrimitiveArray::operator[](size_t index) { + FACEBOOK_JNI_THROW_EXCEPTION_IF(elements_ == nullptr); + return elements_[index]; +} + +template +inline bool PinnedPrimitiveArray::isCopy() const noexcept { + return isCopy_ == JNI_TRUE; +} + +template +inline size_t PinnedPrimitiveArray::size() const noexcept { + return size_; +} + +template +inline PinnedPrimitiveArray::~PinnedPrimitiveArray() noexcept { + if (elements_) { + release(); + } +} + +#pragma push_macro("DECLARE_PRIMITIVE_METHODS") +#undef DECLARE_PRIMITIVE_METHODS +#define DECLARE_PRIMITIVE_METHODS(TYPE, NAME) \ +template<> TYPE* PinnedPrimitiveArray::get(); \ +template<> void PinnedPrimitiveArray::release(); \ + +DECLARE_PRIMITIVE_METHODS(jboolean, Boolean) +DECLARE_PRIMITIVE_METHODS(jbyte, Byte) +DECLARE_PRIMITIVE_METHODS(jchar, Char) +DECLARE_PRIMITIVE_METHODS(jshort, Short) +DECLARE_PRIMITIVE_METHODS(jint, Int) +DECLARE_PRIMITIVE_METHODS(jlong, Long) +DECLARE_PRIMITIVE_METHODS(jfloat, Float) +DECLARE_PRIMITIVE_METHODS(jdouble, Double) +#pragma pop_macro("DECLARE_PRIMITIVE_METHODS") + + +template +inline alias_ref JavaClass::javaClassStatic() { + static auto cls = findClassStatic( + std::string(T::kJavaDescriptor + 1, strlen(T::kJavaDescriptor) - 2).c_str()); + return cls; +} + +template +inline local_ref JavaClass::javaClassLocal() { + std::string className(T::kJavaDescriptor + 1, strlen(T::kJavaDescriptor) - 2); + return findClassLocal(className.c_str()); +} + +}} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/CoreClasses.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/CoreClasses.h new file mode 100644 index 00000000..457b79b5 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/CoreClasses.h @@ -0,0 +1,510 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +/** @file CoreClasses.h + * + * In CoreClasses.h wrappers for the core classes (jobject, jclass, and jstring) is defined + * to provide access to corresponding JNI functions + some conveniance. + */ + +#include "Meta.h" +#include "References.h" + +#include + +#include + +namespace facebook { +namespace jni { + +/// Lookup a class by name. Note this functions returns an alias_ref that +/// points to a leaked global reference. This is appropriate for classes +/// that are never unloaded (which is any class in an Android app and most +/// Java programs). +/// +/// The most common use case for this is storing the result +/// in a "static auto" variable, or a static global. +/// +/// @return Returns a leaked global reference to the class +alias_ref findClassStatic(const char* name); + +/// Lookup a class by name. Note this functions returns a local reference, +/// which means that it must not be stored in a static variable. +/// +/// The most common use case for this is one-time initialization +/// (like caching method ids). +/// +/// @return Returns a global reference to the class +local_ref findClassLocal(const char* name); + +/// Check to see if two references refer to the same object. Comparison with nullptr +/// returns true if and only if compared to another nullptr. A weak reference that +/// refers to a reclaimed object count as nullptr. +bool isSameObject(alias_ref lhs, alias_ref rhs) noexcept; + + +/// Wrapper to provide functionality to jobject references +template<> +class JObjectWrapper { + public: + /// Java type descriptor + static constexpr const char* kJavaDescriptor = "Ljava/lang/Object;"; + + static constexpr const char* get_instantiated_java_descriptor() { return nullptr; } + + /// Wrap an existing JNI reference + JObjectWrapper(jobject reference = nullptr) noexcept; + + // Copy constructor + JObjectWrapper(const JObjectWrapper& other) noexcept; + + /// Get a @ref local_ref of the object's class + local_ref getClass() const noexcept; + + /// Checks if the object is an instance of a class + bool isInstanceOf(alias_ref cls) const noexcept; + + /// Get the primitive value of a field + template + T getFieldValue(JField field) const noexcept; + + /// Get and wrap the value of a field in a @ref local_ref + template + local_ref getFieldValue(JField field) noexcept; + + /// Set the value of field. Any Java type is accepted, including the primitive types + /// and raw reference types. + template + void setFieldValue(JField field, T value) noexcept; + + /// Convenience method to create a std::string representing the object + std::string toString() const; + + protected: + jobject this_; + + private: + template + friend class base_owned_ref; + + template + friend class alias_ref; + + friend void swap(JObjectWrapper& a, JObjectWrapper& b) noexcept; + + void set(jobject reference) noexcept; + jobject get() const noexcept; + jobject self() const noexcept; +}; + +using JObject = JObjectWrapper; + +void swap(JObjectWrapper& a, JObjectWrapper& b) noexcept; + + +/// Wrapper to provide functionality to jclass references +struct NativeMethod; + +template<> +class JObjectWrapper : public JObjectWrapper { + public: + /// Java type descriptor + static constexpr const char* kJavaDescriptor = "Ljava/lang/Class;"; + + using JObjectWrapper::JObjectWrapper; + + /// Get a @local_ref to the super class of this class + local_ref getSuperclass() const noexcept; + + /// Register native methods for the class. Usage looks like this: + /// + /// classRef->registerNatives({ + /// makeNativeMethod("nativeMethodWithAutomaticDescriptor", + /// methodWithAutomaticDescriptor), + /// makeNativeMethod("nativeMethodWithExplicitDescriptor", + /// "(Lcom/facebook/example/MyClass;)V", + /// methodWithExplicitDescriptor), + /// }); + /// + /// By default, C++ exceptions raised will be converted to Java exceptions. + /// To avoid this and get the "standard" JNI behavior of a crash when a C++ + /// exception is crashing out of the JNI method, declare the method noexcept. + void registerNatives(std::initializer_list methods); + + /// Check to see if the class is assignable from another class + /// @pre cls != nullptr + bool isAssignableFrom(alias_ref cls) const noexcept; + + /// Convenience method to lookup the constructor with descriptor as specified by the + /// type arguments + template + JConstructor getConstructor() const; + + /// Convenience method to lookup the constructor with specified descriptor + template + JConstructor getConstructor(const char* descriptor) const; + + /// Look up the method with given name and descriptor as specified with the type arguments + template + JMethod getMethod(const char* name) const; + + /// Look up the method with given name and descriptor + template + JMethod getMethod(const char* name, const char* descriptor) const; + + /// Lookup the field with the given name and deduced descriptor + template + JField(), T>> getField(const char* name) const; + + /// Lookup the field with the given name and descriptor + template + JField(), T>> getField(const char* name, const char* descriptor) const; + + /// Lookup the static field with the given name and deduced descriptor + template + JStaticField(), T>> getStaticField(const char* name) const; + + /// Lookup the static field with the given name and descriptor + template + JStaticField(), T>> getStaticField( + const char* name, + const char* descriptor) const; + + /// Get the primitive value of a static field + template + T getStaticFieldValue(JStaticField field) const noexcept; + + /// Get and wrap the value of a field in a @ref local_ref + template + local_ref getStaticFieldValue(JStaticField field) noexcept; + + /// Set the value of field. Any Java type is accepted, including the primitive types + /// and raw reference types. + template + void setStaticFieldValue(JStaticField field, T value) noexcept; + + /// Allocates a new object and invokes the specified constructor + template + local_ref newObject(JConstructor constructor, Args... args) const; + + /// Look up the static method with given name and descriptor as specified with the type arguments + template + JStaticMethod getStaticMethod(const char* name) const; + + /// Look up the static method with given name and descriptor + template + JStaticMethod getStaticMethod(const char* name, const char* descriptor) const; + + /// Look up the non virtual method with given name and descriptor as specified with the + /// type arguments + template + JNonvirtualMethod getNonvirtualMethod(const char* name) const; + + /// Look up the non virtual method with given name and descriptor + template + JNonvirtualMethod getNonvirtualMethod(const char* name, const char* descriptor) const; + + private: + jclass self() const noexcept; +}; + +using JClass = JObjectWrapper; + +// Convenience method to register methods on a class without holding +// onto the class object. +void registerNatives(const char* name, std::initializer_list methods); + +/// Wrapper to provide functionality to jstring references +template<> +class JObjectWrapper : public JObjectWrapper { + public: + /// Java type descriptor + static constexpr const char* kJavaDescriptor = "Ljava/lang/String;"; + + using JObjectWrapper::JObjectWrapper; + + /// Convenience method to convert a jstring object to a std::string + std::string toStdString() const; + + private: + jstring self() const noexcept; +}; + +/// Convenience functions to convert a std::string or const char* into a @ref local_ref to a +/// jstring +local_ref make_jstring(const char* modifiedUtf8); +local_ref make_jstring(const std::string& modifiedUtf8); + +using JString = JObjectWrapper; + +/// Wrapper to provide functionality to jthrowable references +template<> +class JObjectWrapper : public JObjectWrapper { + public: + /// Java type descriptor + static constexpr const char* kJavaDescriptor = "Ljava/lang/Throwable;"; + + using JObjectWrapper::JObjectWrapper; + + private: + jthrowable self() const noexcept; +}; + + +/// @cond INTERNAL +template class _jtypeArray : public _jobjectArray {}; +// @endcond +/// Wrapper to provide functionality for arrays of j-types +template using jtypeArray = _jtypeArray*; + +template +class ElementProxy { + private: + JObjectWrapper<_jtypeArray*>* target_; + size_t idx_; + + public: + ElementProxy(JObjectWrapper<_jtypeArray*>* target, size_t idx); + + ElementProxy& operator=(const T& o); + + ElementProxy& operator=(alias_ref& o); + + ElementProxy& operator=(alias_ref&& o); + + ElementProxy& operator=(const ElementProxy& o); + + operator const local_ref () const; + + operator local_ref (); + }; + +template +class JObjectWrapper> : public JObjectWrapper { + public: + static constexpr const char* kJavaDescriptor = nullptr; + static std::string get_instantiated_java_descriptor() { + return "[" + jtype_traits::descriptor(); + }; + + using JObjectWrapper::JObjectWrapper; + + /// Allocate a new array from Java heap, for passing as a JNI parameter or return value. + /// NOTE: if using as a return value, you want to call release() instead of get() on the + /// smart pointer. + static local_ref> newArray(size_t count); + + /// Assign an object to the array. + /// Typically you will use the shorthand (*ref)[idx]=value; + void setElement(size_t idx, const T& value); + + /// Read an object from the array. + /// Typically you will use the shorthand + /// T value = (*ref)[idx]; + /// If you use auto, you'll get an ElementProxy, which may need to be cast. + local_ref getElement(size_t idx); + + /// Get the size of the array. + size_t size(); + + /// EXPERIMENTAL SUBSCRIPT SUPPORT + /// This implementation of [] returns a proxy object which then has a bunch of specializations + /// (adopt_local free function, operator= and casting overloads on the ElementProxy) that can + /// make code look like it is dealing with a T rather than an obvious proxy. In particular, the + /// proxy in this iteration does not read a value and therefore does not create a LocalRef + /// until one of these other operators is used. There are certainly holes that you may find + /// by using idioms that haven't been tried yet. Consider yourself warned. On the other hand, + /// it does make for some idiomatic assignment code; see TestBuildStringArray in fbjni_tests + /// for some examples. + ElementProxy operator[](size_t idx); + + private: + jtypeArray self() const noexcept; +}; + +template +using JArrayClass = JObjectWrapper>; + +template +local_ref> adopt_local_array(jobjectArray ref) { + return adopt_local(static_cast>(ref)); +} + +template +local_ref adopt_local(ElementProxy elementProxy) { + return static_cast>(elementProxy); +} + +/// Wrapper to provide functionality to jarray references. +/// This is an empty holder by itself. Construct a PinnedPrimitiveArray to actually interact with +/// the elements of the array. +template<> +class JObjectWrapper : public JObjectWrapper { + public: + static constexpr const char* kJavaDescriptor = nullptr; + + using JObjectWrapper::JObjectWrapper; + size_t size() const noexcept; + + private: + jarray self() const noexcept; +}; + +using JArray = JObjectWrapper; + +template +class PinnedPrimitiveArray; + +#pragma push_macro("DECLARE_PRIMITIVE_ARRAY_UTILS") +#undef DECLARE_PRIMITIVE_ARRAY_UTILS +#define DECLARE_PRIMITIVE_ARRAY_UTILS(TYPE, NAME, DESC) \ + \ +local_ref make_ ## TYPE ## _array(jsize size); \ + \ +template<> class JObjectWrapper : public JArray { \ + public: \ + static constexpr const char* kJavaDescriptor = "[" # DESC; \ + \ + using JArray::JArray; \ + \ + static local_ref newArray(size_t count); \ + \ + j ## TYPE* getRegion(jsize start, jsize length, j ## TYPE* buf); \ + std::unique_ptr getRegion(jsize start, jsize length); \ + void setRegion(jsize start, jsize length, const j ## TYPE* buf); \ + PinnedPrimitiveArray pin(); \ + \ + private: \ + j ## TYPE ## Array self() const noexcept { \ + return static_cast(this_); \ + } \ +}; \ + \ +using JArray ## NAME = JObjectWrapper \ + + +DECLARE_PRIMITIVE_ARRAY_UTILS(boolean, Boolean, "Z"); +DECLARE_PRIMITIVE_ARRAY_UTILS(byte, Byte, "B"); +DECLARE_PRIMITIVE_ARRAY_UTILS(char, Char, "C"); +DECLARE_PRIMITIVE_ARRAY_UTILS(short, Short, "S"); +DECLARE_PRIMITIVE_ARRAY_UTILS(int, Int, "I"); +DECLARE_PRIMITIVE_ARRAY_UTILS(long, Long, "J"); +DECLARE_PRIMITIVE_ARRAY_UTILS(float, Float, "F"); +DECLARE_PRIMITIVE_ARRAY_UTILS(double, Double, "D"); + +#pragma pop_macro("DECLARE_PRIMITIVE_ARRAY_UTILS") + + +/// RAII class for pinned primitive arrays +/// This currently only supports read/write access to existing java arrays. You can't create a +/// primitive array this way yet. This class also pins the entire array into memory during the +/// lifetime of the PinnedPrimitiveArray. If you need to unpin the array manually, call the +/// release() function. During a long-running block of code, you should unpin the array as soon +/// as you're done with it, to avoid holding up the Java garbage collector. +template +class PinnedPrimitiveArray { + public: + static_assert(is_jni_primitive::value, + "PinnedPrimitiveArray requires primitive jni type."); + + PinnedPrimitiveArray(PinnedPrimitiveArray&&) noexcept; + PinnedPrimitiveArray(const PinnedPrimitiveArray&) = delete; + ~PinnedPrimitiveArray() noexcept; + + PinnedPrimitiveArray& operator=(PinnedPrimitiveArray&&) noexcept; + PinnedPrimitiveArray& operator=(const PinnedPrimitiveArray&) = delete; + + T* get(); + void release(); + + const T& operator[](size_t index) const; + T& operator[](size_t index); + bool isCopy() const noexcept; + size_t size() const noexcept; + + private: + alias_ref array_; + T* elements_; + jboolean isCopy_; + size_t size_; + + PinnedPrimitiveArray(alias_ref) noexcept; + + friend class JObjectWrapper; + friend class JObjectWrapper; + friend class JObjectWrapper; + friend class JObjectWrapper; + friend class JObjectWrapper; + friend class JObjectWrapper; + friend class JObjectWrapper; + friend class JObjectWrapper; +}; + + +namespace detail { + +class BaseJavaClass { +public: + typedef _jobject _javaobject; + typedef _javaobject* javaobject; +}; + +} + +// Together, these classes allow convenient use of any class with the fbjni +// helpers. To use: +// +// struct MyClass : public JavaClass { +// constexpr static auto kJavaDescriptor = "Lcom/example/package/MyClass;"; +// }; +// +// alias_ref myClass = foo(); + +template +class JavaClass { +public: + // JNI pattern for jobject assignable pointer + struct _javaobject : public Base::_javaobject { + typedef T javaClass; + }; + typedef _javaobject* javaobject; + + static alias_ref javaClassStatic(); + static local_ref javaClassLocal(); +}; + +template +class JObjectWrapper::value && + std::is_class::type::javaClass>::value + >::type> + : public JObjectWrapper { +public: + static constexpr const char* kJavaDescriptor = + std::remove_pointer::type::javaClass::kJavaDescriptor; + + using JObjectWrapper::JObjectWrapper; + + template + JObjectWrapper(const JObjectWrapper& w) + : JObjectWrapper(w) { + static_assert(std::is_convertible::value, + "U must be convertible to T"); + } +}; + +}} + +#include "CoreClasses-inl.h" +// This is here because code in Meta-inl.h uses alias_ref, which +// requires JObjectWrapper to be concrete before it can work. +#include "Meta-inl.h" diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Exceptions.cpp b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Exceptions.cpp new file mode 100644 index 00000000..20925ae8 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Exceptions.cpp @@ -0,0 +1,399 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include "Exceptions.h" +#include "CoreClasses.h" +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace facebook { +namespace jni { + +// CommonJniExceptions ///////////////////////////////////////////////////////////////////////////// + +class CommonJniExceptions { + public: + static void init(); + + static jclass getThrowableClass() { + return throwableClass_; + } + + static jclass getUnknownCppExceptionClass() { + return unknownCppExceptionClass_; + } + + static jthrowable getUnknownCppExceptionObject() { + return unknownCppExceptionObject_; + } + + static jthrowable getRuntimeExceptionObject() { + return runtimeExceptionObject_; + } + + private: + static jclass throwableClass_; + static jclass unknownCppExceptionClass_; + static jthrowable unknownCppExceptionObject_; + static jthrowable runtimeExceptionObject_; +}; + +// The variables in this class are all JNI global references and are intentionally leaked because +// we assume this library cannot be unloaded. These global references are created manually instead +// of using global_ref from References.h to avoid circular dependency. +jclass CommonJniExceptions::throwableClass_ = nullptr; +jclass CommonJniExceptions::unknownCppExceptionClass_ = nullptr; +jthrowable CommonJniExceptions::unknownCppExceptionObject_ = nullptr; +jthrowable CommonJniExceptions::runtimeExceptionObject_ = nullptr; + + +// Variable to guarantee that fallback exceptions have been initialized early. We don't want to +// do pure dynamic initialization -- we want to warn programmers early that they need to run the +// helpers at library load time instead of lazily getting them when the exception helpers are +// first used. +static std::atomic gIsInitialized(false); + +void CommonJniExceptions::init() { + JNIEnv* env = internal::getEnv(); + FBASSERTMSGF(env, "Could not get JNI Environment"); + + // Throwable class + jclass localThrowableClass = env->FindClass("java/lang/Throwable"); + FBASSERT(localThrowableClass); + throwableClass_ = static_cast(env->NewGlobalRef(localThrowableClass)); + FBASSERT(throwableClass_); + env->DeleteLocalRef(localThrowableClass); + + // UnknownCppException class + jclass localUnknownCppExceptionClass = env->FindClass("com/facebook/jni/UnknownCppException"); + FBASSERT(localUnknownCppExceptionClass); + jmethodID unknownCppExceptionConstructorMID = env->GetMethodID( + localUnknownCppExceptionClass, + "", + "()V"); + FBASSERT(unknownCppExceptionConstructorMID); + unknownCppExceptionClass_ = static_cast(env->NewGlobalRef(localUnknownCppExceptionClass)); + FBASSERT(unknownCppExceptionClass_); + env->DeleteLocalRef(localUnknownCppExceptionClass); + + // UnknownCppException object + jthrowable localUnknownCppExceptionObject = static_cast(env->NewObject( + unknownCppExceptionClass_, + unknownCppExceptionConstructorMID)); + FBASSERT(localUnknownCppExceptionObject); + unknownCppExceptionObject_ = static_cast(env->NewGlobalRef( + localUnknownCppExceptionObject)); + FBASSERT(unknownCppExceptionObject_); + env->DeleteLocalRef(localUnknownCppExceptionObject); + + // RuntimeException object + jclass localRuntimeExceptionClass = env->FindClass("java/lang/RuntimeException"); + FBASSERT(localRuntimeExceptionClass); + + jmethodID runtimeExceptionConstructorMID = env->GetMethodID( + localRuntimeExceptionClass, + "", + "()V"); + FBASSERT(runtimeExceptionConstructorMID); + jthrowable localRuntimeExceptionObject = static_cast(env->NewObject( + localRuntimeExceptionClass, + runtimeExceptionConstructorMID)); + FBASSERT(localRuntimeExceptionObject); + runtimeExceptionObject_ = static_cast(env->NewGlobalRef(localRuntimeExceptionObject)); + FBASSERT(runtimeExceptionObject_); + + env->DeleteLocalRef(localRuntimeExceptionClass); + env->DeleteLocalRef(localRuntimeExceptionObject); +} + + +// initExceptionHelpers() ////////////////////////////////////////////////////////////////////////// + +void internal::initExceptionHelpers() { + CommonJniExceptions::init(); + gIsInitialized.store(true, std::memory_order_seq_cst); +} + +void assertIfExceptionsNotInitialized() { + // Use relaxed memory order because we don't need memory barriers. + // The real init-once enforcement is done by the compiler for the + // "static" in initExceptionHelpers. + FBASSERTMSGF(gIsInitialized.load(std::memory_order_relaxed), + "initExceptionHelpers was never called!"); +} + +// Exception throwing & translating functions ////////////////////////////////////////////////////// + +// Functions that throw Java exceptions + +namespace { + +void setJavaExceptionAndAbortOnFailure(jthrowable throwable) noexcept { + assertIfExceptionsNotInitialized(); + JNIEnv* env = internal::getEnv(); + if (throwable) { + env->Throw(throwable); + } + if (env->ExceptionCheck() != JNI_TRUE) { + std::abort(); + } +} + +void setDefaultException() noexcept { + assertIfExceptionsNotInitialized(); + setJavaExceptionAndAbortOnFailure(CommonJniExceptions::getRuntimeExceptionObject()); +} + +void setCppSystemErrorExceptionInJava(const std::system_error& ex) noexcept { + assertIfExceptionsNotInitialized(); + JNIEnv* env = internal::getEnv(); + jclass cppSystemErrorExceptionClass = env->FindClass( + "com/facebook/jni/CppSystemErrorException"); + if (!cppSystemErrorExceptionClass) { + setDefaultException(); + return; + } + jmethodID constructorMID = env->GetMethodID( + cppSystemErrorExceptionClass, + "", + "(Ljava/lang/String;I)V"); + if (!constructorMID) { + setDefaultException(); + return; + } + jthrowable cppSystemErrorExceptionObject = static_cast(env->NewObject( + cppSystemErrorExceptionClass, + constructorMID, + env->NewStringUTF(ex.what()), + ex.code().value())); + setJavaExceptionAndAbortOnFailure(cppSystemErrorExceptionObject); +} + +template +void setNewJavaException(jclass exceptionClass, const char* fmt, ARGS... args) { + assertIfExceptionsNotInitialized(); + int msgSize = snprintf(nullptr, 0, fmt, args...); + JNIEnv* env = internal::getEnv(); + + try { + char *msg = (char*) alloca(msgSize); + snprintf(msg, kMaxExceptionMessageBufferSize, fmt, args...); + env->ThrowNew(exceptionClass, msg); + } catch (...) { + env->ThrowNew(exceptionClass, ""); + } + + if (env->ExceptionCheck() != JNI_TRUE) { + setDefaultException(); + } +} + +void setNewJavaException(jclass exceptionClass, const char* msg) { + assertIfExceptionsNotInitialized(); + setNewJavaException(exceptionClass, "%s", msg); +} + +template +void setNewJavaException(const char* className, const char* fmt, ARGS... args) { + assertIfExceptionsNotInitialized(); + JNIEnv* env = internal::getEnv(); + jclass exceptionClass = env->FindClass(className); + if (env->ExceptionCheck() != JNI_TRUE && !exceptionClass) { + // If FindClass() has failed but no exception has been thrown, throw a default exception. + setDefaultException(); + return; + } + setNewJavaException(exceptionClass, fmt, args...); +} + +} + +// Functions that throw C++ exceptions + +// TODO(T6618159) Take a stack dump here to save context if it results in a crash when propagated +void throwPendingJniExceptionAsCppException() { + assertIfExceptionsNotInitialized(); + JNIEnv* env = internal::getEnv(); + if (env->ExceptionCheck() == JNI_FALSE) { + return; + } + + jthrowable throwable = env->ExceptionOccurred(); + if (!throwable) { + throw std::runtime_error("Unable to get pending JNI exception."); + } + + env->ExceptionClear(); + throw JniException(throwable); +} + +void throwCppExceptionIf(bool condition) { + assertIfExceptionsNotInitialized(); + if (!condition) { + return; + } + + JNIEnv* env = internal::getEnv(); + if (env->ExceptionCheck() == JNI_TRUE) { + throwPendingJniExceptionAsCppException(); + return; + } + + throw JniException(); +} + +void throwNewJavaException(jthrowable throwable) { + throw JniException(throwable); +} + +void throwNewJavaException(const char* throwableName, const char* msg) { + // If anything of the fbjni calls fail, an exception of a suitable + // form will be thrown, which is what we want. + auto throwableClass = findClassLocal(throwableName); + auto throwable = throwableClass->newObject( + throwableClass->getConstructor(), + make_jstring(msg).release()); + throwNewJavaException(throwable.get()); +} + +// Translate C++ to Java Exception + +void translatePendingCppExceptionToJavaException() noexcept { + assertIfExceptionsNotInitialized(); + try { + try { + throw; + } catch(const JniException& ex) { + ex.setJavaException(); + } catch(const std::ios_base::failure& ex) { + setNewJavaException("java/io/IOException", ex.what()); + } catch(const std::bad_alloc& ex) { + setNewJavaException("java/lang/OutOfMemoryError", ex.what()); + } catch(const std::out_of_range& ex) { + setNewJavaException("java/lang/ArrayIndexOutOfBoundsException", ex.what()); + } catch(const std::system_error& ex) { + setCppSystemErrorExceptionInJava(ex); + } catch(const std::runtime_error& ex) { + setNewJavaException("java/lang/RuntimeException", ex.what()); + } catch(const std::exception& ex) { + setNewJavaException("com/facebook/jni/CppException", ex.what()); + } catch(const char* msg) { + setNewJavaException(CommonJniExceptions::getUnknownCppExceptionClass(), msg); + } catch(...) { + setJavaExceptionAndAbortOnFailure(CommonJniExceptions::getUnknownCppExceptionObject()); + } + } catch(...) { + // This block aborts the program, if something bad happens when handling exceptions, thus + // keeping this function noexcept. + std::abort(); + } +} + +// JniException //////////////////////////////////////////////////////////////////////////////////// + +const std::string JniException::kExceptionMessageFailure_ = "Unable to get exception message."; + +JniException::JniException() : JniException(CommonJniExceptions::getRuntimeExceptionObject()) { } + +JniException::JniException(jthrowable throwable) : isMessageExtracted_(false) { + assertIfExceptionsNotInitialized(); + throwableGlobalRef_ = static_cast(internal::getEnv()->NewGlobalRef(throwable)); + if (!throwableGlobalRef_) { + throw std::bad_alloc(); + } +} + +JniException::JniException(JniException &&rhs) + : throwableGlobalRef_(std::move(rhs.throwableGlobalRef_)), + what_(std::move(rhs.what_)), + isMessageExtracted_(rhs.isMessageExtracted_) { + rhs.throwableGlobalRef_ = nullptr; +} + +JniException::JniException(const JniException &rhs) + : what_(rhs.what_), isMessageExtracted_(rhs.isMessageExtracted_) { + JNIEnv* env = internal::getEnv(); + if (rhs.getThrowable()) { + throwableGlobalRef_ = static_cast(env->NewGlobalRef(rhs.getThrowable())); + if (!throwableGlobalRef_) { + throw std::bad_alloc(); + } + } else { + throwableGlobalRef_ = nullptr; + } +} + +JniException::~JniException() noexcept { + if (throwableGlobalRef_) { + internal::getEnv()->DeleteGlobalRef(throwableGlobalRef_); + } +} + +jthrowable JniException::getThrowable() const noexcept { + return throwableGlobalRef_; +} + +// TODO 6900503: consider making this thread-safe. +void JniException::populateWhat() const noexcept { + JNIEnv* env = internal::getEnv(); + + jmethodID toStringMID = env->GetMethodID( + CommonJniExceptions::getThrowableClass(), + "toString", + "()Ljava/lang/String;"); + jstring messageJString = (jstring) env->CallObjectMethod( + throwableGlobalRef_, + toStringMID); + + isMessageExtracted_ = true; + + if (env->ExceptionCheck()) { + env->ExceptionClear(); + what_ = kExceptionMessageFailure_; + return; + } + + const char* chars = env->GetStringUTFChars(messageJString, nullptr); + if (!chars) { + what_ = kExceptionMessageFailure_; + return; + } + + try { + what_ = std::string(chars); + } catch(...) { + what_ = kExceptionMessageFailure_; + } + + env->ReleaseStringUTFChars(messageJString, chars); +} + +const char* JniException::what() const noexcept { + if (!isMessageExtracted_) { + populateWhat(); + } + return what_.c_str(); +} + +void JniException::setJavaException() const noexcept { + setJavaExceptionAndAbortOnFailure(throwableGlobalRef_); +} + +}} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Exceptions.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Exceptions.h new file mode 100644 index 00000000..9ba9367f --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Exceptions.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/** + * @file Exceptions.h + * + * After invoking a JNI function that can throw a Java exception, the macro + * @ref FACEBOOK_JNI_THROW_PENDING_EXCEPTION() or @ref FACEBOOK_JNI_THROW_EXCEPTION_IF() + * should be invoked. + * + * IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! + * To use these methods you MUST call initExceptionHelpers() when your library is loaded. + */ + +#pragma once + +#include +#include +#include + +#include + +#include "Common.h" + +// If a pending JNI Java exception is found, wraps it in a JniException object and throws it as +// a C++ exception. +#define FACEBOOK_JNI_THROW_PENDING_EXCEPTION() \ + ::facebook::jni::throwPendingJniExceptionAsCppException() + +// If the condition is true, throws a JniException object, which wraps the pending JNI Java +// exception if any. If no pending exception is found, throws a JniException object that wraps a +// RuntimeException throwable.  +#define FACEBOOK_JNI_THROW_EXCEPTION_IF(CONDITION) \ + ::facebook::jni::throwCppExceptionIf(CONDITION) + +namespace facebook { +namespace jni { + +namespace internal { + void initExceptionHelpers(); +} + +/** + * Before using any of the state initialized above, call this. It + * will assert if initialization has not yet occurred. + */ +void assertIfExceptionsNotInitialized(); + +// JniException //////////////////////////////////////////////////////////////////////////////////// + +/** + * This class wraps a Java exception into a C++ exception; if the exception is routed back + * to the Java side, it can be unwrapped and just look like a pure Java interaction. The class + * is resilient to errors while creating the exception, falling back to some pre-allocated + * exceptions if a new one cannot be allocated or populated. + * + * Note: the what() method of this class is not thread-safe (t6900503). + */ +class JniException : public std::exception { + public: + JniException(); + + explicit JniException(jthrowable throwable); + + JniException(JniException &&rhs); + + JniException(const JniException &other); + + ~JniException() noexcept; + + jthrowable getThrowable() const noexcept; + + virtual const char* what() const noexcept; + + void setJavaException() const noexcept; + + private: + jthrowable throwableGlobalRef_; + mutable std::string what_; + mutable bool isMessageExtracted_; + const static std::string kExceptionMessageFailure_; + + void populateWhat() const noexcept; +}; + +// Exception throwing & translating functions ////////////////////////////////////////////////////// + +// Functions that throw C++ exceptions + +void throwPendingJniExceptionAsCppException(); + +void throwCppExceptionIf(bool condition); + +static const int kMaxExceptionMessageBufferSize = 512; + +[[noreturn]] void throwNewJavaException(jthrowable); + +[[noreturn]] void throwNewJavaException(const char* throwableName, const char* msg); + +// These methods are the preferred way to throw a Java exception from +// a C++ function. They create and throw a C++ exception which wraps +// a Java exception, so the C++ flow is interrupted. Then, when +// translatePendingCppExceptionToJavaException is called at the +// topmost level of the native stack, the wrapped Java exception is +// thrown to the java caller. +template +[[noreturn]] void throwNewJavaException(const char* throwableName, const char* fmt, Args... args) { + assertIfExceptionsNotInitialized(); + int msgSize = snprintf(nullptr, 0, fmt, args...); + + char *msg = (char*) alloca(msgSize); + snprintf(msg, kMaxExceptionMessageBufferSize, fmt, args...); + throwNewJavaException(throwableName, msg); +} + +// Identifies any pending C++ exception and throws it as a Java exception. If the exception can't +// be thrown, it aborts the program. This is a noexcept function at C++ level. +void translatePendingCppExceptionToJavaException() noexcept; + +// For convenience, some exception names in java.lang are available here. + +const char* const gJavaLangIllegalArgumentException = "java/lang/IllegalArgumentException"; + +}} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Hybrid.cpp b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Hybrid.cpp new file mode 100644 index 00000000..ebcb778d --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Hybrid.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include "Hybrid.h" + +#include "Exceptions.h" +#include "Registration.h" + +namespace facebook { +namespace jni { + +namespace detail { + +void setNativePointer(alias_ref hybridData, + std::unique_ptr new_value) { + static auto pointerField = hybridData->getClass()->getField("mNativePointer"); + auto* old_value = reinterpret_cast(hybridData->getFieldValue(pointerField)); + if (new_value) { + // Modify should only ever be called once with a non-null + // new_value. If this happens again it's a programmer error, so + // blow up. + FBASSERTMSGF(old_value == 0, "Attempt to set C++ native pointer twice"); + } else if (old_value == 0) { + return; + } + // delete on a null pointer is defined to be a noop. + delete old_value; + // This releases ownership from the unique_ptr, and passes the pointer, and + // ownership of it, to HybridData which is managed by the java GC. The + // finalizer on hybridData calls resetNative which will delete the object, if + // reseetNative has not already been called. + hybridData->setFieldValue(pointerField, reinterpret_cast(new_value.release())); +} + +BaseHybridClass* getNativePointer(alias_ref hybridData) { + static auto pointerField = hybridData->getClass()->getField("mNativePointer"); + auto* value = reinterpret_cast(hybridData->getFieldValue(pointerField)); + if (!value) { + throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException"); + } + return value; +} + +local_ref getHybridData(alias_ref jthis, + JField field) { + auto hybridData = jthis->getFieldValue(field); + if (!hybridData) { + throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException"); + } + return hybridData; +} + +} + +namespace { + +void resetNative(alias_ref jthis) { + detail::setNativePointer(jthis, nullptr); +} + +} + +void HybridDataOnLoad() { + registerNatives("com/facebook/jni/HybridData", { + makeNativeMethod("resetNative", resetNative), + }); +} + +}} + diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Hybrid.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Hybrid.h new file mode 100644 index 00000000..8b62ccde --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Hybrid.h @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include +#include +#include +#include "CoreClasses.h" + +namespace facebook { +namespace jni { + +namespace detail { + +class BaseHybridClass : public BaseJavaClass { +public: + virtual ~BaseHybridClass() {} +}; + +struct HybridData : public JavaClass { + constexpr static auto kJavaDescriptor = "Lcom/facebook/jni/HybridData;"; +}; + +void setNativePointer(alias_ref hybridData, + std::unique_ptr new_value); +BaseHybridClass* getNativePointer(alias_ref hybridData); +local_ref getHybridData(alias_ref jthis, + JField field); + +// Normally, pass through types unmolested. +template +struct Convert { + typedef T jniType; + static jniType fromJni(jniType t) { + return t; + } + static jniType toJniRet(jniType t) { + return t; + } + static jniType toCall(jniType t) { + return t; + } +}; + +// This is needed for return conversion +template <> +struct Convert { + typedef void jniType; +}; + +// convert to std::string from jstring +template <> +struct Convert { + typedef jstring jniType; + static std::string fromJni(jniType t) { + return wrap_alias(t)->toStdString(); + } + static jniType toJniRet(const std::string& t) { + return make_jstring(t).release(); + } + static local_ref toCall(const std::string& t) { + return make_jstring(t); + } +}; + +// convert return from const char* +template <> +struct Convert { + typedef jstring jniType; + // no automatic synthesis of const char*. (It can't be freed.) + static jniType toJniRet(const char* t) { + return make_jstring(t).release(); + } + static local_ref toCall(const char* t) { + return make_jstring(t); + } +}; + +// jboolean is an unsigned char, not a bool. Allow it to work either way. +template<> +struct Convert { + typedef jboolean jniType; + static bool fromJni(jniType t) { + return t; + } + static jniType toJniRet(bool t) { + return t; + } + static jniType toCall(bool t) { + return t; + } +}; + +// convert to alias_ref from T +template +struct Convert> { + typedef T jniType; + static alias_ref fromJni(jniType t) { + return wrap_alias(t); + } + static jniType toJniRet(alias_ref t) { + return t.get(); + } + static jniType toCall(alias_ref t) { + return t.get(); + } +}; + +// convert return from local_ref +template +struct Convert> { + typedef T jniType; + // No automatic synthesis of local_ref + static jniType toJniRet(local_ref t) { + return t.release(); + } + static jniType toCall(local_ref t) { + return t.get(); + } +}; + +// convert return from global_ref +template +struct Convert> { + typedef T jniType; + // No automatic synthesis of global_ref + static jniType toJniRet(global_ref t) { + return t.get(); + } + static jniType toCall(global_ref t) { + return t.get(); + } +}; + +// In order to avoid potentially filling the jni locals table, +// temporary objects (right now, this is just jstrings) need to be +// released. This is done by returning a holder which autoconverts to +// jstring. This is only relevant when the jniType is passed down, as +// in newObjectJavaArgs. + +template +inline T callToJni(T&& t) { + return t; +} + +inline jstring callToJni(local_ref&& sref) { + return sref.get(); +} + +struct jstring_holder { + local_ref s_; + jstring_holder(const char* s) : s_(make_jstring(s)) {} + operator jstring() { return s_.get(); } +}; + +template +struct HybridRoot {}; + +template +struct HybridRoot::value>::type> + : public BaseHybridClass {}; + +} + +template +class HybridClass : public Base + , public detail::HybridRoot + , public JavaClass { +public: + typedef detail::HybridData::javaobject jhybriddata; + typedef typename JavaClass::javaobject jhybridobject; + + using JavaClass::javaClassStatic; + using JavaClass::javaClassLocal; + using JavaClass::javaobject; + typedef typename JavaClass::_javaobject _javaobject; + +protected: + typedef HybridClass HybridBase; + + // This ensures that a C++ hybrid part cannot be created on its own + // by default. If a hybrid wants to enable this, it can provide its + // own public ctor, or change the accessibility of this to public. + using Base::Base; + + static void registerHybrid(std::initializer_list methods) { + javaClassStatic()->registerNatives(methods); + } + + static local_ref makeHybridData(std::unique_ptr cxxPart) { + static auto dataCtor = detail::HybridData::javaClassStatic()->getConstructor(); + auto hybridData = detail::HybridData::javaClassStatic()->newObject(dataCtor); + detail::setNativePointer(hybridData, std::move(cxxPart)); + return hybridData; + } + + template + static local_ref makeCxxInstance(Args&&... args) { + return makeHybridData(std::unique_ptr(new T(std::forward(args)...))); + } + +public: + // Factory method for creating a hybrid object where the arguments + // are used to initialize the C++ part directly without passing them + // through java. This method requires the Java part to have a ctor + // which takes a HybridData, and for the C++ part to have a ctor + // compatible with the arguments passed here. For safety, the ctor + // can be private, and the hybrid declared a friend of its base, so + // the hybrid can only be created from here. + // + // Exception behavior: This can throw an exception if creating the + // C++ object fails, or any JNI methods throw. + template + static local_ref newObjectCxxArgs(Args&&... args) { + auto hybridData = makeCxxInstance(std::forward(args)...); + static auto ctor = javaClassStatic()->template getConstructor(); + return javaClassStatic()->newObject(ctor, hybridData.get()); + } + + // Factory method for creating a hybrid object where the arguments + // are passed to the java ctor. + template + static local_ref newObjectJavaArgs(Args&&... args) { + static auto ctor = + javaClassStatic()->template getConstructor< + jhybridobject(typename detail::Convert::type>::jniType...)>(); + // This can't use the same impl as Convert::toJniRet because that + // function sometimes creates and then releases local_refs, which + // could potentially cause the locals table to fill. Instead, we + // use two calls, one which can return a local_ref if needed, and + // a second which extracts its value. The lifetime of the + // local_ref is the expression, after which it is destroyed and + // the local_ref is cleaned up. + auto lref = + javaClassStatic()->newObject( + ctor, detail::callToJni( + detail::Convert::type>::toCall(args))...); + return lref; + } + + // If a hybrid class throws an exception which derives from + // std::exception, it will be passed to mapException on the hybrid + // class, or nearest ancestor. This allows boilerplate exception + // translation code (for example, calling throwNewJavaException on a + // particular java class) to be hoisted to a common function. If + // mapException returns, then the std::exception will be translated + // to Java. + static void mapException(const std::exception& ex) {} +}; + +// Given a *_ref object which refers to a hybrid class, this will reach inside +// of it, find the mHybridData, extract the C++ instance pointer, cast it to +// the appropriate type, and return it. +template +inline typename std::remove_pointer::type::javaClass* cthis(T jthis) { + static auto dataField = + jthis->getClass()->template getField("mHybridData"); + // I'd like to use dynamic_cast here, but -fno-rtti is the default. + auto* value = static_cast::type::javaClass*>( + detail::getNativePointer(detail::getHybridData(jthis, dataField))); + // This would require some serious programmer error. + FBASSERTMSGF(value != 0, "Incorrect C++ type in hybrid field"); + return value; +} + +void HybridDataOnLoad(); + +} +} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Meta-inl.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Meta-inl.h new file mode 100644 index 00000000..d51157b1 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Meta-inl.h @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include + +#include "Common.h" +#include "Exceptions.h" + +namespace facebook { +namespace jni { + +// JMethod ///////////////////////////////////////////////////////////////////////////////////////// + +inline JMethodBase::JMethodBase(jmethodID method_id) noexcept + : method_id_{method_id} +{} + +inline JMethodBase::operator bool() const noexcept { + return method_id_ != nullptr; +} + +inline jmethodID JMethodBase::getId() const noexcept { + return method_id_; +} + +template +inline void JMethod::operator()(alias_ref self, Args... args) { + const auto env = internal::getEnv(); + env->CallVoidMethod(self.get(), getId(), args...); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); +} + +#pragma push_macro("DEFINE_PRIMITIVE_CALL") +#undef DEFINE_PRIMITIVE_CALL +#define DEFINE_PRIMITIVE_CALL(TYPE, METHOD) \ +template \ +inline TYPE JMethod::operator()(alias_ref self, Args... args) { \ + const auto env = internal::getEnv(); \ + auto result = env->Call ## METHOD ## Method(self.get(), getId(), args...); \ + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \ + return result; \ +} + +DEFINE_PRIMITIVE_CALL(jboolean, Boolean) +DEFINE_PRIMITIVE_CALL(jbyte, Byte) +DEFINE_PRIMITIVE_CALL(jchar, Char) +DEFINE_PRIMITIVE_CALL(jshort, Short) +DEFINE_PRIMITIVE_CALL(jint, Int) +DEFINE_PRIMITIVE_CALL(jlong, Long) +DEFINE_PRIMITIVE_CALL(jfloat, Float) +DEFINE_PRIMITIVE_CALL(jdouble, Double) +#pragma pop_macro("DEFINE_PRIMITIVE_CALL") + +template +inline local_ref JMethod::operator()(alias_ref self, Args... args) { + const auto env = internal::getEnv(); + auto result = env->CallObjectMethod(self.get(), getId(), args...); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + return adopt_local(static_cast(result)); +} + +template +inline void JStaticMethod::operator()(alias_ref cls, Args... args) { + const auto env = internal::getEnv(); + env->CallStaticVoidMethod(cls.get(), getId(), args...); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); +} + +#pragma push_macro("DEFINE_PRIMITIVE_STATIC_CALL") +#undef DEFINE_PRIMITIVE_STATIC_CALL +#define DEFINE_PRIMITIVE_STATIC_CALL(TYPE, METHOD) \ +template \ +inline TYPE JStaticMethod::operator()(alias_ref cls, Args... args) { \ + const auto env = internal::getEnv(); \ + auto result = env->CallStatic ## METHOD ## Method(cls.get(), getId(), args...); \ + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \ + return result; \ +} + +DEFINE_PRIMITIVE_STATIC_CALL(jboolean, Boolean) +DEFINE_PRIMITIVE_STATIC_CALL(jbyte, Byte) +DEFINE_PRIMITIVE_STATIC_CALL(jchar, Char) +DEFINE_PRIMITIVE_STATIC_CALL(jshort, Short) +DEFINE_PRIMITIVE_STATIC_CALL(jint, Int) +DEFINE_PRIMITIVE_STATIC_CALL(jlong, Long) +DEFINE_PRIMITIVE_STATIC_CALL(jfloat, Float) +DEFINE_PRIMITIVE_STATIC_CALL(jdouble, Double) +#pragma pop_macro("DEFINE_PRIMITIVE_STATIC_CALL") + +template +inline local_ref JStaticMethod::operator()(alias_ref cls, Args... args) { + const auto env = internal::getEnv(); + auto result = env->CallStaticObjectMethod(cls.get(), getId(), args...); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + return adopt_local(static_cast(result)); +} + + +template +inline void +JNonvirtualMethod::operator()(alias_ref self, jclass cls, Args... args) { + const auto env = internal::getEnv(); + env->CallNonvirtualVoidMethod(self.get(), cls, getId(), args...); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); +} + +#pragma push_macro("DEFINE_PRIMITIVE_NON_VIRTUAL_CALL") +#undef DEFINE_PRIMITIVE_NON_VIRTUAL_CALL +#define DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(TYPE, METHOD) \ +template \ +inline TYPE \ +JNonvirtualMethod::operator()(alias_ref self, jclass cls, Args... args) { \ + const auto env = internal::getEnv(); \ + auto result = env->CallNonvirtual ## METHOD ## Method(self.get(), cls, getId(), args...); \ + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \ + return result; \ +} + +DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jboolean, Boolean) +DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jbyte, Byte) +DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jchar, Char) +DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jshort, Short) +DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jint, Int) +DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jlong, Long) +DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jfloat, Float) +DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jdouble, Double) +#pragma pop_macro("DEFINE_PRIMITIVE_NON_VIRTUAL_CALL") + +template +inline local_ref JNonvirtualMethod::operator()( + alias_ref self, + jclass cls, + Args... args) { + const auto env = internal::getEnv(); + auto result = env->CallNonvirtualObjectMethod(self.get(), cls, getId(), args...); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + return adopt_local(static_cast(result)); +} + + +// jtype_traits //////////////////////////////////////////////////////////////////////////////////// + +/// The generic way to associate a descriptor to a type is to look it up in the +/// corresponding @ref JObjectWrapper specialization. This makes it easy to add +/// support for your user defined type. +template +struct jtype_traits { + // The jni type signature (described at + // http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/types.html). + static std::string descriptor() { + static const auto descriptor = JObjectWrapper::kJavaDescriptor != nullptr ? + std::string{JObjectWrapper::kJavaDescriptor} : + JObjectWrapper::get_instantiated_java_descriptor(); + return descriptor; + } + + // The signature used for class lookups. See + // http://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#getName(). + static std::string base_name() { + if (JObjectWrapper::kJavaDescriptor != nullptr) { + std::string base_name = JObjectWrapper::kJavaDescriptor; + return base_name.substr(1, base_name.size() - 2); + } + return JObjectWrapper::get_instantiated_java_descriptor(); + } +}; + +#pragma push_macro("DEFINE_FIELD_AND_ARRAY_TRAIT") +#undef DEFINE_FIELD_AND_ARRAY_TRAIT + +#define DEFINE_FIELD_AND_ARRAY_TRAIT(TYPE, DSC) \ +template<> \ +struct jtype_traits { \ + static std::string descriptor() { return std::string{#DSC}; } \ + static std::string base_name() { return descriptor(); } \ +}; \ +template<> \ +struct jtype_traits { \ + static std::string descriptor() { return std::string{"[" #DSC}; } \ + static std::string base_name() { return descriptor(); } \ +}; + +// There is no voidArray, handle that without the macro. +template<> +struct jtype_traits { + static std::string descriptor() { return std::string{"V"}; }; +}; + +DEFINE_FIELD_AND_ARRAY_TRAIT(jboolean, Z) +DEFINE_FIELD_AND_ARRAY_TRAIT(jbyte, B) +DEFINE_FIELD_AND_ARRAY_TRAIT(jchar, C) +DEFINE_FIELD_AND_ARRAY_TRAIT(jshort, S) +DEFINE_FIELD_AND_ARRAY_TRAIT(jint, I) +DEFINE_FIELD_AND_ARRAY_TRAIT(jlong, J) +DEFINE_FIELD_AND_ARRAY_TRAIT(jfloat, F) +DEFINE_FIELD_AND_ARRAY_TRAIT(jdouble, D) + +#pragma pop_macro("DEFINE_FIELD_AND_ARRAY_TRAIT") + + +// JField /////////////////////////////////////////////////////////////////////////////////////// + +template +inline JField::JField(jfieldID field) noexcept + : field_id_{field} +{} + +template +inline JField::operator bool() const noexcept { + return field_id_ != nullptr; +} + +template +inline jfieldID JField::getId() const noexcept { + return field_id_; +} + +#pragma push_macro("DEFINE_FIELD_PRIMITIVE_GET_SET") +#undef DEFINE_FIELD_PRIMITIVE_GET_SET +#define DEFINE_FIELD_PRIMITIVE_GET_SET(TYPE, METHOD) \ +template<> \ +inline TYPE JField::get(jobject object) const noexcept { \ + const auto env = internal::getEnv(); \ + return env->Get ## METHOD ## Field(object, field_id_); \ +} \ + \ +template<> \ +inline void JField::set(jobject object, TYPE value) noexcept { \ + const auto env = internal::getEnv(); \ + env->Set ## METHOD ## Field(object, field_id_, value); \ +} + +DEFINE_FIELD_PRIMITIVE_GET_SET(jboolean, Boolean) +DEFINE_FIELD_PRIMITIVE_GET_SET(jbyte, Byte) +DEFINE_FIELD_PRIMITIVE_GET_SET(jchar, Char) +DEFINE_FIELD_PRIMITIVE_GET_SET(jshort, Short) +DEFINE_FIELD_PRIMITIVE_GET_SET(jint, Int) +DEFINE_FIELD_PRIMITIVE_GET_SET(jlong, Long) +DEFINE_FIELD_PRIMITIVE_GET_SET(jfloat, Float) +DEFINE_FIELD_PRIMITIVE_GET_SET(jdouble, Double) +#pragma pop_macro("DEFINE_FIELD_PRIMITIVE_GET_SET") + +template +inline T JField::get(jobject object) const noexcept { + return static_cast(internal::getEnv()->GetObjectField(object, field_id_)); +} + +template +inline void JField::set(jobject object, T value) noexcept { + internal::getEnv()->SetObjectField(object, field_id_, static_cast(value)); +} + +// JStaticField ///////////////////////////////////////////////////////////////////////////////// + +template +inline JStaticField::JStaticField(jfieldID field) noexcept + : field_id_{field} +{} + +template +inline JStaticField::operator bool() const noexcept { + return field_id_ != nullptr; +} + +template +inline jfieldID JStaticField::getId() const noexcept { + return field_id_; +} + +#pragma push_macro("DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET") +#undef DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET +#define DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(TYPE, METHOD) \ +template<> \ +inline TYPE JStaticField::get(jclass jcls) const noexcept { \ + const auto env = internal::getEnv(); \ + return env->GetStatic ## METHOD ## Field(jcls, field_id_); \ +} \ + \ +template<> \ +inline void JStaticField::set(jclass jcls, TYPE value) noexcept { \ + const auto env = internal::getEnv(); \ + env->SetStatic ## METHOD ## Field(jcls, field_id_, value); \ +} + +DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jboolean, Boolean) +DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jbyte, Byte) +DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jchar, Char) +DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jshort, Short) +DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jint, Int) +DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jlong, Long) +DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jfloat, Float) +DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET(jdouble, Double) +#pragma pop_macro("DEFINE_STATIC_FIELD_PRIMITIVE_GET_SET") + +template +inline T JStaticField::get(jclass jcls) const noexcept { + const auto env = internal::getEnv(); + return static_cast(env->GetStaticObjectField(jcls, field_id_)); +} + +template +inline void JStaticField::set(jclass jcls, T value) noexcept { + internal::getEnv()->SetStaticObjectField(jcls, field_id_, value); +} + + +// jmethod_traits ////////////////////////////////////////////////////////////////////////////////// + +// TODO(T6608405) Adapt this to implement a register natives method that requires no descriptor +namespace internal { + +template +inline std::string JavaDescriptor() { + return jtype_traits::descriptor(); +} + +template +inline std::string JavaDescriptor() { + return JavaDescriptor() + JavaDescriptor(); +} + +template +inline std::string JMethodDescriptor() { + return "(" + JavaDescriptor() + ")" + JavaDescriptor(); +} + +template +inline std::string JMethodDescriptor() { + return "()" + JavaDescriptor(); +} + +} // internal + +template +inline std::string jmethod_traits::descriptor() { + return internal::JMethodDescriptor(); +} + +template +inline std::string jmethod_traits::constructor_descriptor() { + return internal::JMethodDescriptor(); +} + +}} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Meta.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Meta.h new file mode 100644 index 00000000..de1bde0d --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Meta.h @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/** @file meta.h + * + * Provides wrappers for meta data such as methods and fields. + */ + +#pragma once + +#include +#include + +#include + +#include "References.h" + +namespace facebook { +namespace jni { + +/// Wrapper of a jmethodID. Provides a common base for JMethod specializations +class JMethodBase { + public: + /// Verify that the method is valid + explicit operator bool() const noexcept; + + /// Access the wrapped id + jmethodID getId() const noexcept; + + protected: + /// Create a wrapper of a method id + explicit JMethodBase(jmethodID method_id = nullptr) noexcept; + + private: + jmethodID method_id_; +}; + + +/// Representation of a jmethodID +template +class JMethod; + +/// @cond INTERNAL +#pragma push_macro("DEFINE_PRIMITIVE_METHOD_CLASS") + +#undef DEFINE_PRIMITIVE_METHOD_CLASS + +// Defining JMethod specializations based on return value +#define DEFINE_PRIMITIVE_METHOD_CLASS(TYPE) \ +template \ +class JMethod : public JMethodBase { \ + public: \ + static_assert(std::is_void::value || IsJniPrimitive(), \ + "TYPE must be primitive or void"); \ + \ + using JMethodBase::JMethodBase; \ + JMethod() noexcept {}; \ + JMethod(const JMethod& other) noexcept = default; \ + \ + TYPE operator()(alias_ref self, Args... args); \ + \ + friend class JObjectWrapper; \ +} + +DEFINE_PRIMITIVE_METHOD_CLASS(void); +DEFINE_PRIMITIVE_METHOD_CLASS(jboolean); +DEFINE_PRIMITIVE_METHOD_CLASS(jbyte); +DEFINE_PRIMITIVE_METHOD_CLASS(jchar); +DEFINE_PRIMITIVE_METHOD_CLASS(jshort); +DEFINE_PRIMITIVE_METHOD_CLASS(jint); +DEFINE_PRIMITIVE_METHOD_CLASS(jlong); +DEFINE_PRIMITIVE_METHOD_CLASS(jfloat); +DEFINE_PRIMITIVE_METHOD_CLASS(jdouble); + +#pragma pop_macro("DEFINE_PRIMITIVE_METHOD_CLASS") +/// @endcond + + +/// JMethod specialization for references that wraps the return value in a @ref local_ref +template +class JMethod : public JMethodBase { + public: + static_assert(IsPlainJniReference(), "T* must be a JNI reference"); + + using JMethodBase::JMethodBase; + JMethod() noexcept {}; + JMethod(const JMethod& other) noexcept = default; + + /// Invoke a method and return a local reference wrapping the result + local_ref operator()(alias_ref self, Args... args); + + friend class JObjectWrapper; +}; + + +/// Convenience type representing constructors +template +using JConstructor = JMethod; + +/// Representation of a jStaticMethodID +template +class JStaticMethod; + +/// @cond INTERNAL +#pragma push_macro("DEFINE_PRIMITIVE_STATIC_METHOD_CLASS") + +#undef DEFINE_PRIMITIVE_STATIC_METHOD_CLASS + +// Defining JStaticMethod specializations based on return value +#define DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(TYPE) \ +template \ +class JStaticMethod : public JMethodBase { \ + static_assert(std::is_void::value || IsJniPrimitive(), \ + "T must be a JNI primitive or void"); \ + \ + public: \ + using JMethodBase::JMethodBase; \ + JStaticMethod() noexcept {}; \ + JStaticMethod(const JStaticMethod& other) noexcept = default; \ + \ + TYPE operator()(alias_ref cls, Args... args); \ + \ + friend class JObjectWrapper; \ +} + +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(void); +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jboolean); +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jbyte); +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jchar); +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jshort); +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jint); +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jlong); +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jfloat); +DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jdouble); + +#pragma pop_macro("DEFINE_PRIMITIVE_STATIC_METHOD_CLASS") +/// @endcond + + +/// JStaticMethod specialization for references that wraps the return value in a @ref local_ref +template +class JStaticMethod : public JMethodBase { + static_assert(IsPlainJniReference(), "T* must be a JNI reference"); + + public: + using JMethodBase::JMethodBase; + JStaticMethod() noexcept {}; + JStaticMethod(const JStaticMethod& other) noexcept = default; + + /// Invoke a method and return a local reference wrapping the result + local_ref operator()(alias_ref cls, Args... args); + + friend class JObjectWrapper; +}; + +/// Representation of a jNonvirtualMethodID +template +class JNonvirtualMethod; + +/// @cond INTERNAL +#pragma push_macro("DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS") + +#undef DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS + +// Defining JNonvirtualMethod specializations based on return value +#define DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(TYPE) \ +template \ +class JNonvirtualMethod : public JMethodBase { \ + static_assert(std::is_void::value || IsJniPrimitive(), \ + "T must be a JNI primitive or void"); \ + \ + public: \ + using JMethodBase::JMethodBase; \ + JNonvirtualMethod() noexcept {}; \ + JNonvirtualMethod(const JNonvirtualMethod& other) noexcept = default; \ + \ + TYPE operator()(alias_ref self, jclass cls, Args... args); \ + \ + friend class JObjectWrapper; \ +} + +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(void); +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jboolean); +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jbyte); +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jchar); +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jshort); +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jint); +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jlong); +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jfloat); +DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jdouble); + +#pragma pop_macro("DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS") +/// @endcond + + +/// JNonvirtualMethod specialization for references that wraps the return value in a @ref local_ref +template +class JNonvirtualMethod : public JMethodBase { + static_assert(IsPlainJniReference(), "T* must be a JNI reference"); + + public: + using JMethodBase::JMethodBase; + JNonvirtualMethod() noexcept {}; + JNonvirtualMethod(const JNonvirtualMethod& other) noexcept = default; + + /// Invoke a method and return a local reference wrapping the result + local_ref operator()(alias_ref self, jclass cls, Args... args); + + friend class JObjectWrapper; +}; + + +/** + * JField represents typed fields and simplifies their access. Note that object types return + * raw pointers which generally should promptly get a wrap_local treatment. + */ +template +class JField { + static_assert(IsJniScalar(), "T must be a JNI scalar"); + + public: + /// Wraps an existing field id + explicit JField(jfieldID field = nullptr) noexcept; + + /// Verify that the id is valid + explicit operator bool() const noexcept; + + /// Access the wrapped id + jfieldID getId() const noexcept; + + private: + jfieldID field_id_; + + /// Get field value + /// @pre object != nullptr + T get(jobject object) const noexcept; + + /// Set field value + /// @pre object != nullptr + void set(jobject object, T value) noexcept; + + friend class JObjectWrapper; +}; + + +/** + * JStaticField represents typed fields and simplifies their access. Note that object types + * return raw pointers which generally should promptly get a wrap_local treatment. + */ +template +class JStaticField { + static_assert(IsJniScalar(), "T must be a JNI scalar"); + + public: + /// Wraps an existing field id + explicit JStaticField(jfieldID field = nullptr) noexcept; + + /// Verify that the id is valid + explicit operator bool() const noexcept; + + /// Access the wrapped id + jfieldID getId() const noexcept; + + private: + jfieldID field_id_; + + /// Get field value + /// @pre object != nullptr + T get(jclass jcls) const noexcept; + + /// Set field value + /// @pre object != nullptr + void set(jclass jcls, T value) noexcept; + + friend class JObjectWrapper; + +}; + + +/// Type traits for Java types (currently providing Java type descriptors) +template +struct jtype_traits; + + +/// Type traits for Java methods (currently providing Java type descriptors) +template +struct jmethod_traits; + +/// Template magic to provide @ref jmethod_traits +template +struct jmethod_traits { + static std::string descriptor(); + static std::string constructor_descriptor(); +}; + +}} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/ReferenceAllocators-inl.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/ReferenceAllocators-inl.h new file mode 100644 index 00000000..d60c9002 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/ReferenceAllocators-inl.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include +#include +#include + +#include "Exceptions.h" +#include "References.h" + +namespace facebook { +namespace jni { + +/// @cond INTERNAL +namespace internal { + +// Statistics mostly provided for test (only updated if FBJNI_DEBUG_REFS is defined) +struct ReferenceStats { + std::atomic_uint locals_deleted, globals_deleted, weaks_deleted; + + void reset() noexcept; +}; + +extern ReferenceStats g_reference_stats; +} +/// @endcond + + +// LocalReferenceAllocator ///////////////////////////////////////////////////////////////////////// + +inline jobject LocalReferenceAllocator::newReference(jobject original) const { + internal::dbglog("Local new: %p", original); + auto ref = internal::getEnv()->NewLocalRef(original); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + return ref; +} + +inline void LocalReferenceAllocator::deleteReference(jobject reference) const noexcept { + internal::dbglog("Local release: %p", reference); + + if (reference) { + #ifdef FBJNI_DEBUG_REFS + ++internal::g_reference_stats.locals_deleted; + #endif + assert(verifyReference(reference)); + internal::getEnv()->DeleteLocalRef(reference); + } +} + +inline bool LocalReferenceAllocator::verifyReference(jobject reference) const noexcept { + if (!reference || !internal::doesGetObjectRefTypeWork()) { + return true; + } + return internal::getEnv()->GetObjectRefType(reference) == JNILocalRefType; +} + + +// GlobalReferenceAllocator //////////////////////////////////////////////////////////////////////// + +inline jobject GlobalReferenceAllocator::newReference(jobject original) const { + internal::dbglog("Global new: %p", original); + auto ref = internal::getEnv()->NewGlobalRef(original); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + return ref; +} + +inline void GlobalReferenceAllocator::deleteReference(jobject reference) const noexcept { + internal::dbglog("Global release: %p", reference); + + if (reference) { + #ifdef FBJNI_DEBUG_REFS + ++internal::g_reference_stats.globals_deleted; + #endif + assert(verifyReference(reference)); + internal::getEnv()->DeleteGlobalRef(reference); + } +} + +inline bool GlobalReferenceAllocator::verifyReference(jobject reference) const noexcept { + if (!reference || !internal::doesGetObjectRefTypeWork()) { + return true; + } + return internal::getEnv()->GetObjectRefType(reference) == JNIGlobalRefType; +} + + +// WeakGlobalReferenceAllocator //////////////////////////////////////////////////////////////////// + +inline jobject WeakGlobalReferenceAllocator::newReference(jobject original) const { + internal::dbglog("Weak global new: %p", original); + auto ref = internal::getEnv()->NewWeakGlobalRef(original); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + return ref; +} + +inline void WeakGlobalReferenceAllocator::deleteReference(jobject reference) const noexcept { + internal::dbglog("Weak Global release: %p", reference); + + if (reference) { + #ifdef FBJNI_DEBUG_REFS + ++internal::g_reference_stats.weaks_deleted; + #endif + assert(verifyReference(reference)); + internal::getEnv()->DeleteWeakGlobalRef(reference); + } +} + +inline bool WeakGlobalReferenceAllocator::verifyReference(jobject reference) const noexcept { + if (!reference || !internal::doesGetObjectRefTypeWork()) { + return true; + } + return internal::getEnv()->GetObjectRefType(reference) == JNIWeakGlobalRefType; +} + +}} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/ReferenceAllocators.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/ReferenceAllocators.h new file mode 100644 index 00000000..ee328e07 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/ReferenceAllocators.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/** + * @file ReferenceAllocators.h + * + * Reference allocators are used to create and delete various classes of JNI references (local, + * global, and weak global). + */ + +#pragma once + +#include "Common.h" + +namespace facebook { namespace jni { + +/// Allocator that handles local references +class LocalReferenceAllocator { + public: + jobject newReference(jobject original) const; + void deleteReference(jobject reference) const noexcept; + bool verifyReference(jobject reference) const noexcept; +}; + +/// Allocator that handles global references +class GlobalReferenceAllocator { + public: + jobject newReference(jobject original) const; + void deleteReference(jobject reference) const noexcept; + bool verifyReference(jobject reference) const noexcept; +}; + +/// Allocator that handles weak global references +class WeakGlobalReferenceAllocator { + public: + jobject newReference(jobject original) const; + void deleteReference(jobject reference) const noexcept; + bool verifyReference(jobject reference) const noexcept; +}; + +/// @cond INTERNAL +namespace internal { + +/** + * @return true iff env->GetObjectRefType is expected to work properly. + */ +bool doesGetObjectRefTypeWork(); + +} +/// @endcond + +}} + +#include "ReferenceAllocators-inl.h" diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/References-inl.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/References-inl.h new file mode 100644 index 00000000..bae3d5d6 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/References-inl.h @@ -0,0 +1,387 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include +#include "CoreClasses.h" + +namespace facebook { +namespace jni { + +template +inline enable_if_t(), local_ref> adopt_local(T ref) noexcept { + return local_ref{ref}; +} + +template +inline enable_if_t(), global_ref> adopt_global(T ref) noexcept { + return global_ref{ref}; +} + +template +inline enable_if_t(), weak_ref> adopt_weak_global(T ref) noexcept { + return weak_ref{ref}; +} + + +template +inline enable_if_t(), alias_ref> wrap_alias(T ref) noexcept { + return alias_ref(ref); +} + + +template +enable_if_t(), alias_ref> wrap_alias(T ref) noexcept; + + +template +inline enable_if_t(), T> getPlainJniReference(T ref) { + return ref; +} + +template +inline T getPlainJniReference(alias_ref ref) { + return ref.get(); +} + +template +inline T getPlainJniReference(const base_owned_ref& ref) { + return ref.getPlainJniReference(); +} + + +namespace internal { + +template +enable_if_t(), plain_jni_reference_t> make_ref(const T& reference) { + auto old_reference = getPlainJniReference(reference); + if (!old_reference) { + return nullptr; + } + + auto ref = Alloc{}.newReference(old_reference); + if (!ref) { + // Note that we end up here if we pass a weak ref that refers to a collected object. + // Thus, it's hard to come up with a reason why this function should be used with + // weak references. + throw std::bad_alloc{}; + } + + return static_cast>(ref); +} + +} + +template +enable_if_t(), local_ref>> +make_local(const T& ref) { + return adopt_local(internal::make_ref(ref)); +} + +template +enable_if_t(), global_ref>> +make_global(const T& ref) { + return adopt_global(internal::make_ref(ref)); +} + +template +enable_if_t(), weak_ref>> +make_weak(const T& ref) { + return adopt_weak_global(internal::make_ref(ref)); +} + +template +inline enable_if_t() && IsNonWeakReference(), bool> +operator==(const T1& a, const T2& b) { + return isSameObject(getPlainJniReference(a), getPlainJniReference(b)); +} + +template +inline enable_if_t() && IsNonWeakReference(), bool> +operator!=(const T1& a, const T2& b) { + return !(a == b); +} + + +// base_owned_ref /////////////////////////////////////////////////////////////////////// + +template +inline constexpr base_owned_ref::base_owned_ref() noexcept + : object_{nullptr} +{} + +template +inline constexpr base_owned_ref::base_owned_ref( + std::nullptr_t t) noexcept + : object_{nullptr} +{} + +template +inline base_owned_ref::base_owned_ref( + const base_owned_ref& other) + : object_{Alloc{}.newReference(other.getPlainJniReference())} +{} + +template +template +inline base_owned_ref::base_owned_ref(const base_owned_ref& other) + : object_{Alloc{}.newReference(other.getPlainJniReference())} +{} + +template +inline facebook::jni::base_owned_ref::base_owned_ref( + T reference) noexcept + : object_{reference} { + assert(Alloc{}.verifyReference(reference)); + internal::dbglog("New wrapped ref=%p this=%p", getPlainJniReference(), this); +} + +template +inline base_owned_ref::base_owned_ref( + base_owned_ref&& other) noexcept + : object_{other.object_} { + internal::dbglog("New move from ref=%p other=%p", other.getPlainJniReference(), &other); + internal::dbglog("New move to ref=%p this=%p", getPlainJniReference(), this); + // JObjectWrapper is a simple type and does not support move semantics so we explicitly + // clear other + other.object_.set(nullptr); +} + +template +template +base_owned_ref::base_owned_ref(base_owned_ref&& other) noexcept + : object_{other.object_} { + internal::dbglog("New move from ref=%p other=%p", other.getPlainJniReference(), &other); + internal::dbglog("New move to ref=%p this=%p", getPlainJniReference(), this); + // JObjectWrapper is a simple type and does not support move semantics so we explicitly + // clear other + other.object_.set(nullptr); +} + +template +inline base_owned_ref::~base_owned_ref() noexcept { + reset(); + internal::dbglog("Ref destruct ref=%p this=%p", getPlainJniReference(), this); +} + +template +inline T base_owned_ref::release() noexcept { + auto value = getPlainJniReference(); + internal::dbglog("Ref release ref=%p this=%p", value, this); + object_.set(nullptr); + return value; +} + +template +inline void base_owned_ref::reset() noexcept { + reset(nullptr); +} + +template +inline void base_owned_ref::reset(T reference) noexcept { + if (getPlainJniReference()) { + assert(Alloc{}.verifyReference(reference)); + Alloc{}.deleteReference(getPlainJniReference()); + } + object_.set(reference); +} + +template +inline T base_owned_ref::getPlainJniReference() const noexcept { + return static_cast(object_.get()); +} + + +// weak_ref /////////////////////////////////////////////////////////////////////// + +template +inline weak_ref& weak_ref::operator=( + const weak_ref& other) { + auto otherCopy = other; + swap(*this, otherCopy); + return *this; +} + +template +inline weak_ref& weak_ref::operator=( + weak_ref&& other) noexcept { + internal::dbglog("Op= move ref=%p this=%p oref=%p other=%p", + getPlainJniReference(), this, other.getPlainJniReference(), &other); + reset(other.release()); + return *this; +} + +template +local_ref weak_ref::lockLocal() { + return adopt_local(static_cast(LocalReferenceAllocator{}.newReference(getPlainJniReference()))); +} + +template +global_ref weak_ref::lockGlobal() { + return adopt_global(static_cast(GlobalReferenceAllocator{}.newReference(getPlainJniReference()))); +} + +template +inline void swap( + weak_ref& a, + weak_ref& b) noexcept { + internal::dbglog("Ref swap a.ref=%p a=%p b.ref=%p b=%p", + a.getPlainJniReference(), &a, b.getPlainJniReference(), &b); + using std::swap; + swap(a.object_, b.object_); +} + + +// basic_strong_ref //////////////////////////////////////////////////////////////////////////// + +template +inline basic_strong_ref& basic_strong_ref::operator=( + const basic_strong_ref& other) { + auto otherCopy = other; + swap(*this, otherCopy); + return *this; +} + +template +inline basic_strong_ref& basic_strong_ref::operator=( + basic_strong_ref&& other) noexcept { + internal::dbglog("Op= move ref=%p this=%p oref=%p other=%p", + getPlainJniReference(), this, other.getPlainJniReference(), &other); + reset(other.release()); + return *this; +} + +template +inline alias_ref basic_strong_ref::releaseAlias() noexcept { + return wrap_alias(release()); +} + +template +inline basic_strong_ref::operator bool() const noexcept { + return get() != nullptr; +} + +template +inline T basic_strong_ref::get() const noexcept { + return getPlainJniReference(); +} + +template +inline JObjectWrapper* basic_strong_ref::operator->() noexcept { + return &object_; +} + +template +inline const JObjectWrapper* basic_strong_ref::operator->() const noexcept { + return &object_; +} + +template +inline JObjectWrapper& basic_strong_ref::operator*() noexcept { + return object_; +} + +template +inline const JObjectWrapper& basic_strong_ref::operator*() const noexcept { + return object_; +} + +template +inline void swap( + basic_strong_ref& a, + basic_strong_ref& b) noexcept { + internal::dbglog("Ref swap a.ref=%p a=%p b.ref=%p b=%p", + a.getPlainJniReference(), &a, b.getPlainJniReference(), &b); + using std::swap; + swap(a.object_, b.object_); +} + + +// alias_ref ////////////////////////////////////////////////////////////////////////////// + +template +inline constexpr alias_ref::alias_ref() noexcept + : object_{nullptr} +{} + +template +inline constexpr alias_ref::alias_ref(std::nullptr_t) noexcept + : object_{nullptr} +{} + +template +inline alias_ref::alias_ref(const alias_ref& other) noexcept + : object_{other.object_} +{} + + +template +inline alias_ref::alias_ref(T ref) noexcept + : object_{ref} { + assert( + LocalReferenceAllocator{}.verifyReference(ref) || + GlobalReferenceAllocator{}.verifyReference(ref)); +} + +template +template +inline alias_ref::alias_ref(alias_ref other) noexcept + : object_{other.get()} +{} + +template +template +inline alias_ref::alias_ref(const basic_strong_ref& other) noexcept + : object_{other.get()} +{} + +template +inline alias_ref& alias_ref::operator=(alias_ref other) noexcept { + swap(*this, other); + return *this; +} + +template +inline alias_ref::operator bool() const noexcept { + return get() != nullptr; +} + +template +inline T facebook::jni::alias_ref::get() const noexcept { + return static_cast(object_.get()); +} + +template +inline JObjectWrapper* alias_ref::operator->() noexcept { + return &object_; +} + +template +inline const JObjectWrapper* alias_ref::operator->() const noexcept { + return &object_; +} + +template +inline JObjectWrapper& alias_ref::operator*() noexcept { + return object_; +} + +template +inline const JObjectWrapper& alias_ref::operator*() const noexcept { + return object_; +} + +template +inline void swap(alias_ref& a, alias_ref& b) noexcept { + using std::swap; + swap(a.object_, b.object_); +} + +}} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/References.cpp b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/References.cpp new file mode 100644 index 00000000..0ee4c9e8 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/References.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include "References.h" + +namespace facebook { +namespace jni { + +JniLocalScope::JniLocalScope(JNIEnv* env, jint capacity) + : env_(env) { + hasFrame_ = false; + auto pushResult = env->PushLocalFrame(capacity); + FACEBOOK_JNI_THROW_EXCEPTION_IF(pushResult < 0); + hasFrame_ = true; +} + +JniLocalScope::~JniLocalScope() { + if (hasFrame_) { + env_->PopLocalFrame(nullptr); + } +} + +namespace internal { + +// Default implementation always returns true. +// Platform-specific sources can override this. +bool doesGetObjectRefTypeWork() __attribute__ ((weak)); +bool doesGetObjectRefTypeWork() { + return true; +} + +} + +} +} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/References.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/References.h new file mode 100644 index 00000000..575f2cc6 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/References.h @@ -0,0 +1,515 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + + +/** @file References.h + * + * Functionality similar to smart pointers, but for references into the VM. Four main reference + * types are provided: local_ref, global_ref, weak_ref, and alias_ref. All are generic + * templates that and refer to objects in the jobject hierarchy. The type of the referred objects + * are specified using the template parameter. All reference types except alias_ref own their + * underlying reference, just as a std smart pointer owns the underlying raw pointer. In the context + * of std smart pointers, these references behave like unique_ptr, and have basically the same + * interface. Thus, when the reference is destructed, the plain JNI reference, i.e. the underlying + * JNI reference (like the parameters passed directly to JNI functions), is released. The alias + * references provides no ownership and is a simple wrapper for plain JNI references. + * + * All but the weak references provides access to the underlying object using dereferencing, and a + * get() method. It is also possible to convert these references to booleans to test for nullity. + * To access the underlying object of a weak reference, the reference must either be released, or + * the weak reference can be used to create a local or global reference. + * + * An owning reference is created either by moving the reference from an existing owned reference, + * by copying an existing owned reference (which creates a new underlying reference), by using the + * default constructor which initialize the reference to nullptr, or by using a helper function. The + * helper function exist in two flavors: make_XXX or adopt_XXX. + * + * Adopting takes a plain JNI reference and wrap it in an owned reference. It takes ownership of the + * plain JNI reference so be sure that no one else owns the reference when you adopt it, and make + * sure that you know what kind of reference it is. + * + * New owned references can be created from existing plain JNI references, alias references, local + * references, and global references (i.e. non-weak references) using the make_local, make_global, + * and make_weak functions. + * + * Alias references can be implicitly initialized using global, local and plain JNI references using + * the wrap_alias function. Here, we don't assume ownership of the passed-in reference, but rather + * create a separate reference that we do own, leaving the passed-in reference to its fate. + * + * Similar rules apply for assignment. An owned reference can be copy or move assigned using a smart + * reference of the same type. In the case of copy assignment a new reference is created. Alias + * reference can also be assigned new values, but since they are simple wrappers of plain JNI + * references there is no move semantics involved. + * + * Alias references are special in that they do not own the object and can therefore safely be + * converted to and from its corresponding plain JNI reference. They are useful as parameters of + * functions that do not affect the lifetime of a reference. Usage can be compared with using plain + * JNI pointers as parameters where a function does not take ownership of the underlying object. + * + * The local, global, and alias references makes it possible to access methods in the underlying + * objects. A core set of classes are implemented in CoreClasses.h, and user defined wrappers are + * supported (see example below). The wrappers also supports inheritance so a wrapper can inherit + * from another wrapper to gain access to its functionality. As an example the jstring wrapper + * inherits from the jobject wrapper, so does the jclass wrapper. That means that you can for + * example call the toString() method using the jclass wrapper, or any other class that inherits + * from the jobject wrapper. + * + * Note that the wrappers are parameterized on the static type of your (jobject) pointer, thus if + * you have a jobject that refers to a Java String you will need to cast it to jstring to get the + * jstring wrapper. This also mean that if you make a down cast that is invalid there will be no one + * stopping you and the wrappers currently does not detect this which can cause crashes. Thus, cast + * wisely. + * + * @include WrapperSample.cpp + */ + +#pragma once + +#include +#include +#include + +#include + +#include "ReferenceAllocators.h" +#include "TypeTraits.h" + +namespace facebook { +namespace jni { + +/** + * The JObjectWrapper is specialized to provide functionality for various Java classes, some + * specializations are provided, and it is easy to add your own. See example + * @sample WrapperSample.cpp + */ +template +class JObjectWrapper; + + +template +class base_owned_ref; + +template +class basic_strong_ref; + +template +class weak_ref; + +template +class alias_ref; + + +/// A smart unique reference owning a local JNI reference +template +using local_ref = basic_strong_ref; + +/// A smart unique reference owning a global JNI reference +template +using global_ref = basic_strong_ref; + + +/// Convenience function to wrap an existing local reference +template +enable_if_t(), local_ref> adopt_local(T ref) noexcept; + +/// Convenience function to wrap an existing global reference +template +enable_if_t(), global_ref> adopt_global(T ref) noexcept; + +/// Convenience function to wrap an existing weak reference +template +enable_if_t(), weak_ref> adopt_weak_global(T ref) noexcept; + + +/** + * Create a new local reference from an existing reference + * + * @param ref a plain JNI, alias, or strong reference + * @return an owned local reference (referring to null if the input does) + * @throws std::bad_alloc if the JNI reference could not be created + */ +template +enable_if_t(), local_ref>> +make_local(const T& r); + +/** + * Create a new global reference from an existing reference + * + * @param ref a plain JNI, alias, or strong reference + * @return an owned global reference (referring to null if the input does) + * @throws std::bad_alloc if the JNI reference could not be created + */ +template +enable_if_t(), global_ref>> +make_global(const T& r); + +/** + * Create a new weak global reference from an existing reference + * + * @param ref a plain JNI, alias, or strong reference + * @return an owned weak global reference (referring to null if the input does) + * @throws std::bad_alloc if the returned reference is null + */ +template +enable_if_t(), weak_ref>> +make_weak(const T& r); + + +/// Swaps two owning references of the same type +template +void swap(weak_ref& a, weak_ref& b) noexcept; + +/// Swaps two owning references of the same type +template +void swap(basic_strong_ref& a, basic_strong_ref& b) noexcept; + +/** + * Retrieve the plain reference from a plain reference. + */ +template +enable_if_t(), T> getPlainJniReference(T ref); + +/** + * Retrieve the plain reference from an alias reference. + */ +template +T getPlainJniReference(alias_ref ref); + +/** + * Retrieve the plain JNI reference from any reference owned reference. + */ +template +T getPlainJniReference(const base_owned_ref& ref); + +/** + * Compare two references to see if they refer to the same object + */ +template +enable_if_t() && IsNonWeakReference(), bool> +operator==(const T1& a, const T2& b); + +/** + * Compare two references to see if they don't refer to the same object + */ +template +enable_if_t() && IsNonWeakReference(), bool> +operator!=(const T1& a, const T2& b); + + +template +class base_owned_ref { + + static_assert(IsPlainJniReference(), "T must be a JNI reference"); + + public: + + /** + * Release the ownership and set the reference to null. Thus no deleter is invoked. + * @return Returns the reference + */ + T release() noexcept; + + /** + * Reset the reference to refer to nullptr. + */ + void reset() noexcept; + + protected: + + JObjectWrapper object_; + + /* + * Wrap an existing reference and transfers its ownership to the newly created unique reference. + * NB! Does not create a new reference + */ + explicit base_owned_ref(T reference) noexcept; + + /// Create a null reference + constexpr base_owned_ref() noexcept; + + /// Create a null reference + constexpr explicit base_owned_ref(std::nullptr_t) noexcept; + + /// Copy constructor (note creates a new reference) + base_owned_ref(const base_owned_ref& other); + template + base_owned_ref(const base_owned_ref& other); + + /// Transfers ownership of an underlying reference from one unique reference to another + base_owned_ref(base_owned_ref&& other) noexcept; + template + base_owned_ref(base_owned_ref&& other) noexcept; + + /// The delete the underlying reference if applicable + ~base_owned_ref() noexcept; + + + /// Assignment operator (note creates a new reference) + base_owned_ref& operator=(const base_owned_ref& other); + + /// Assignment by moving a reference thus not creating a new reference + base_owned_ref& operator=(base_owned_ref&& rhs) noexcept; + + + T getPlainJniReference() const noexcept; + + void reset(T reference) noexcept; + + + friend T jni::getPlainJniReference<>(const base_owned_ref& ref); + + template + friend class base_owned_ref; +}; + + +/** + * A smart reference that owns its underlying JNI reference. The class provides basic + * functionality to handle a reference but gives no access to it unless the reference is + * released, thus no longer owned. The API is stolen with pride from unique_ptr and the + * semantics should be basically the same. This class should not be used directly, instead use + * @ref weak_ref + */ +template +class weak_ref : public base_owned_ref { + + static_assert(IsPlainJniReference(), "T must be a JNI reference"); + + public: + + using PlainJniType = T; + using Allocator = WeakGlobalReferenceAllocator; + + // This inherits non-default, non-copy, non-move ctors. + using base_owned_ref::base_owned_ref; + + /// Create a null reference + constexpr weak_ref() noexcept + : base_owned_ref{} {} + + /// Create a null reference + constexpr explicit weak_ref(std::nullptr_t) noexcept + : base_owned_ref{nullptr} {} + + /// Copy constructor (note creates a new reference) + weak_ref(const weak_ref& other) + : base_owned_ref{other} {} + + /// Transfers ownership of an underlying reference from one unique reference to another + weak_ref(weak_ref&& other) noexcept + : base_owned_ref{std::move(other)} {} + + + /// Assignment operator (note creates a new reference) + weak_ref& operator=(const weak_ref& other); + + /// Assignment by moving a reference thus not creating a new reference + weak_ref& operator=(weak_ref&& rhs) noexcept; + + + // Creates an owned local reference to the referred object or to null if the object is reclaimed + local_ref lockLocal(); + + // Creates an owned global reference to the referred object or to null if the object is reclaimed + global_ref lockGlobal(); + + private: + + using base_owned_ref::getPlainJniReference; + + /* + * Wrap an existing reference and transfers its ownership to the newly created unique reference. + * NB! Does not create a new reference + */ + explicit weak_ref(T reference) noexcept + : base_owned_ref{reference} {} + + + template friend class weak_ref; + friend weak_ref(), T>> + adopt_weak_global(T ref) noexcept; + friend void swap(weak_ref& a, weak_ref& b) noexcept; +}; + + +/** + * A class representing owned strong references to Java objects. This class + * should not be used directly, instead use @ref local_ref, or @ref global_ref. + */ +template +class basic_strong_ref : public base_owned_ref { + + static_assert(IsPlainJniReference(), "T must be a JNI reference"); + + public: + + using PlainJniType = T; + using Allocator = Alloc; + + // This inherits non-default, non-copy, non-move ctors. + using base_owned_ref::base_owned_ref; + using base_owned_ref::release; + using base_owned_ref::reset; + + /// Create a null reference + constexpr basic_strong_ref() noexcept + : base_owned_ref{} {} + + /// Create a null reference + constexpr explicit basic_strong_ref(std::nullptr_t) noexcept + : base_owned_ref{nullptr} {} + + /// Copy constructor (note creates a new reference) + basic_strong_ref(const basic_strong_ref& other) + : base_owned_ref{other} {} + + /// Transfers ownership of an underlying reference from one unique reference to another + basic_strong_ref(basic_strong_ref&& other) noexcept + : base_owned_ref{std::move(other)} {} + + /// Assignment operator (note creates a new reference) + basic_strong_ref& operator=(const basic_strong_ref& other); + + /// Assignment by moving a reference thus not creating a new reference + basic_strong_ref& operator=(basic_strong_ref&& rhs) noexcept; + + + /// Release the ownership of the reference and return the wrapped reference in an alias + alias_ref releaseAlias() noexcept; + + /// Checks if the reference points to a non-null object + explicit operator bool() const noexcept; + + /// Get the plain JNI reference + T get() const noexcept; + + /// Access the functionality provided by the object wrappers + JObjectWrapper* operator->() noexcept; + + /// Access the functionality provided by the object wrappers + const JObjectWrapper* operator->() const noexcept; + + /// Provide a reference to the underlying wrapper (be sure that it is non-null before invoking) + JObjectWrapper& operator*() noexcept; + + /// Provide a const reference to the underlying wrapper (be sure that it is non-null + /// before invoking) + const JObjectWrapper& operator*() const noexcept; + + private: + + using base_owned_ref::object_; + using base_owned_ref::getPlainJniReference; + + /* + * Wrap an existing reference and transfers its ownership to the newly created unique reference. + * NB! Does not create a new reference + */ + explicit basic_strong_ref(T reference) noexcept + : base_owned_ref{reference} {} + + + friend enable_if_t(), local_ref> adopt_local(T ref) noexcept; + friend enable_if_t(), global_ref> adopt_global(T ref) noexcept; + friend void swap(basic_strong_ref& a, basic_strong_ref& b) noexcept; +}; + + +template +enable_if_t(), alias_ref> wrap_alias(T ref) noexcept; + +/// Swaps to alias referencec of the same type +template +void swap(alias_ref& a, alias_ref& b) noexcept; + +/** + * A non-owning variant of the smart references (a dumb reference). These references still provide + * access to the functionality of the @ref JObjectWrapper specializations including exception + * handling and ease of use. Use this representation when you don't want to claim ownership of the + * underlying reference (compare to using raw pointers instead of smart pointers.) For symmetry use + * @ref alias_ref instead of this class. + */ +template +class alias_ref { + + static_assert(IsPlainJniReference(), "T must be a JNI reference"); + + public: + + using PlainJniType = T; + + + /// Create a null reference + constexpr alias_ref() noexcept; + + /// Create a null reference + constexpr alias_ref(std::nullptr_t) noexcept; + + /// Copy constructor + alias_ref(const alias_ref& other) noexcept; + + /// Wrap an existing plain JNI reference + alias_ref(T ref) noexcept; + + /// Wrap an existing smart reference of any type convertible to T + template(), T>> + alias_ref(alias_ref other) noexcept; + + /// Wrap an existing alias reference of a type convertible to T + template(), T>> + alias_ref(const basic_strong_ref& other) noexcept; + + + /// Assignment operator + alias_ref& operator=(alias_ref other) noexcept; + + /// Checks if the reference points to a non-null object + explicit operator bool() const noexcept; + + /// Converts back to a plain JNI reference + T get() const noexcept; + + /// Access the functionality provided by the object wrappers + JObjectWrapper* operator->() noexcept; + + /// Access the functionality provided by the object wrappers + const JObjectWrapper* operator->() const noexcept; + + /// Provide a guaranteed non-null reference (be sure that it is non-null before invoking) + JObjectWrapper& operator*() noexcept; + + /// Provide a guaranteed non-null reference (be sure that it is non-null before invoking) + const JObjectWrapper& operator*() const noexcept; + + private: + JObjectWrapper object_; + + friend void swap(alias_ref& a, alias_ref& b) noexcept; +}; + + +/** + * RAII object to create a local JNI frame, using PushLocalFrame/PopLocalFrame. + * + * This is useful when you have a call which is initiated from C++-land, and therefore + * doesn't automatically get a local JNI frame managed for you by the JNI framework. + */ +class JniLocalScope { +public: + JniLocalScope(JNIEnv* p_env, jint capacity); + ~JniLocalScope(); + +private: + JNIEnv* env_; + bool hasFrame_; +}; + +}} + +#include "References-inl.h" diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Registration-inl.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Registration-inl.h new file mode 100644 index 00000000..29414d19 --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Registration-inl.h @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include "Exceptions.h" +#include "Hybrid.h" + +namespace facebook { +namespace jni { + +namespace detail { + +// convert to HybridClass* from jhybridobject +template +struct Convert< + T, typename std::enable_if< + std::is_base_of::type>::value>::type> { + typedef typename std::remove_pointer::type::jhybridobject jniType; + static T fromJni(jniType t) { + if (t == nullptr) { + return nullptr; + } + return facebook::jni::cthis(wrap_alias(t)); + } + // There is no automatic return conversion for objects. +}; + +// registration wrapper for legacy JNI-style functions + +template +inline NativeMethodWrapper* exceptionWrapJNIMethod(void (*)(JNIEnv*, C, Args... args)) { + struct funcWrapper { + static void call(JNIEnv* env, jobject obj, Args... args) { + // Note that if func was declared noexcept, then both gcc and clang are smart + // enough to elide the try/catch. + try { + (*func)(env, static_cast(obj), args...); + } catch (...) { + translatePendingCppExceptionToJavaException(); + } + } + }; + + // This intentionally erases the real type; JNI will do it anyway + return reinterpret_cast(&(funcWrapper::call)); +} + +template +inline NativeMethodWrapper* exceptionWrapJNIMethod(R (*)(JNIEnv*, C, Args... args)) { + struct funcWrapper { + static R call(JNIEnv* env, jobject obj, Args... args) { + try { + return (*func)(env, static_cast(obj), args...); + } catch (...) { + translatePendingCppExceptionToJavaException(); + return R{}; + } + } + }; + + // This intentionally erases the real type; JNI will do it anyway + return reinterpret_cast(&(funcWrapper::call)); +} + +// registration wrappers for functions, with autoconversion of arguments. + +template +inline NativeMethodWrapper* exceptionWrapJNIMethod(void (*)(alias_ref, Args... args)) { + struct funcWrapper { + static void call(JNIEnv*, jobject obj, + typename Convert::type>::jniType... args) { + try { + (*func)(static_cast(obj), Convert::type>::fromJni(args)...); + } catch (...) { + translatePendingCppExceptionToJavaException(); + } + } + }; + + // This intentionally erases the real type; JNI will do it anyway + return reinterpret_cast(&(funcWrapper::call)); +} + +template +inline NativeMethodWrapper* exceptionWrapJNIMethod(R (*)(alias_ref, Args... args)) { + struct funcWrapper { + typedef typename Convert::type>::jniType jniRet; + + static jniRet call(JNIEnv*, jobject obj, + typename Convert::type>::jniType... args) { + try { + return Convert::type>::toJniRet( + (*func)(static_cast(obj), Convert::type>::fromJni(args)...)); + } catch (...) { + translatePendingCppExceptionToJavaException(); + return jniRet{}; + } + } + }; + + // This intentionally erases the real type; JNI will do it anyway + return reinterpret_cast(&(funcWrapper::call)); +} + +// registration wrappers for non-static methods, with autoconvertion of arguments. + +template +inline NativeMethodWrapper* exceptionWrapJNIMethod(void (C::*method0)(Args... args)) { + struct funcWrapper { + static void call(JNIEnv* env, jobject obj, + typename Convert::type>::jniType... args) { + try { + try { + auto aref = wrap_alias(static_cast(obj)); + // This is usually a noop, but if the hybrid object is a + // base class of other classes which register JNI methods, + // this will get the right type for the registered method. + auto cobj = static_cast(facebook::jni::cthis(aref)); + (cobj->*method)(Convert::type>::fromJni(args)...); + } catch (const std::exception& ex) { + C::mapException(ex); + throw; + } + } catch (...) { + translatePendingCppExceptionToJavaException(); + } + } + }; + + // This intentionally erases the real type; JNI will do it anyway + return reinterpret_cast(&(funcWrapper::call)); +} + +template +inline NativeMethodWrapper* exceptionWrapJNIMethod(R (C::*method0)(Args... args)) { + struct funcWrapper { + typedef typename Convert::type>::jniType jniRet; + + static jniRet call(JNIEnv* env, jobject obj, + typename Convert::type>::jniType... args) { + try { + try { + auto aref = wrap_alias(static_cast(obj)); + // This is usually a noop, but if the hybrid object is a + // base class of other classes which register JNI methods, + // this will get the right type for the registered method. + auto cobj = static_cast(facebook::jni::cthis(aref)); + return Convert::type>::toJniRet( + (cobj->*method)(Convert::type>::fromJni(args)...)); + } catch (const std::exception& ex) { + C::mapException(ex); + throw; + } + } catch (...) { + translatePendingCppExceptionToJavaException(); + return jniRet{}; + } + } + }; + + // This intentionally erases the real type; JNI will do it anyway + return reinterpret_cast(&(funcWrapper::call)); +} + +template +inline std::string makeDescriptor(R (*)(JNIEnv*, C, Args... args)) { + return jmethod_traits::descriptor(); +} + +template +inline std::string makeDescriptor(R (*)(alias_ref, Args... args)) { + typedef typename Convert::type>::jniType jniRet; + return jmethod_traits::type>::jniType...)> + ::descriptor(); +} + +template +inline std::string makeDescriptor(R (C::*)(Args... args)) { + typedef typename Convert::type>::jniType jniRet; + return jmethod_traits::type>::jniType...)> + ::descriptor(); +} + +} + +}} diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Registration.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Registration.h new file mode 100644 index 00000000..e690f96b --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/Registration.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include +#include "References.h" + +namespace facebook { +namespace jni { + +namespace detail { + +// This uses the real JNI function as a non-type template parameter to +// cause a (static member) function to exist with the same signature, +// but with try/catch exception translation. +template +NativeMethodWrapper* exceptionWrapJNIMethod(void (*func0)(JNIEnv*, jobject, Args... args)); + +// Same as above, but for non-void return types. +template +NativeMethodWrapper* exceptionWrapJNIMethod(R (*func0)(JNIEnv*, jobject, Args... args)); + +// Automatically wrap object argument, and don't take env explicitly. +template +NativeMethodWrapper* exceptionWrapJNIMethod(void (*func0)(alias_ref, Args... args)); + +// Automatically wrap object argument, and don't take env explicitly, +// non-void return type. +template +NativeMethodWrapper* exceptionWrapJNIMethod(R (*func0)(alias_ref, Args... args)); + +// Extract C++ instance from object, and invoke given method on it. +template +NativeMethodWrapper* exceptionWrapJNIMethod(void (C::*method0)(Args... args)); + +// Extract C++ instance from object, and invoke given method on it, +// non-void return type +template +NativeMethodWrapper* exceptionWrapJNIMethod(R (C::*method0)(Args... args)); + +// This uses deduction to figure out the descriptor name if the types +// are primitive or have JObjectWrapper specializations. +template +std::string makeDescriptor(R (*func)(JNIEnv*, C, Args... args)); + +// This uses deduction to figure out the descriptor name if the types +// are primitive or have JObjectWrapper specializations. +template +std::string makeDescriptor(R (*func)(alias_ref, Args... args)); + +// This uses deduction to figure out the descriptor name if the types +// are primitive or have JObjectWrapper specializations. +template +std::string makeDescriptor(R (C::*method0)(Args... args)); + +} + +// We have to use macros here, because the func needs to be used +// as both a decltype expression argument and as a non-type template +// parameter, since C++ provides no way for translateException +// to deduce the type of its non-type template parameter. +// The empty string in the macros below ensures that name +// is always a string literal (because that syntax is only +// valid when name is a string literal). +#define makeNativeMethod2(name, func) \ + { name "", ::facebook::jni::detail::makeDescriptor(&func), \ + ::facebook::jni::detail::exceptionWrapJNIMethod(&func) } + +#define makeNativeMethod3(name, desc, func) \ + { name "", desc, \ + ::facebook::jni::detail::exceptionWrapJNIMethod(&func) } + +// Variadic template hacks to get macros with different numbers of +// arguments. Usage instructions are in CoreClasses.h. +#define makeNativeMethodN(a, b, c, count, ...) makeNativeMethod ## count +#define makeNativeMethod(...) makeNativeMethodN(__VA_ARGS__, 3, 2)(__VA_ARGS__) + +}} + +#include "Registration-inl.h" diff --git a/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/TypeTraits.h b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/TypeTraits.h new file mode 100644 index 00000000..b4bdd15e --- /dev/null +++ b/tests/react-test-app/android/app/src/main/jni/first-party/jni/fbjni/TypeTraits.h @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include + +namespace facebook { +namespace jni { + +/// Generic std::enable_if helper +template +using enable_if_t = typename std::enable_if::type; + +/// Generic std::is_convertible helper +template +constexpr bool IsConvertible() { + return std::is_convertible::value; +} + +template class TT, typename T> +struct is_instantiation_of : std::false_type {}; + +template class TT, typename... Ts> +struct is_instantiation_of> : std::true_type {}; + +template class TT, typename... Ts> +constexpr bool IsInstantiationOf() { + return is_instantiation_of::value; +} + +/// Metafunction to determine whether a type is a JNI reference or not +template +struct is_plain_jni_reference : + std::integral_constant::value && + std::is_base_of< + typename std::remove_pointer::type, + typename std::remove_pointer::type>::value> {}; + +/// Helper to simplify use of is_plain_jni_reference +template +constexpr bool IsPlainJniReference() { + return is_plain_jni_reference::value; +} + +/// Metafunction to determine whether a type is a primitive JNI type or not +template +struct is_jni_primitive : + std::integral_constant::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value> {}; + +/// Helper to simplify use of is_jni_primitive +template +constexpr bool IsJniPrimitive() { + return is_jni_primitive::value; +} + +/// Metafunction to determine if a type is a scalar (primitive or reference) JNI type +template +struct is_jni_scalar : + std::integral_constant::value || + is_jni_primitive::value> {}; + +/// Helper to simplify use of is_jni_scalar +template +constexpr bool IsJniScalar() { + return is_jni_scalar::value; +} + +// Metafunction to determine if a type is a JNI type +template +struct is_jni_type : + std::integral_constant::value || + std::is_void::value> {}; + +/// Helper to simplify use of is_jni_type +template +constexpr bool IsJniType() { + return is_jni_type::value; +} + +template +class weak_global_ref; + +template +class basic_strong_ref; + +template +class alias_ref; + +template +struct is_non_weak_reference : + std::integral_constant() || + IsInstantiationOf() || + IsInstantiationOf()> {}; + +template +constexpr bool IsNonWeakReference() { + return is_non_weak_reference::value; +} + +template +struct is_any_reference : + std::integral_constant() || + IsInstantiationOf() || + IsInstantiationOf() || + IsInstantiationOf()> {}; + +template +constexpr bool IsAnyReference() { + return is_any_reference::value; +} + +template +struct reference_traits { + static_assert(IsPlainJniReference(), "Need a plain JNI reference"); + using plain_jni_reference_t = T; +}; + +template