diff --git a/src/ios/RealmJS.xcodeproj/project.pbxproj b/src/ios/RealmJS.xcodeproj/project.pbxproj index 2110ecdf..3ff11c4f 100644 --- a/src/ios/RealmJS.xcodeproj/project.pbxproj +++ b/src/ios/RealmJS.xcodeproj/project.pbxproj @@ -20,6 +20,7 @@ 029048201C0428DF00ABDED4 /* rpc.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 029048101C0428DF00ABDED4 /* rpc.hpp */; settings = {ATTRIBUTES = (Public, ); }; }; 029048371C042A3C00ABDED4 /* platform.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 029048351C042A3C00ABDED4 /* platform.hpp */; }; 0290483B1C042EE200ABDED4 /* RealmJS.h in Headers */ = {isa = PBXBuildFile; fileRef = 0290483A1C042EE200ABDED4 /* RealmJS.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 02AFE5891CA9B23400953DA3 /* jsc_list.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02AFE5871CA9B23400953DA3 /* jsc_list.cpp */; }; 02B58CCE1AE99D4D009B348C /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02B58CCD1AE99D4D009B348C /* JavaScriptCore.framework */; }; 02D8D1F71B601984006DB49D /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02B58CCD1AE99D4D009B348C /* JavaScriptCore.framework */; }; 02F59EBF1C88F17D007F774C /* index_set.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59EAF1C88F17D007F774C /* index_set.cpp */; }; @@ -38,7 +39,6 @@ 02F59EE31C88F2BB007F774C /* transact_log_handler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F59EDD1C88F2BB007F774C /* transact_log_handler.cpp */; }; F61378791C18EAC5008BFC51 /* js in Resources */ = {isa = PBXBuildFile; fileRef = F61378781C18EAAC008BFC51 /* js */; }; F63FF2C61C12469E00B3B8E0 /* js_init.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 029048011C0428DF00ABDED4 /* js_init.cpp */; }; - F63FF2C71C12469E00B3B8E0 /* js_list.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 029048031C0428DF00ABDED4 /* js_list.cpp */; }; F63FF2C81C12469E00B3B8E0 /* js_object.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 029048051C0428DF00ABDED4 /* js_object.cpp */; }; F63FF2C91C12469E00B3B8E0 /* js_realm.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 029048071C0428DF00ABDED4 /* js_realm.cpp */; }; F63FF2CA1C12469E00B3B8E0 /* js_results.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 029048091C0428DF00ABDED4 /* js_results.cpp */; }; @@ -106,7 +106,6 @@ 0270BC7B1B7D020100010E03 /* RealmJSTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = RealmJSTests.mm; path = ios/RealmJSTests.mm; sourceTree = ""; }; 029048011C0428DF00ABDED4 /* js_init.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = js_init.cpp; sourceTree = ""; }; 029048021C0428DF00ABDED4 /* js_init.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = js_init.h; sourceTree = ""; }; - 029048031C0428DF00ABDED4 /* js_list.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = js_list.cpp; sourceTree = ""; }; 029048041C0428DF00ABDED4 /* js_list.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = js_list.hpp; sourceTree = ""; }; 029048051C0428DF00ABDED4 /* js_object.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = js_object.cpp; sourceTree = ""; }; 029048061C0428DF00ABDED4 /* js_object.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = js_object.hpp; sourceTree = ""; }; @@ -124,6 +123,8 @@ 029048381C042A8F00ABDED4 /* platform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = platform.mm; sourceTree = ""; }; 0290483A1C042EE200ABDED4 /* RealmJS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RealmJS.h; sourceTree = ""; }; 02A3C7A41BC4341500B1A7BE /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; + 02AFE5871CA9B23400953DA3 /* jsc_list.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = jsc_list.cpp; path = jsc/jsc_list.cpp; sourceTree = ""; }; + 02AFE5881CA9B23400953DA3 /* jsc_list.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = jsc_list.hpp; path = jsc/jsc_list.hpp; sourceTree = ""; }; 02B58CB11AE99CEC009B348C /* RealmJS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RealmJS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 02B58CBC1AE99CEC009B348C /* RealmJSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RealmJSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 02B58CCD1AE99D4D009B348C /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; @@ -247,7 +248,6 @@ 029048021C0428DF00ABDED4 /* js_init.h */, F6CB30FC1C8EDD760070EF3F /* js_collection.cpp */, F6CB30FD1C8EDD760070EF3F /* js_collection.hpp */, - 029048031C0428DF00ABDED4 /* js_list.cpp */, 029048041C0428DF00ABDED4 /* js_list.hpp */, 029048051C0428DF00ABDED4 /* js_object.cpp */, 029048061C0428DF00ABDED4 /* js_object.hpp */, @@ -259,6 +259,8 @@ 0290480C1C0428DF00ABDED4 /* js_schema.hpp */, 0290480D1C0428DF00ABDED4 /* js_util.cpp */, 0290480E1C0428DF00ABDED4 /* js_util.hpp */, + 02AFE5871CA9B23400953DA3 /* jsc_list.cpp */, + 02AFE5881CA9B23400953DA3 /* jsc_list.hpp */, 029048351C042A3C00ABDED4 /* platform.hpp */, 0290480F1C0428DF00ABDED4 /* rpc.cpp */, 029048101C0428DF00ABDED4 /* rpc.hpp */, @@ -652,10 +654,10 @@ 02F59EE31C88F2BB007F774C /* transact_log_handler.cpp in Sources */, F63FF2E81C159C4B00B3B8E0 /* platform.mm in Sources */, 02F59EC31C88F17D007F774C /* results.cpp in Sources */, + 02AFE5891CA9B23400953DA3 /* jsc_list.cpp in Sources */, F63FF2E21C15921A00B3B8E0 /* base64.cpp in Sources */, F63FF2C61C12469E00B3B8E0 /* js_init.cpp in Sources */, 02F59ECA1C88F190007F774C /* parser.cpp in Sources */, - F63FF2C71C12469E00B3B8E0 /* js_list.cpp in Sources */, F63FF2C81C12469E00B3B8E0 /* js_object.cpp in Sources */, 02F59EE11C88F2BB007F774C /* async_query.cpp in Sources */, 02F59EC01C88F17D007F774C /* list.cpp in Sources */, diff --git a/src/js_compat.hpp b/src/js_compat.hpp new file mode 100644 index 00000000..f13e177c --- /dev/null +++ b/src/js_compat.hpp @@ -0,0 +1,68 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 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. +// +//////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "types.hpp" +#include + +namespace realm { +namespace js { + +void *GetInternal(Types::ObjectType jsObject) { + return JSObjectGetPrivate(jsObject); +} + + +std::string StringForStringType(Types::StringType jsString); +std::string StringForValueType(Types::ContextType ctx, Types::ValueType value); +std::string ValidatedStringForValueType(Types::ContextType ctx, Types::ValueType value, const char * name = nullptr); +bool ValidatedBooleanForValueType(Types::ContextType ctx, Types::ValueType value, const char * name = nullptr); + +Types::StringType StringTypeForString(const std::string &str); +Types::ValueType ValueTypeForString(Types::ContextType ctx, const std::string &str); + +bool IsValueTypeArray(Types::ContextType ctx, Types::ValueType value); +bool IsValueTypeArrayBuffer(Types::ContextType ctx, Types::ValueType value); +bool IsValueTypeDate(Types::ContextType ctx, Types::ValueType value); + +Types::ObjectType ValidatedValueTypeToObject(Types::ContextType ctx, Types::ValueType value, const char *message = NULL); +Types::ObjectType ValidatedValueTypeToDate(Types::ContextType ctx, Types::ValueType value, const char *message = NULL); +Types::ObjectType ValidatedValueTypeToFunction(Types::ContextType ctx, Types::ValueType value, const char *message = NULL); +double ValidatedValueTypeToNumber(Types::ContextType ctx, Types::ValueType value); +Types::ValueType ValidatedPropertyValue(Types::ContextType ctx, Types::ObjectType object, Types::StringType property); +Types::ValueType ValidatedPropertyAtIndex(Types::ContextType ctx, Types::ObjectType object, unsigned int index); +Types::ObjectType ValidatedObjectProperty(Types::ContextType ctx, Types::ObjectType object, Types::StringType property, const char *err = NULL); +Types::ObjectType ValidatedObjectAtIndex(Types::ContextType ctx, Types::ObjectType object, unsigned int index); +std::string ValidatedStringProperty(Types::ContextType ctx, Types::ObjectType object, Types::StringType property); +bool ValidatedBooleanProperty(Types::ContextType ctx, Types::ObjectType object, Types::StringType property, const char *err = NULL); +size_t ValidatedListLength(Types::ContextType ctx, Types::ObjectType object); +void ValidatedSetProperty(Types::ContextType ctx, Types::ObjectType object, Types::StringType propertyName, Types::ValueType value, JSPropertyAttributes attributes = 0); + +bool IsValueTypeIsObject(Types::ContextType ctx, Types::ValueType value); +bool IsValueTypeObjectOfType(Types::ContextType ctx, Types::ValueType value, Types::StringType type); +bool ObjectTypeHasProperty(Types::ContextType ctx, Types::ObjectType object, Types::StringType propName); + +template +void SetReturnNumber(Types::ContextType ctx, Types::ValueType &returnObject, T number); +void SetReturnArray(Types::ContextType ctx, size_t count, const Types::ValueType *objects, Types::ValueType &returnObject);\ +void SetReturnUndefined(Types::ContextType ctx, Types::ValueType &returnObject); + +void SetException(Types::ContextType ctx, Types::ValueType * &exceptionObject, std::exception &exception); + +}} \ No newline at end of file diff --git a/src/js_init.cpp b/src/js_init.cpp index 3c1623c4..e000f17c 100644 --- a/src/js_init.cpp +++ b/src/js_init.cpp @@ -20,7 +20,7 @@ #include "js_realm.hpp" #include "js_object.hpp" #include "js_collection.hpp" -#include "js_list.hpp" +#include "jsc_list.hpp" #include "js_results.hpp" #include "js_util.hpp" #include "js_schema.hpp" diff --git a/src/js_list.cpp b/src/js_list.cpp deleted file mode 100644 index 79eb1606..00000000 --- a/src/js_list.cpp +++ /dev/null @@ -1,300 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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 "js_list.hpp" -#include "js_collection.hpp" -#include "js_object.hpp" -#include "js_results.hpp" -#include "js_util.hpp" - -#include "object_accessor.hpp" -#include "parser.hpp" -#include "query_builder.hpp" - -#include - -using RJSAccessor = realm::NativeAccessor; -using namespace realm; - - -void RJSSetReturnUndefined(JSContextRef ctx, JSValueRef &returnObject) { - returnObject = JSValueMakeUndefined(ctx); -} - -template -void RJSSetReturnNumber(JSContextRef ctx, JSValueRef &returnObject, T number) { - returnObject = JSValueMakeNumber(ctx, number); -} - -void RJSSetReturnArray(JSContextRef ctx, size_t count, const JSValueRef *objects, JSValueRef &returnObject) { - returnObject = JSObjectMakeArray(ctx, count, objects, NULL); -} - -void RJSSetException(JSContextRef ctx, JSValueRef * &exceptionObject, std::exception &exception) { - if (exceptionObject) { - *exceptionObject = RJSMakeError(ctx, exception); - } -} - -JSValueRef ListGetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* jsException) { - try { - List *list = RJSGetInternal(object); - std::string indexStr = RJSStringForJSString(propertyName); - if (indexStr == "length") { - return JSValueMakeNumber(ctx, list->size()); - } - - return RJSObjectCreate(ctx, Object(list->get_realm(), list->get_object_schema(), list->get(RJSValidatedPositiveIndex(indexStr)))); - } - catch (std::out_of_range &exp) { - // getters for nonexistent properties in JS should always return undefined - return JSValueMakeUndefined(ctx); - } - catch (std::invalid_argument &exp) { - // for stol failure this could be another property that is handled externally, so ignore - return NULL; - } - catch (std::exception &exp) { - if (jsException) { - *jsException = RJSMakeError(ctx, exp); - } - return NULL; - } -} - -bool ListSetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* jsException) { - try { - List *list = RJSGetInternal(object); - std::string indexStr = RJSStringForJSString(propertyName); - if (indexStr == "length") { - throw std::runtime_error("The 'length' property is readonly."); - } - - list->set(ctx, value, RJSValidatedPositiveIndex(indexStr)); - return true; - } - catch (std::invalid_argument &exp) { - // for stol failure this could be another property that is handled externally, so ignore - return false; - } - catch (std::exception &exp) { - if (jsException) { - *jsException = RJSMakeError(ctx, exp); - } - return false; - } -} - -void ListPropertyNames(JSContextRef ctx, JSObjectRef object, JSPropertyNameAccumulatorRef propertyNames) { - List *list = RJSGetInternal(object); - size_t size = list->size(); - - char str[32]; - for (size_t i = 0; i < size; i++) { - sprintf(str, "%zu", i); - JSStringRef name = JSStringCreateWithUTF8CString(str); - JSPropertyNameAccumulatorAddName(propertyNames, name); - JSStringRelease(name); - } -} - -template -void ListPush(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { - try { - List *list = RJSGetInternal(thisObject); - RJSValidateArgumentCountIsAtLeast(argumentCount, 1); - for (size_t i = 0; i < argumentCount; i++) { - list->add(ctx, arguments[i]); - } - RJSSetReturnNumber(ctx, returnObject, list->size()); - } - catch (std::exception &exp) { - RJSSetException(ctx, exceptionObject, exp); - } -} - -template -void ListPop(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { - try { - List *list = RJSGetInternal(thisObject); - RJSValidateArgumentCount(argumentCount, 0); - - size_t size = list->size(); - if (size == 0) { - list->verify_in_transaction(); - RJSSetReturnUndefined(ctx, returnObject); - } - else { - size_t index = size - 1; - returnObject = RJSObjectCreate(ctx, Object(list->get_realm(), list->get_object_schema(), list->get(index))); - list->remove(index); - } - } - catch (std::exception &exception) { - RJSSetException(ctx, exceptionObject, exception); - } -} - - -template -void ListUnshift(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { - try { - List *list = RJSGetInternal(thisObject); - RJSValidateArgumentCountIsAtLeast(argumentCount, 1); - for (size_t i = 0; i < argumentCount; i++) { - list->insert(ctx, arguments[i], i); - } - RJSSetReturnNumber(ctx, returnObject, list->size()); - } - catch (std::exception &exp) { - RJSSetException(ctx, exceptionObject, exp); - } -} - -template -void ListShift(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { - try { - List *list = RJSGetInternal(thisObject); - RJSValidateArgumentCount(argumentCount, 0); - if (list->size() == 0) { - list->verify_in_transaction(); - RJSSetReturnUndefined(ctx, returnObject); - } - else { - returnObject = RJSObjectCreate(ctx, Object(list->get_realm(), list->get_object_schema(), list->get(0))); - list->remove(0); - } - } - catch (std::exception &exp) { - RJSSetException(ctx, exceptionObject, exp); - } -} - -template -void ListSplice(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { - try { - List *list = RJSGetInternal(thisObject); - size_t size = list->size(); - - RJSValidateArgumentCountIsAtLeast(argumentCount, 1); - long index = std::min(RJSValidatedValueToNumber(ctx, arguments[0]), size); - if (index < 0) { - index = std::max(size + index, 0); - } - - long remove; - if (argumentCount < 2) { - remove = size - index; - } - else { - remove = std::max(RJSValidatedValueToNumber(ctx, arguments[1]), 0); - remove = std::min(remove, size - index); - } - - std::vector removedObjects(remove); - for (size_t i = 0; i < remove; i++) { - removedObjects[i] = RJSObjectCreate(ctx, Object(list->get_realm(), list->get_object_schema(), list->get(index))); - list->remove(index); - } - for (size_t i = 2; i < argumentCount; i++) { - list->insert(ctx, arguments[i], index + i - 2); - } - RJSSetReturnArray(ctx, remove, removedObjects.data(), returnObject); - } - catch (std::exception &exp) { - RJSSetException(ctx, exceptionObject, exp); - } -} - - -template -void ListStaticResults(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { - try { - List *list = RJSGetInternal(thisObject); - RJSValidateArgumentCount(argumentCount, 0); - returnObject = RJSResultsCreate(ctx, list->get_realm(), list->get_object_schema(), std::move(list->get_query()), false); - } - catch (std::exception &exp) { - RJSSetException(ctx, exceptionObject, exp); - } -} - -template -void ListFiltered(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { - try { - List *list = RJSGetInternal(thisObject); - RJSValidateArgumentCountIsAtLeast(argumentCount, 1); - - SharedRealm sharedRealm = *RJSGetInternal(thisObject); - returnObject = RJSResultsCreateFiltered(ctx, sharedRealm, list->get_object_schema(), std::move(list->get_query()), argumentCount, arguments); - } - catch (std::exception &exp) { - RJSSetException(ctx, exceptionObject, exp); - } -} - -template -void ListSorted(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { - try { - List *list = RJSGetInternal(thisObject); - RJSValidateArgumentRange(argumentCount, 1, 2); - - SharedRealm sharedRealm = *RJSGetInternal(thisObject); - returnObject = RJSResultsCreateSorted(ctx, sharedRealm, list->get_object_schema(), std::move(list->get_query()), argumentCount, arguments); - } - catch (std::exception &exp) { - RJSSetException(ctx, exceptionObject, exp); - } -} - -#define LIST_METHOD(METHOD_NAME) \ -JSValueRef METHOD_NAME(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { \ - JSValueRef returnObject = NULL; \ - METHOD_NAME(ctx, thisObject, argumentCount, arguments, returnObject, jsException); \ - return returnObject; \ -} - -LIST_METHOD(ListPush) -LIST_METHOD(ListPop) -LIST_METHOD(ListUnshift) -LIST_METHOD(ListShift) -LIST_METHOD(ListSplice) -LIST_METHOD(ListStaticResults) -LIST_METHOD(ListFiltered) -LIST_METHOD(ListSorted) - -JSObjectRef RJSListCreate(JSContextRef ctx, List &list) { - return RJSWrapObject(ctx, RJSListClass(), new List(list)); -} - -static const JSStaticFunction RJSListFuncs[] = { - {"push", ListPush, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, - {"pop", ListPop, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, - {"shift", ListShift, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, - {"unshift", ListUnshift, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, - {"splice", ListSplice, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, - {"filtered", ListFiltered, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, - {"sorted", ListSorted, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, - {"snapshot", ListStaticResults, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, - {NULL, NULL}, -}; - -JSClassRef RJSListClass() { - static JSClassRef s_listClass = RJSCreateWrapperClass("List", ListGetProperty, ListSetProperty, RJSListFuncs, ListPropertyNames, RJSCollectionClass()); - return s_listClass; -} diff --git a/src/js_list.hpp b/src/js_list.hpp index 50fe6716..a952a623 100644 --- a/src/js_list.hpp +++ b/src/js_list.hpp @@ -18,9 +18,168 @@ #pragma once +#include "js_list.hpp" +#include "js_collection.hpp" +#include "js_object.hpp" +#include "js_results.hpp" #include "js_util.hpp" + #include "shared_realm.hpp" #include "list.hpp" +#include "object_accessor.hpp" +#include "parser.hpp" +#include "query_builder.hpp" -JSClassRef RJSListClass(); -JSObjectRef RJSListCreate(JSContextRef ctx, realm::List &list); +#include + +using RJSAccessor = realm::NativeAccessor; +using namespace realm; + +template +void ListPush(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { + try { + List *list = RJSGetInternal(thisObject); + RJSValidateArgumentCountIsAtLeast(argumentCount, 1); + for (size_t i = 0; i < argumentCount; i++) { + list->add(ctx, arguments[i]); + } + RJSSetReturnNumber(ctx, returnObject, list->size()); + } + catch (std::exception &exp) { + RJSSetException(ctx, exceptionObject, exp); + } +} + +template +void ListPop(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { + try { + List *list = RJSGetInternal(thisObject); + RJSValidateArgumentCount(argumentCount, 0); + + size_t size = list->size(); + if (size == 0) { + list->verify_in_transaction(); + RJSSetReturnUndefined(ctx, returnObject); + } + else { + size_t index = size - 1; + returnObject = RJSObjectCreate(ctx, Object(list->get_realm(), list->get_object_schema(), list->get(index))); + list->remove(index); + } + } + catch (std::exception &exception) { + RJSSetException(ctx, exceptionObject, exception); + } +} + + +template +void ListUnshift(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { + try { + List *list = RJSGetInternal(thisObject); + RJSValidateArgumentCountIsAtLeast(argumentCount, 1); + for (size_t i = 0; i < argumentCount; i++) { + list->insert(ctx, arguments[i], i); + } + RJSSetReturnNumber(ctx, returnObject, list->size()); + } + catch (std::exception &exp) { + RJSSetException(ctx, exceptionObject, exp); + } +} + +template +void ListShift(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { + try { + List *list = RJSGetInternal(thisObject); + RJSValidateArgumentCount(argumentCount, 0); + if (list->size() == 0) { + list->verify_in_transaction(); + RJSSetReturnUndefined(ctx, returnObject); + } + else { + returnObject = RJSObjectCreate(ctx, Object(list->get_realm(), list->get_object_schema(), list->get(0))); + list->remove(0); + } + } + catch (std::exception &exp) { + RJSSetException(ctx, exceptionObject, exp); + } +} + +template +void ListSplice(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { + try { + List *list = RJSGetInternal(thisObject); + size_t size = list->size(); + + RJSValidateArgumentCountIsAtLeast(argumentCount, 1); + long index = std::min(RJSValidatedValueToNumber(ctx, arguments[0]), size); + if (index < 0) { + index = std::max(size + index, 0); + } + + long remove; + if (argumentCount < 2) { + remove = size - index; + } + else { + remove = std::max(RJSValidatedValueToNumber(ctx, arguments[1]), 0); + remove = std::min(remove, size - index); + } + + std::vector removedObjects(remove); + for (size_t i = 0; i < remove; i++) { + removedObjects[i] = RJSObjectCreate(ctx, Object(list->get_realm(), list->get_object_schema(), list->get(index))); + list->remove(index); + } + for (size_t i = 2; i < argumentCount; i++) { + list->insert(ctx, arguments[i], index + i - 2); + } + RJSSetReturnArray(ctx, remove, removedObjects.data(), returnObject); + } + catch (std::exception &exp) { + RJSSetException(ctx, exceptionObject, exp); + } +} + + +template +void ListStaticResults(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { + try { + List *list = RJSGetInternal(thisObject); + RJSValidateArgumentCount(argumentCount, 0); + returnObject = RJSResultsCreate(ctx, list->get_realm(), list->get_object_schema(), std::move(list->get_query()), false); + } + catch (std::exception &exp) { + RJSSetException(ctx, exceptionObject, exp); + } +} + +template +void ListFiltered(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { + try { + List *list = RJSGetInternal(thisObject); + RJSValidateArgumentCountIsAtLeast(argumentCount, 1); + + SharedRealm sharedRealm = *RJSGetInternal(thisObject); + returnObject = RJSResultsCreateFiltered(ctx, sharedRealm, list->get_object_schema(), std::move(list->get_query()), argumentCount, arguments); + } + catch (std::exception &exp) { + RJSSetException(ctx, exceptionObject, exp); + } +} + +template +void ListSorted(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { + try { + List *list = RJSGetInternal(thisObject); + RJSValidateArgumentRange(argumentCount, 1, 2); + + SharedRealm sharedRealm = *RJSGetInternal(thisObject); + returnObject = RJSResultsCreateSorted(ctx, sharedRealm, list->get_object_schema(), std::move(list->get_query()), argumentCount, arguments); + } + catch (std::exception &exp) { + RJSSetException(ctx, exceptionObject, exp); + } +} \ No newline at end of file diff --git a/src/js_object.cpp b/src/js_object.cpp index 13654b61..718a5b1a 100644 --- a/src/js_object.cpp +++ b/src/js_object.cpp @@ -20,7 +20,7 @@ #include "js_object.hpp" #include "js_results.hpp" #include "js_schema.hpp" -#include "js_list.hpp" +#include "jsc_list.hpp" #include "js_realm.hpp" #include "object_store.hpp" @@ -93,8 +93,6 @@ JSObjectRef RJSObjectCreate(JSContextRef ctx, Object object) { return jsObject; } -extern JSObjectRef RJSDictForPropertyArray(JSContextRef ctx, const ObjectSchema &object_schema, JSObjectRef array); - namespace realm { template<> bool RJSAccessor::dict_has_value_for_key(JSContextRef ctx, JSValueRef dict, const std::string &prop_name) { diff --git a/src/js_realm.cpp b/src/js_realm.cpp index 0bca0d23..e2a2068d 100644 --- a/src/js_realm.cpp +++ b/src/js_realm.cpp @@ -19,7 +19,7 @@ #include "js_realm.hpp" #include "js_object.hpp" #include "js_results.hpp" -#include "js_list.hpp" +#include "jsc_list.hpp" #include "js_schema.hpp" #include "platform.hpp" @@ -27,6 +27,7 @@ #include "impl/realm_coordinator.hpp" #include "object_accessor.hpp" #include "binding_context.hpp" +#include "results.hpp" #include #include @@ -310,47 +311,21 @@ std::string RJSValidatedObjectTypeForValue(SharedRealm &realm, JSContextRef ctx, return RJSValidatedStringForValue(ctx, value, "objectType"); } -JSValueRef RealmObjects(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { - try { +template +void RealmObjects(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { try { RJSValidateArgumentCount(argumentCount, 1); SharedRealm sharedRealm = *RJSGetInternal(thisObject); std::string className = RJSValidatedObjectTypeForValue(sharedRealm, ctx, arguments[0]); - return RJSResultsCreate(ctx, sharedRealm, className); + returnObject = RJSResultsCreate(ctx, sharedRealm, className); } catch (std::exception &exp) { - if (jsException) { - *jsException = RJSMakeError(ctx, exp); - } - return NULL; + RJSSetException(ctx, exceptionObject, exp); } } -JSObjectRef RJSDictForPropertyArray(JSContextRef ctx, const ObjectSchema &object_schema, JSObjectRef array) { - // copy to dictionary - if (object_schema.properties.size() != RJSValidatedListLength(ctx, array)) { - throw std::runtime_error("Array must contain values for all object properties"); - } - - JSValueRef exception = NULL; - JSObjectRef dict = JSObjectMake(ctx, NULL, NULL); - for (unsigned int i = 0; i < object_schema.properties.size(); i++) { - JSStringRef nameStr = JSStringCreateWithUTF8CString(object_schema.properties[i].name.c_str()); - JSValueRef value = JSObjectGetPropertyAtIndex(ctx, array, i, &exception); - if (exception) { - throw RJSException(ctx, exception); - } - JSObjectSetProperty(ctx, dict, nameStr, value, kJSPropertyAttributeNone, &exception); - if (exception) { - throw RJSException(ctx, exception); - } - JSStringRelease(nameStr); - } - return dict; -} - -JSValueRef RealmCreateObject(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { - try { +template +void RealmCreateObject(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { try { RJSValidateArgumentRange(argumentCount, 2, 3); SharedRealm sharedRealm = *RJSGetInternal(thisObject); @@ -359,8 +334,7 @@ JSValueRef RealmCreateObject(JSContextRef ctx, JSObjectRef function, JSObjectRef auto object_schema = schema->find(className); if (object_schema == schema->end()) { - *jsException = RJSMakeError(ctx, "Object type '" + className + "' not found in schema."); - return NULL; + throw std::runtime_error("Object type '" + className + "' not found in schema."); } JSObjectRef object = RJSValidatedValueToObject(ctx, arguments[1]); @@ -370,66 +344,63 @@ JSValueRef RealmCreateObject(JSContextRef ctx, JSObjectRef function, JSObjectRef bool update = false; if (argumentCount == 3) { - update = JSValueToBoolean(ctx, arguments[2]); + update = RJSValidatedValueToBoolean(ctx, arguments[2]); } - return RJSObjectCreate(ctx, Object::create(ctx, sharedRealm, *object_schema, object, update)); + returnObject = RJSObjectCreate(ctx, Object::create(ctx, sharedRealm, *object_schema, object, update)); } catch (std::exception &exp) { - if (jsException) { - *jsException = RJSMakeError(ctx, exp); - } - return NULL; + RJSSetException(ctx, exceptionObject, exp); } } -JSValueRef RealmDelete(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { - try { +template +void RealmDelete(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { try { RJSValidateArgumentCount(argumentCount, 1); - - if (RJSIsValueArray(ctx, arguments[0]) || - JSValueIsObjectOfClass(ctx, arguments[0], RJSResultsClass()) || - JSValueIsObjectOfClass(ctx, arguments[0], RJSListClass())) - { - JSObjectRef array = RJSValidatedValueToObject(ctx, arguments[0]); - size_t length = RJSValidatedListLength(ctx, array); - for (long i = length-1; i >= 0; i--) { - JSValueRef object = RJSValidatedObjectAtIndex(ctx, array, (unsigned int)i); - RealmDelete(ctx, function, thisObject, 1, &object, jsException); - if (*jsException) { - return NULL; - } - } - return NULL; - } - - if (!JSValueIsObjectOfClass(ctx, arguments[0], RJSObjectClass())) { - throw std::runtime_error("Argument to 'delete' must be a Realm object or a collection of Realm objects."); - } - - Object *object = RJSGetInternal(RJSValidatedValueToObject(ctx, arguments[0])); - + SharedRealm realm = *RJSGetInternal(thisObject); - if (!realm->is_in_transaction()) { throw std::runtime_error("Can only delete objects within a transaction."); } - - realm::TableRef table = ObjectStore::table_for_object_type(realm->read_group(), object->get_object_schema().name); - table->move_last_over(object->row().get_index()); - - return NULL; + + JSObjectRef arg = RJSValidatedValueToObject(ctx, arguments[0]); + if (RJSValueIsObjectOfClass(ctx, arg, RJSObjectClass())) { + Object *object = RJSGetInternal(arg); + realm::TableRef table = ObjectStore::table_for_object_type(realm->read_group(), object->get_object_schema().name); + table->move_last_over(object->row().get_index()); + } + else if (RJSIsValueArray(ctx, arg)) { + size_t length = RJSValidatedListLength(ctx, arg); + for (long i = length-1; i >= 0; i--) { + JSObjectRef jsObject = RJSValidatedValueToObject(ctx, RJSValidatedObjectAtIndex(ctx, arg, (unsigned int)i)); + if (!JSValueIsObjectOfClass(ctx, jsObject, RJSObjectClass())) { + throw std::runtime_error("Argument to 'delete' must be a Realm object or a collection of Realm objects."); + } + + Object *object = RJSGetInternal(jsObject); + realm::TableRef table = ObjectStore::table_for_object_type(realm->read_group(), object->get_object_schema().name); + table->move_last_over(object->row().get_index()); + } + } + else if(RJSValueIsObjectOfClass(ctx, arg, RJSResultsClass())) { + Results *results = RJSGetInternal(arg); + results->clear(); + } + else if(RJSValueIsObjectOfClass(ctx, arg, RJSListClass())) { + List *list = RJSGetInternal(arg); + list->delete_all(); + } + else { + throw std::runtime_error("Argument to 'delete' must be a Realm object or a collection of Realm objects."); + } } catch (std::exception &exp) { - if (jsException) { - *jsException = RJSMakeError(ctx, exp); - } - return NULL; + RJSSetException(ctx, exceptionObject, exp); } } -JSValueRef RealmDeleteAll(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { - try { +template +void RealmDeleteAll(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { try { RJSValidateArgumentCount(argumentCount, 0); SharedRealm realm = *RJSGetInternal(thisObject); @@ -441,39 +412,29 @@ JSValueRef RealmDeleteAll(JSContextRef ctx, JSObjectRef function, JSObjectRef th for (auto objectSchema : *realm->config().schema) { ObjectStore::table_for_object_type(realm->read_group(), objectSchema.name)->clear(); } - return NULL; } catch (std::exception &exp) { - if (jsException) { - *jsException = RJSMakeError(ctx, exp); - } - return NULL; + RJSSetException(ctx, exceptionObject, exp); } } -JSValueRef RealmWrite(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { +template +void RealmWrite(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { + SharedRealm realm = *RJSGetInternal(thisObject); try { RJSValidateArgumentCount(argumentCount, 1); JSObjectRef object = RJSValidatedValueToFunction(ctx, arguments[0]); - SharedRealm realm = *RJSGetInternal(thisObject); realm->begin_transaction(); - JSObjectCallAsFunction(ctx, object, thisObject, 0, NULL, jsException); - if (*jsException) { - realm->cancel_transaction(); - } - else { - realm->commit_transaction(); - } + RJSCallFunction(ctx, object, thisObject, 0, NULL); + realm->commit_transaction(); } catch (std::exception &exp) { - if (jsException) { - *jsException = RJSMakeError(ctx, exp); + if (realm->is_in_transaction()) { + realm->cancel_transaction(); } - return NULL; + RJSSetException(ctx, exceptionObject, exp); } - - return NULL; } std::string RJSValidatedNotificationName(JSContextRef ctx, JSValueRef value) { @@ -484,44 +445,36 @@ std::string RJSValidatedNotificationName(JSContextRef ctx, JSValueRef value) { return name; } -JSValueRef RealmAddListener(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { - try { +template +void RealmAddListener(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { try { RJSValidateArgumentCount(argumentCount, 2); __unused std::string name = RJSValidatedNotificationName(ctx, arguments[0]); JSObjectRef callback = RJSValidatedValueToFunction(ctx, arguments[1]); SharedRealm realm = *RJSGetInternal(thisObject); static_cast(realm->m_binding_context.get())->add_notification(callback); - return NULL; } catch (std::exception &exp) { - if (jsException) { - *jsException = RJSMakeError(ctx, exp); - } - return NULL; + RJSSetException(ctx, exceptionObject, exp); } } -JSValueRef RealmRemoveListener(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { - try { +template +void RealmRemoveListener(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { try { RJSValidateArgumentCount(argumentCount, 2); __unused std::string name = RJSValidatedNotificationName(ctx, arguments[0]); JSObjectRef callback = RJSValidatedValueToFunction(ctx, arguments[1]); SharedRealm realm = *RJSGetInternal(thisObject); static_cast(realm->m_binding_context.get())->remove_notification(callback); - return NULL; } catch (std::exception &exp) { - if (jsException) { - *jsException = RJSMakeError(ctx, exp); - } - return NULL; + RJSSetException(ctx, exceptionObject, exp); } } -JSValueRef RealmRemoveAllListeners(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { - try { +template +void RealmRemoveAllListeners(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { try { RJSValidateArgumentRange(argumentCount, 0, 1); if (argumentCount) { RJSValidatedNotificationName(ctx, arguments[0]); @@ -529,30 +482,33 @@ JSValueRef RealmRemoveAllListeners(JSContextRef ctx, JSObjectRef function, JSObj SharedRealm realm = *RJSGetInternal(thisObject); static_cast(realm->m_binding_context.get())->remove_all_notifications(); - return NULL; } catch (std::exception &exp) { - if (jsException) { - *jsException = RJSMakeError(ctx, exp); - } - return NULL; + RJSSetException(ctx, exceptionObject, exp); } } -JSValueRef RealmClose(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { - try { +template +void RealmClose(ContextType ctx, ThisType thisObject, size_t argumentCount, const ArgumentsType &arguments, ReturnType &returnObject, ExceptionType &exceptionObject) { try { RJSValidateArgumentCount(argumentCount, 0); SharedRealm realm = *RJSGetInternal(thisObject); realm->close(); } catch (std::exception &exp) { - if (jsException) { - *jsException = RJSMakeError(ctx, exp); - } + RJSSetException(ctx, exceptionObject, exp); } - return NULL; } +WRAP_METHOD(RealmObjects) +WRAP_METHOD(RealmCreateObject) +WRAP_METHOD(RealmDelete) +WRAP_METHOD(RealmDeleteAll) +WRAP_METHOD(RealmWrite) +WRAP_METHOD(RealmAddListener) +WRAP_METHOD(RealmRemoveListener) +WRAP_METHOD(RealmRemoveAllListeners) +WRAP_METHOD(RealmClose) + static const JSStaticFunction RJSRealmFuncs[] = { {"objects", RealmObjects, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, {"create", RealmCreateObject, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, diff --git a/src/js_util.hpp b/src/js_util.hpp index 0b219337..c55cba4b 100644 --- a/src/js_util.hpp +++ b/src/js_util.hpp @@ -31,6 +31,13 @@ #include "property.hpp" #include "schema.hpp" +#define WRAP_METHOD(METHOD_NAME) \ +JSValueRef METHOD_NAME(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { \ + JSValueRef returnObject = NULL; \ + METHOD_NAME(ctx, thisObject, argumentCount, arguments, returnObject, jsException); \ + return returnObject; \ +} + template inline void RJSFinalize(JSObjectRef object) { @@ -149,6 +156,13 @@ static inline double RJSValidatedValueToNumber(JSContextRef ctx, JSValueRef valu return number; } +static inline double RJSValidatedValueToBoolean(JSContextRef ctx, JSValueRef value) { + if (!JSValueIsBoolean(ctx, value)) { + throw std::invalid_argument("Value is not a boolean."); + } + return JSValueToBoolean(ctx, value); +} + static inline JSValueRef RJSValidatedPropertyValue(JSContextRef ctx, JSObjectRef object, JSStringRef property) { JSValueRef exception = NULL; JSValueRef propertyValue = JSObjectGetProperty(ctx, object, property, &exception); @@ -245,3 +259,58 @@ static inline bool RJSIsValueObjectOfType(JSContextRef ctx, JSValueRef value, JS return ret; } + +static inline void RJSSetReturnUndefined(JSContextRef ctx, JSValueRef &returnObject) { + returnObject = JSValueMakeUndefined(ctx); +} + +template +static inline void RJSSetReturnNumber(JSContextRef ctx, JSValueRef &returnObject, T number) { + returnObject = JSValueMakeNumber(ctx, number); +} + +static inline void RJSSetReturnArray(JSContextRef ctx, size_t count, const JSValueRef *objects, JSValueRef &returnObject) { + returnObject = JSObjectMakeArray(ctx, count, objects, NULL); +} + +static inline void RJSSetException(JSContextRef ctx, JSValueRef * &exceptionObject, std::exception &exception) { + if (exceptionObject) { + *exceptionObject = RJSMakeError(ctx, exception); + } +} + +static JSObjectRef RJSDictForPropertyArray(JSContextRef ctx, const realm::ObjectSchema &object_schema, JSObjectRef array) { + // copy to dictionary + if (object_schema.properties.size() != RJSValidatedListLength(ctx, array)) { + throw std::runtime_error("Array must contain values for all object properties"); + } + + JSValueRef exception = NULL; + JSObjectRef dict = JSObjectMake(ctx, NULL, NULL); + for (unsigned int i = 0; i < object_schema.properties.size(); i++) { + JSStringRef nameStr = JSStringCreateWithUTF8CString(object_schema.properties[i].name.c_str()); + JSValueRef value = JSObjectGetPropertyAtIndex(ctx, array, i, &exception); + if (exception) { + throw RJSException(ctx, exception); + } + JSObjectSetProperty(ctx, dict, nameStr, value, kJSPropertyAttributeNone, &exception); + if (exception) { + throw RJSException(ctx, exception); + } + JSStringRelease(nameStr); + } + return dict; +} + +static void RJSCallFunction(JSContextRef ctx, JSObjectRef function, JSObjectRef object, size_t argumentCount, const JSValueRef *arguments) { + JSValueRef exception = NULL; + JSObjectCallAsFunction(ctx, function, object, argumentCount, arguments, &exception); + if (exception) { + throw RJSException(ctx, exception); + } +} + + +static bool RJSValueIsObjectOfClass(JSContextRef ctx, JSValueRef value, JSClassRef jsClass) { + return JSValueIsObjectOfClass(ctx, value, jsClass); +} diff --git a/src/jsc/jsc_list.cpp b/src/jsc/jsc_list.cpp new file mode 100644 index 00000000..653249c5 --- /dev/null +++ b/src/jsc/jsc_list.cpp @@ -0,0 +1,117 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 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 "jsc_list.hpp" +#include "js_list.hpp" + +#include + +using RJSAccessor = realm::NativeAccessor; +using namespace realm; + +JSValueRef ListGetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* jsException) { + try { + List *list = RJSGetInternal(object); + std::string indexStr = RJSStringForJSString(propertyName); + if (indexStr == "length") { + return JSValueMakeNumber(ctx, list->size()); + } + + return RJSObjectCreate(ctx, Object(list->get_realm(), list->get_object_schema(), list->get(RJSValidatedPositiveIndex(indexStr)))); + } + catch (std::out_of_range &exp) { + // getters for nonexistent properties in JS should always return undefined + return JSValueMakeUndefined(ctx); + } + catch (std::invalid_argument &exp) { + // for stol failure this could be another property that is handled externally, so ignore + return NULL; + } + catch (std::exception &exp) { + if (jsException) { + *jsException = RJSMakeError(ctx, exp); + } + return NULL; + } +} + +bool ListSetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* jsException) { + try { + List *list = RJSGetInternal(object); + std::string indexStr = RJSStringForJSString(propertyName); + if (indexStr == "length") { + throw std::runtime_error("The 'length' property is readonly."); + } + + list->set(ctx, value, RJSValidatedPositiveIndex(indexStr)); + return true; + } + catch (std::invalid_argument &exp) { + // for stol failure this could be another property that is handled externally, so ignore + return false; + } + catch (std::exception &exp) { + if (jsException) { + *jsException = RJSMakeError(ctx, exp); + } + return false; + } +} + +void ListPropertyNames(JSContextRef ctx, JSObjectRef object, JSPropertyNameAccumulatorRef propertyNames) { + List *list = RJSGetInternal(object); + size_t size = list->size(); + + char str[32]; + for (size_t i = 0; i < size; i++) { + sprintf(str, "%zu", i); + JSStringRef name = JSStringCreateWithUTF8CString(str); + JSPropertyNameAccumulatorAddName(propertyNames, name); + JSStringRelease(name); + } +} + +WRAP_METHOD(ListPush) +WRAP_METHOD(ListPop) +WRAP_METHOD(ListUnshift) +WRAP_METHOD(ListShift) +WRAP_METHOD(ListSplice) +WRAP_METHOD(ListStaticResults) +WRAP_METHOD(ListFiltered) +WRAP_METHOD(ListSorted) + +JSObjectRef RJSListCreate(JSContextRef ctx, List &list) { + return RJSWrapObject(ctx, RJSListClass(), new List(list)); +} + +static const JSStaticFunction RJSListFuncs[] = { + {"push", ListPush, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, + {"pop", ListPop, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, + {"shift", ListShift, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, + {"unshift", ListUnshift, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, + {"splice", ListSplice, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, + {"filtered", ListFiltered, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, + {"sorted", ListSorted, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, + {"snapshot", ListStaticResults, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, + {NULL, NULL}, +}; + +JSClassRef RJSListClass() { + static JSClassRef s_listClass = RJSCreateWrapperClass("List", ListGetProperty, ListSetProperty, RJSListFuncs, ListPropertyNames, RJSCollectionClass()); + return s_listClass; +} diff --git a/src/jsc/jsc_list.hpp b/src/jsc/jsc_list.hpp new file mode 100644 index 00000000..e5592661 --- /dev/null +++ b/src/jsc/jsc_list.hpp @@ -0,0 +1,25 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 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. +// +//////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "js_util.hpp" +#include "list.hpp" + +JSClassRef RJSListClass(); +JSObjectRef RJSListCreate(JSContextRef ctx, realm::List &list); diff --git a/src/jsc/jsc_util.hpp b/src/jsc/jsc_util.hpp new file mode 100644 index 00000000..ce9b370d --- /dev/null +++ b/src/jsc/jsc_util.hpp @@ -0,0 +1,251 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 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. +// +//////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include "property.hpp" +#include "schema.hpp" + +namespace realm { +namespace js { + +template +inline void RJSFinalize(JSObjectRef object) { + delete static_cast(JSObjectGetPrivate(object)); + JSObjectSetPrivate(object, NULL); +} + +template +inline JSObjectRef RJSWrapObject(JSContextRef ctx, JSClassRef jsClass, T object, JSValueRef prototype = NULL) { + JSObjectRef ref = JSObjectMake(ctx, jsClass, (void *)object); + if (prototype) { + JSObjectSetPrototype(ctx, ref, prototype); + } + return ref; +} + +template +inline T RJSGetInternal(JSObjectRef jsObject) { + return static_cast(JSObjectGetPrivate(jsObject)); +} + +template +JSClassRef RJSCreateWrapperClass(const char * name, JSObjectGetPropertyCallback getter = NULL, JSObjectSetPropertyCallback setter = NULL, const JSStaticFunction *funcs = NULL, + JSObjectGetPropertyNamesCallback propertyNames = NULL, JSClassRef parentClass = NULL) { + JSClassDefinition classDefinition = kJSClassDefinitionEmpty; + classDefinition.className = name; + classDefinition.finalize = RJSFinalize; + classDefinition.getProperty = getter; + classDefinition.setProperty = setter; + classDefinition.staticFunctions = funcs; + classDefinition.getPropertyNames = propertyNames; + classDefinition.parentClass = parentClass; + return JSClassCreate(&classDefinition); +} + +std::string RJSStringForJSString(JSStringRef jsString); +std::string RJSStringForValue(JSContextRef ctx, JSValueRef value); +std::string RJSValidatedStringForValue(JSContextRef ctx, JSValueRef value, const char * name = nullptr); + +JSStringRef RJSStringForString(const std::string &str); +JSValueRef RJSValueForString(JSContextRef ctx, const std::string &str); + +inline void RJSValidateArgumentCount(size_t argumentCount, size_t expected, const char *message = NULL) { + if (argumentCount != expected) { + throw std::invalid_argument(message ?: "Invalid arguments"); + } +} + +inline void RJSValidateArgumentCountIsAtLeast(size_t argumentCount, size_t expected, const char *message = NULL) { + if (argumentCount < expected) { + throw std::invalid_argument(message ?: "Invalid arguments"); + } +} + +inline void RJSValidateArgumentRange(size_t argumentCount, size_t min, size_t max, const char *message = NULL) { + if (argumentCount < min || argumentCount > max) { + throw std::invalid_argument(message ?: "Invalid arguments"); + } +} + +class RJSException : public std::runtime_error { +public: + RJSException(JSContextRef ctx, JSValueRef &ex) : std::runtime_error(RJSStringForValue(ctx, ex)), + m_jsException(ex) {} + JSValueRef exception() { return m_jsException; } + +private: + JSValueRef m_jsException; +}; + +JSValueRef RJSMakeError(JSContextRef ctx, RJSException &exp); +JSValueRef RJSMakeError(JSContextRef ctx, std::exception &exp); +JSValueRef RJSMakeError(JSContextRef ctx, const std::string &message); + +bool RJSIsValueArray(JSContextRef ctx, JSValueRef value); +bool RJSIsValueArrayBuffer(JSContextRef ctx, JSValueRef value); +bool RJSIsValueDate(JSContextRef ctx, JSValueRef value); + +static inline JSObjectRef RJSValidatedValueToObject(JSContextRef ctx, JSValueRef value, const char *message = NULL) { + JSObjectRef object = JSValueToObject(ctx, value, NULL); + if (!object) { + throw std::runtime_error(message ?: "Value is not an object."); + } + return object; +} + +static inline JSObjectRef RJSValidatedValueToDate(JSContextRef ctx, JSValueRef value, const char *message = NULL) { + JSObjectRef object = JSValueToObject(ctx, value, NULL); + if (!object || !RJSIsValueDate(ctx, object)) { + throw std::runtime_error(message ?: "Value is not a date."); + } + return object; +} + +static inline JSObjectRef RJSValidatedValueToFunction(JSContextRef ctx, JSValueRef value, const char *message = NULL) { + JSObjectRef object = JSValueToObject(ctx, value, NULL); + if (!object || !JSObjectIsFunction(ctx, object)) { + throw std::runtime_error(message ?: "Value is not a function."); + } + return object; +} + +static inline double RJSValidatedValueToNumber(JSContextRef ctx, JSValueRef value) { + if (JSValueIsNull(ctx, value)) { + throw std::invalid_argument("`null` is not a number."); + } + + JSValueRef exception = NULL; + double number = JSValueToNumber(ctx, value, &exception); + if (exception) { + throw RJSException(ctx, exception); + } + if (isnan(number)) { + throw std::invalid_argument("Value not convertible to a number."); + } + return number; +} + +static inline JSValueRef RJSValidatedPropertyValue(JSContextRef ctx, JSObjectRef object, JSStringRef property) { + JSValueRef exception = NULL; + JSValueRef propertyValue = JSObjectGetProperty(ctx, object, property, &exception); + if (exception) { + throw RJSException(ctx, exception); + } + return propertyValue; +} + +static inline JSValueRef RJSValidatedPropertyAtIndex(JSContextRef ctx, JSObjectRef object, unsigned int index) { + JSValueRef exception = NULL; + JSValueRef propertyValue = JSObjectGetPropertyAtIndex(ctx, object, index, &exception); + if (exception) { + throw RJSException(ctx, exception); + } + return propertyValue; +} + +static inline JSObjectRef RJSValidatedObjectProperty(JSContextRef ctx, JSObjectRef object, JSStringRef property, const char *err = NULL) { + JSValueRef propertyValue = RJSValidatedPropertyValue(ctx, object, property); + if (JSValueIsUndefined(ctx, propertyValue)) { + throw std::runtime_error(err ?: "Object property '" + RJSStringForJSString(property) + "' is undefined"); + } + return RJSValidatedValueToObject(ctx, propertyValue, err); +} + +static inline JSObjectRef RJSValidatedObjectAtIndex(JSContextRef ctx, JSObjectRef object, unsigned int index) { + return RJSValidatedValueToObject(ctx, RJSValidatedPropertyAtIndex(ctx, object, index)); +} + +static inline std::string RJSValidatedStringProperty(JSContextRef ctx, JSObjectRef object, JSStringRef property) { + JSValueRef exception = NULL; + JSValueRef propertyValue = JSObjectGetProperty(ctx, object, property, &exception); + if (exception) { + throw RJSException(ctx, exception); + } + return RJSValidatedStringForValue(ctx, propertyValue, RJSStringForJSString(property).c_str()); +} + +static inline size_t RJSValidatedListLength(JSContextRef ctx, JSObjectRef object) { + JSValueRef exception = NULL; + static JSStringRef lengthString = JSStringCreateWithUTF8CString("length"); + JSValueRef lengthValue = JSObjectGetProperty(ctx, object, lengthString, &exception); + if (exception) { + throw RJSException(ctx, exception); + } + if (!JSValueIsNumber(ctx, lengthValue)) { + throw std::runtime_error("Missing property 'length'"); + } + + return RJSValidatedValueToNumber(ctx, lengthValue); +} + +static inline void RJSValidatedSetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSPropertyAttributes attributes = 0) { + JSValueRef exception = NULL; + JSObjectSetProperty(ctx, object, propertyName, value, attributes, &exception); + if (exception) { + throw RJSException(ctx, exception); + } +} + +template +T stot(const std::string s) { + std::istringstream iss(s); + T value; + iss >> value; + if (iss.fail()) { + throw std::invalid_argument("Cannot convert string '" + s + "'"); + } + return value; +} + +static inline size_t RJSValidatedPositiveIndex(std::string indexStr) { + long index = stot(indexStr); + if (index < 0) { + throw std::out_of_range(std::string("Index ") + indexStr + " cannot be less than zero."); + } + return index; +} + +static inline bool RJSIsValueObjectOfType(JSContextRef ctx, JSValueRef value, JSStringRef type) { + JSObjectRef globalObject = JSContextGetGlobalObject(ctx); + + JSValueRef exception = NULL; + JSValueRef constructorValue = JSObjectGetProperty(ctx, globalObject, type, &exception); + if (exception) { + throw RJSException(ctx, exception); + } + + bool ret = JSValueIsInstanceOfConstructor(ctx, value, RJSValidatedValueToObject(ctx, constructorValue), &exception); + if (exception) { + throw RJSException(ctx, exception); + } + + return ret; +} + +}} diff --git a/src/rpc.cpp b/src/rpc.cpp index d6d70fb6..e615eacf 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -24,7 +24,7 @@ #include "js_init.h" #include "js_object.hpp" #include "js_results.hpp" -#include "js_list.hpp" +#include "jsc_list.hpp" #include "js_realm.hpp" #include "js_util.hpp"