From d91a2d15c51ccce493be115bec1d6b0e8bdd5442 Mon Sep 17 00:00:00 2001 From: Darin Dimitrov Date: Thu, 10 May 2018 15:11:37 +0300 Subject: [PATCH] Implement instanceof operator for Java interfaces --- test-app/app/src/main/assets/app/mainpage.js | 3 +- .../app/tests/testsInstanceOfOperator.js | 59 +++++++++++++ .../java/com/tns/tests/AbstractParent.java | 6 ++ .../main/java/com/tns/tests/DerivedChild.java | 4 + .../java/com/tns/tests/DerivedParent.java | 6 ++ .../main/java/com/tns/tests/MyInterface1.java | 5 ++ .../main/java/com/tns/tests/MyInterface2.java | 5 ++ .../runtime/src/main/cpp/MetadataNode.cpp | 85 +++++++++++++++++++ test-app/runtime/src/main/cpp/MetadataNode.h | 36 ++++---- 9 files changed, 192 insertions(+), 17 deletions(-) create mode 100644 test-app/app/src/main/assets/app/tests/testsInstanceOfOperator.js create mode 100644 test-app/app/src/main/java/com/tns/tests/AbstractParent.java create mode 100644 test-app/app/src/main/java/com/tns/tests/DerivedChild.java create mode 100644 test-app/app/src/main/java/com/tns/tests/DerivedParent.java create mode 100644 test-app/app/src/main/java/com/tns/tests/MyInterface1.java create mode 100644 test-app/app/src/main/java/com/tns/tests/MyInterface2.java diff --git a/test-app/app/src/main/assets/app/mainpage.js b/test-app/app/src/main/assets/app/mainpage.js index 8c8c822a3..1a81c9afd 100644 --- a/test-app/app/src/main/assets/app/mainpage.js +++ b/test-app/app/src/main/assets/app/mainpage.js @@ -48,4 +48,5 @@ require("./tests/field-access-test"); require("./tests/byte-buffer-test"); require("./tests/dex-interface-implementation"); require("./tests/testInterfaceImplementation"); -require("./tests/testRuntimeImplementedAPIs"); \ No newline at end of file +require("./tests/testRuntimeImplementedAPIs"); +require("./tests/testsInstanceOfOperator"); \ No newline at end of file diff --git a/test-app/app/src/main/assets/app/tests/testsInstanceOfOperator.js b/test-app/app/src/main/assets/app/tests/testsInstanceOfOperator.js new file mode 100644 index 000000000..1d6f270df --- /dev/null +++ b/test-app/app/src/main/assets/app/tests/testsInstanceOfOperator.js @@ -0,0 +1,59 @@ +describe("instanceof operator tests", function () { + it("should return false for numeric types", function () { + const actual = 5 instanceof com.tns.tests.MyInterface1; + expect(actual).toBe(false); + }); + + it("should return false for plain javascript classes", function () { + function Polygon() { + } + Polygon.prototype.sayName = function() { + return "polygon"; + }; + const actual = new Polygon() instanceof com.tns.tests.MyInterface1; + expect(actual).toBe(false); + }); + + it("should return false for ES6 classes", function () { + class Polygon { + sayName() { + return "polygon"; + } + } + const actual = new Polygon() instanceof com.tns.tests.MyInterface1; + expect(actual).toBe(false); + }); + + it("should return false for null", function () { + const actual = null instanceof com.tns.tests.MyInterface1; + expect(actual).toBe(false); + }); + + it("should return false for instanceof", function () { + const actual = undefined instanceof com.tns.tests.MyInterface1; + expect(actual).toBe(false); + }); + + it("should return false for instances that do not implement the interface", function () { + const actual = new java.io.File("/") instanceof com.tns.tests.MyInterface1; + expect(actual).toBe(false); + }); + + it("should return true for instances that inherit from a base type", function () { + const derivedChild = new com.tns.tests.DerivedChild(); + const actual = derivedChild instanceof com.tns.tests.DerivedParent; + expect(actual).toBe(true); + }); + + it("should return true for instances whose parent class directly implements an interface", function () { + const derivedChild = new com.tns.tests.DerivedChild(); + const actual = derivedChild instanceof com.tns.tests.MyInterface2; + expect(actual).toBe(true); + }); + + it("should return true for instances who have a parent class in the hierarchy implementing an interface", function () { + const derivedChild = new com.tns.tests.DerivedChild(); + const actual = derivedChild instanceof com.tns.tests.MyInterface1; + expect(actual).toBe(true); + }); +}); \ No newline at end of file diff --git a/test-app/app/src/main/java/com/tns/tests/AbstractParent.java b/test-app/app/src/main/java/com/tns/tests/AbstractParent.java new file mode 100644 index 000000000..76f319bce --- /dev/null +++ b/test-app/app/src/main/java/com/tns/tests/AbstractParent.java @@ -0,0 +1,6 @@ +package com.tns.tests; + +public abstract class AbstractParent implements MyInterface1 { + public void myMethod1() { + } +} diff --git a/test-app/app/src/main/java/com/tns/tests/DerivedChild.java b/test-app/app/src/main/java/com/tns/tests/DerivedChild.java new file mode 100644 index 000000000..97162e70d --- /dev/null +++ b/test-app/app/src/main/java/com/tns/tests/DerivedChild.java @@ -0,0 +1,4 @@ +package com.tns.tests; + +public class DerivedChild extends DerivedParent { +} diff --git a/test-app/app/src/main/java/com/tns/tests/DerivedParent.java b/test-app/app/src/main/java/com/tns/tests/DerivedParent.java new file mode 100644 index 000000000..b1f9bc66d --- /dev/null +++ b/test-app/app/src/main/java/com/tns/tests/DerivedParent.java @@ -0,0 +1,6 @@ +package com.tns.tests; + +public class DerivedParent extends AbstractParent implements MyInterface2 { + public void myMethod2() { + } +} diff --git a/test-app/app/src/main/java/com/tns/tests/MyInterface1.java b/test-app/app/src/main/java/com/tns/tests/MyInterface1.java new file mode 100644 index 000000000..0de25a1ac --- /dev/null +++ b/test-app/app/src/main/java/com/tns/tests/MyInterface1.java @@ -0,0 +1,5 @@ +package com.tns.tests; + +public interface MyInterface1 { + void myMethod1(); +} diff --git a/test-app/app/src/main/java/com/tns/tests/MyInterface2.java b/test-app/app/src/main/java/com/tns/tests/MyInterface2.java new file mode 100644 index 000000000..d6a41cb2f --- /dev/null +++ b/test-app/app/src/main/java/com/tns/tests/MyInterface2.java @@ -0,0 +1,5 @@ +package com.tns.tests; + +public interface MyInterface2 { + void myMethod2(); +} diff --git a/test-app/runtime/src/main/cpp/MetadataNode.cpp b/test-app/runtime/src/main/cpp/MetadataNode.cpp index 30d77dde0..eb24d9f22 100644 --- a/test-app/runtime/src/main/cpp/MetadataNode.cpp +++ b/test-app/runtime/src/main/cpp/MetadataNode.cpp @@ -1175,6 +1175,22 @@ void MetadataNode::PackageGetterCallback(Local property, const PropertyCal if (foundChild) { auto childNode = MetadataNode::GetOrCreateInternal(child.treeNode); cachedItem = childNode->CreateWrapper(isolate); + + uint8_t childNodeType = s_metadataReader.GetNodeType(child.treeNode); + bool isInterface = s_metadataReader.IsNodeTypeInterface(childNodeType); + if (isInterface) { + // For all java interfaces we register the special Symbol.hasInstance property + // which is invoked by the instanceof operator (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/hasInstance). + // For example: + // + // Object.defineProperty(android.view.animation.Interpolator, Symbol.hasInstance, { + // value: function(obj) { + // return true; + // } + // }); + RegisterSymbolHasInstanceCallback(isolate, child, cachedItem); + } + V8SetPrivateValue(isolate, thiz, strProperty, cachedItem); } } @@ -1800,6 +1816,75 @@ void MetadataNode::SetMissingBaseMethods(Isolate* isolate, const vector interface) { + if (interface->IsNullOrUndefined()) { + return; + } + + JEnv env; + auto className = GetJniClassName(entry); + auto clazz = env.FindClass(className); + if (clazz == nullptr) { + return; + } + + auto extData = External::New(isolate, clazz); + auto hasInstanceTemplate = FunctionTemplate::New(isolate, MetadataNode::SymbolHasInstanceCallback, extData); + auto hasInstanceFunc = hasInstanceTemplate->GetFunction(); + PropertyDescriptor descriptor(hasInstanceFunc, false); + auto hasInstanceSymbol = Symbol::GetHasInstance(isolate); + interface->ToObject()->DefineProperty(isolate->GetCurrentContext(), hasInstanceSymbol, descriptor); +} + +void MetadataNode::SymbolHasInstanceCallback(const v8::FunctionCallbackInfo& info) { + auto length = info.Length(); + if (length != 1) { + throw NativeScriptException(string("Symbol.hasInstance must take exactly 1 argument")); + } + + auto arg = info[0]; + if (arg->IsNullOrUndefined()) { + info.GetReturnValue().Set(false); + return; + } + + auto clazz = reinterpret_cast(info.Data().As()->Value()); + + auto isolate = info.GetIsolate(); + auto runtime = Runtime::GetRuntime(isolate); + auto objectManager = runtime->GetObjectManager(); + auto obj = objectManager->GetJavaObjectByJsObject(arg->ToObject()); + + if (obj.IsNull()) { + // Couldn't find a corresponding java instance counterpart. This could happen + // if the "instanceof" operator is invoked on a pure javascript instance + info.GetReturnValue().Set(false); + return; + } + + JEnv env; + auto isInstanceOf = env.IsInstanceOf(obj, clazz); + + info.GetReturnValue().Set(isInstanceOf); +} + +std::string MetadataNode::GetJniClassName(MetadataEntry entry) { + std::stack s; + MetadataTreeNode* n = entry.treeNode; + while (n != nullptr && n->name != "") { + s.push(n->name); + n = n->parent; + } + + string fullClassName; + while (!s.empty()) { + auto top = s.top(); + fullClassName = (fullClassName == "") ? top : fullClassName + "/" + top; + s.pop(); + } + + return fullClassName; +} string MetadataNode::TNS_PREFIX = "com/tns/gen/"; MetadataReader MetadataNode::s_metadataReader; diff --git a/test-app/runtime/src/main/cpp/MetadataNode.h b/test-app/runtime/src/main/cpp/MetadataNode.h index 0f72d46e3..35c657d12 100644 --- a/test-app/runtime/src/main/cpp/MetadataNode.h +++ b/test-app/runtime/src/main/cpp/MetadataNode.h @@ -29,7 +29,7 @@ #include namespace tns { - class MetadataNode { +class MetadataNode { public: static void Init(v8::Isolate* isolate); @@ -129,6 +129,10 @@ namespace tns { static bool GetExtendLocation(std::string& extendLocation, bool isTypeScriptExtend); static ExtendedClassCacheData GetCachedExtendedClassData(v8::Isolate* isolate, const std::string& proxyClassName); + static void RegisterSymbolHasInstanceCallback(v8::Isolate* isolate, MetadataEntry entry, v8::Local interface); + static void SymbolHasInstanceCallback(const v8::FunctionCallbackInfo& info); + static std::string GetJniClassName(MetadataEntry entry); + v8::Local Wrap(v8::Isolate* isolate, const v8::Local& function, const std::string& name, const std::string& origin, bool isCtorFunc); bool CheckClassHierarchy(JEnv& env, jclass currentClass, MetadataTreeNode* curentTreeNode, MetadataTreeNode* baseTreeNode, std::vector& skippedBaseTypes); @@ -150,13 +154,13 @@ namespace tns { struct MethodCallbackData { MethodCallbackData() - : - node(nullptr), parent(nullptr), isSuper(false) { + : + node(nullptr), parent(nullptr), isSuper(false) { } MethodCallbackData(MetadataNode* _node) - : - node(_node), parent(nullptr), isSuper(false) { + : + node(_node), parent(nullptr), isSuper(false) { } std::vector candidates; @@ -167,8 +171,8 @@ namespace tns { struct ExtendedClassCallbackData { ExtendedClassCallbackData(MetadataNode* _node, const std::string& _extendedName, const v8::Local& _implementationObject, std::string _fullClassName) - : - node(_node), extendedName(_extendedName), fullClassName(_fullClassName) { + : + node(_node), extendedName(_extendedName), fullClassName(_fullClassName) { implementationObject = new v8::Persistent(_implementationObject->GetIsolate(), _implementationObject); } @@ -181,8 +185,8 @@ namespace tns { struct TypeMetadata { TypeMetadata(const std::string& _name) - : - name(_name) { + : + name(_name) { } std::string name; @@ -190,8 +194,8 @@ namespace tns { struct CtorCacheData { CtorCacheData(v8::Persistent* _ft, std::vector _instanceMethodCallbacks) - : - ft(_ft), instanceMethodCallbacks(_instanceMethodCallbacks) { + : + ft(_ft), instanceMethodCallbacks(_instanceMethodCallbacks) { } v8::Persistent* ft; @@ -200,12 +204,12 @@ namespace tns { struct ExtendedClassCacheData { ExtendedClassCacheData() - : - extendedCtorFunction(nullptr), node(nullptr) { + : + extendedCtorFunction(nullptr), node(nullptr) { } ExtendedClassCacheData(const v8::Local& extCtorFunc, const std::string& _extendedName, MetadataNode* _node) - : - extendedName(_extendedName), node(_node) { + : + extendedName(_extendedName), node(_node) { extendedCtorFunction = new v8::Persistent(extCtorFunc->GetIsolate(), extCtorFunc); } v8::Persistent* extendedCtorFunction; @@ -220,5 +224,5 @@ namespace tns { std::map ExtendedCtorFuncCache; }; - }; +}; } \ No newline at end of file