Skip to content

Commit 0329c36

Browse files
authored
Merge pull request #1049 from NativeScript/darind/instanceof-operator
Implement instanceof operator for Java interfaces
2 parents 81be198 + d91a2d1 commit 0329c36

File tree

9 files changed

+192
-17
lines changed

9 files changed

+192
-17
lines changed

Diff for: test-app/app/src/main/assets/app/mainpage.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,5 @@ require("./tests/field-access-test");
4848
require("./tests/byte-buffer-test");
4949
require("./tests/dex-interface-implementation");
5050
require("./tests/testInterfaceImplementation");
51-
require("./tests/testRuntimeImplementedAPIs");
51+
require("./tests/testRuntimeImplementedAPIs");
52+
require("./tests/testsInstanceOfOperator");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
describe("instanceof operator tests", function () {
2+
it("should return false for numeric types", function () {
3+
const actual = 5 instanceof com.tns.tests.MyInterface1;
4+
expect(actual).toBe(false);
5+
});
6+
7+
it("should return false for plain javascript classes", function () {
8+
function Polygon() {
9+
}
10+
Polygon.prototype.sayName = function() {
11+
return "polygon";
12+
};
13+
const actual = new Polygon() instanceof com.tns.tests.MyInterface1;
14+
expect(actual).toBe(false);
15+
});
16+
17+
it("should return false for ES6 classes", function () {
18+
class Polygon {
19+
sayName() {
20+
return "polygon";
21+
}
22+
}
23+
const actual = new Polygon() instanceof com.tns.tests.MyInterface1;
24+
expect(actual).toBe(false);
25+
});
26+
27+
it("should return false for null", function () {
28+
const actual = null instanceof com.tns.tests.MyInterface1;
29+
expect(actual).toBe(false);
30+
});
31+
32+
it("should return false for instanceof", function () {
33+
const actual = undefined instanceof com.tns.tests.MyInterface1;
34+
expect(actual).toBe(false);
35+
});
36+
37+
it("should return false for instances that do not implement the interface", function () {
38+
const actual = new java.io.File("/") instanceof com.tns.tests.MyInterface1;
39+
expect(actual).toBe(false);
40+
});
41+
42+
it("should return true for instances that inherit from a base type", function () {
43+
const derivedChild = new com.tns.tests.DerivedChild();
44+
const actual = derivedChild instanceof com.tns.tests.DerivedParent;
45+
expect(actual).toBe(true);
46+
});
47+
48+
it("should return true for instances whose parent class directly implements an interface", function () {
49+
const derivedChild = new com.tns.tests.DerivedChild();
50+
const actual = derivedChild instanceof com.tns.tests.MyInterface2;
51+
expect(actual).toBe(true);
52+
});
53+
54+
it("should return true for instances who have a parent class in the hierarchy implementing an interface", function () {
55+
const derivedChild = new com.tns.tests.DerivedChild();
56+
const actual = derivedChild instanceof com.tns.tests.MyInterface1;
57+
expect(actual).toBe(true);
58+
});
59+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.tns.tests;
2+
3+
public abstract class AbstractParent implements MyInterface1 {
4+
public void myMethod1() {
5+
}
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package com.tns.tests;
2+
3+
public class DerivedChild extends DerivedParent {
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.tns.tests;
2+
3+
public class DerivedParent extends AbstractParent implements MyInterface2 {
4+
public void myMethod2() {
5+
}
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.tns.tests;
2+
3+
public interface MyInterface1 {
4+
void myMethod1();
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.tns.tests;
2+
3+
public interface MyInterface2 {
4+
void myMethod2();
5+
}

Diff for: test-app/runtime/src/main/cpp/MetadataNode.cpp

+85
Original file line numberDiff line numberDiff line change
@@ -1175,6 +1175,22 @@ void MetadataNode::PackageGetterCallback(Local<Name> property, const PropertyCal
11751175
if (foundChild) {
11761176
auto childNode = MetadataNode::GetOrCreateInternal(child.treeNode);
11771177
cachedItem = childNode->CreateWrapper(isolate);
1178+
1179+
uint8_t childNodeType = s_metadataReader.GetNodeType(child.treeNode);
1180+
bool isInterface = s_metadataReader.IsNodeTypeInterface(childNodeType);
1181+
if (isInterface) {
1182+
// For all java interfaces we register the special Symbol.hasInstance property
1183+
// which is invoked by the instanceof operator (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/hasInstance).
1184+
// For example:
1185+
//
1186+
// Object.defineProperty(android.view.animation.Interpolator, Symbol.hasInstance, {
1187+
// value: function(obj) {
1188+
// return true;
1189+
// }
1190+
// });
1191+
RegisterSymbolHasInstanceCallback(isolate, child, cachedItem);
1192+
}
1193+
11781194
V8SetPrivateValue(isolate, thiz, strProperty, cachedItem);
11791195
}
11801196
}
@@ -1800,6 +1816,75 @@ void MetadataNode::SetMissingBaseMethods(Isolate* isolate, const vector<Metadata
18001816
}
18011817
}
18021818

1819+
void MetadataNode::RegisterSymbolHasInstanceCallback(Isolate* isolate, MetadataEntry entry, Local<Value> interface) {
1820+
if (interface->IsNullOrUndefined()) {
1821+
return;
1822+
}
1823+
1824+
JEnv env;
1825+
auto className = GetJniClassName(entry);
1826+
auto clazz = env.FindClass(className);
1827+
if (clazz == nullptr) {
1828+
return;
1829+
}
1830+
1831+
auto extData = External::New(isolate, clazz);
1832+
auto hasInstanceTemplate = FunctionTemplate::New(isolate, MetadataNode::SymbolHasInstanceCallback, extData);
1833+
auto hasInstanceFunc = hasInstanceTemplate->GetFunction();
1834+
PropertyDescriptor descriptor(hasInstanceFunc, false);
1835+
auto hasInstanceSymbol = Symbol::GetHasInstance(isolate);
1836+
interface->ToObject()->DefineProperty(isolate->GetCurrentContext(), hasInstanceSymbol, descriptor);
1837+
}
1838+
1839+
void MetadataNode::SymbolHasInstanceCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
1840+
auto length = info.Length();
1841+
if (length != 1) {
1842+
throw NativeScriptException(string("Symbol.hasInstance must take exactly 1 argument"));
1843+
}
1844+
1845+
auto arg = info[0];
1846+
if (arg->IsNullOrUndefined()) {
1847+
info.GetReturnValue().Set(false);
1848+
return;
1849+
}
1850+
1851+
auto clazz = reinterpret_cast<jclass>(info.Data().As<External>()->Value());
1852+
1853+
auto isolate = info.GetIsolate();
1854+
auto runtime = Runtime::GetRuntime(isolate);
1855+
auto objectManager = runtime->GetObjectManager();
1856+
auto obj = objectManager->GetJavaObjectByJsObject(arg->ToObject());
1857+
1858+
if (obj.IsNull()) {
1859+
// Couldn't find a corresponding java instance counterpart. This could happen
1860+
// if the "instanceof" operator is invoked on a pure javascript instance
1861+
info.GetReturnValue().Set(false);
1862+
return;
1863+
}
1864+
1865+
JEnv env;
1866+
auto isInstanceOf = env.IsInstanceOf(obj, clazz);
1867+
1868+
info.GetReturnValue().Set(isInstanceOf);
1869+
}
1870+
1871+
std::string MetadataNode::GetJniClassName(MetadataEntry entry) {
1872+
std::stack<string> s;
1873+
MetadataTreeNode* n = entry.treeNode;
1874+
while (n != nullptr && n->name != "") {
1875+
s.push(n->name);
1876+
n = n->parent;
1877+
}
1878+
1879+
string fullClassName;
1880+
while (!s.empty()) {
1881+
auto top = s.top();
1882+
fullClassName = (fullClassName == "") ? top : fullClassName + "/" + top;
1883+
s.pop();
1884+
}
1885+
1886+
return fullClassName;
1887+
}
18031888

18041889
string MetadataNode::TNS_PREFIX = "com/tns/gen/";
18051890
MetadataReader MetadataNode::s_metadataReader;

Diff for: test-app/runtime/src/main/cpp/MetadataNode.h

+20-16
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
#include <map>
3030

3131
namespace tns {
32-
class MetadataNode {
32+
class MetadataNode {
3333
public:
3434
static void Init(v8::Isolate* isolate);
3535

@@ -129,6 +129,10 @@ namespace tns {
129129
static bool GetExtendLocation(std::string& extendLocation, bool isTypeScriptExtend);
130130
static ExtendedClassCacheData GetCachedExtendedClassData(v8::Isolate* isolate, const std::string& proxyClassName);
131131

132+
static void RegisterSymbolHasInstanceCallback(v8::Isolate* isolate, MetadataEntry entry, v8::Local<v8::Value> interface);
133+
static void SymbolHasInstanceCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
134+
static std::string GetJniClassName(MetadataEntry entry);
135+
132136
v8::Local<v8::Function> Wrap(v8::Isolate* isolate, const v8::Local<v8::Function>& function, const std::string& name, const std::string& origin, bool isCtorFunc);
133137

134138
bool CheckClassHierarchy(JEnv& env, jclass currentClass, MetadataTreeNode* curentTreeNode, MetadataTreeNode* baseTreeNode, std::vector<MetadataTreeNode*>& skippedBaseTypes);
@@ -150,13 +154,13 @@ namespace tns {
150154

151155
struct MethodCallbackData {
152156
MethodCallbackData()
153-
:
154-
node(nullptr), parent(nullptr), isSuper(false) {
157+
:
158+
node(nullptr), parent(nullptr), isSuper(false) {
155159
}
156160

157161
MethodCallbackData(MetadataNode* _node)
158-
:
159-
node(_node), parent(nullptr), isSuper(false) {
162+
:
163+
node(_node), parent(nullptr), isSuper(false) {
160164
}
161165

162166
std::vector<MetadataEntry> candidates;
@@ -167,8 +171,8 @@ namespace tns {
167171

168172
struct ExtendedClassCallbackData {
169173
ExtendedClassCallbackData(MetadataNode* _node, const std::string& _extendedName, const v8::Local<v8::Object>& _implementationObject, std::string _fullClassName)
170-
:
171-
node(_node), extendedName(_extendedName), fullClassName(_fullClassName) {
174+
:
175+
node(_node), extendedName(_extendedName), fullClassName(_fullClassName) {
172176
implementationObject = new v8::Persistent<v8::Object>(_implementationObject->GetIsolate(), _implementationObject);
173177
}
174178

@@ -181,17 +185,17 @@ namespace tns {
181185

182186
struct TypeMetadata {
183187
TypeMetadata(const std::string& _name)
184-
:
185-
name(_name) {
188+
:
189+
name(_name) {
186190
}
187191

188192
std::string name;
189193
};
190194

191195
struct CtorCacheData {
192196
CtorCacheData(v8::Persistent<v8::FunctionTemplate>* _ft, std::vector<MethodCallbackData*> _instanceMethodCallbacks)
193-
:
194-
ft(_ft), instanceMethodCallbacks(_instanceMethodCallbacks) {
197+
:
198+
ft(_ft), instanceMethodCallbacks(_instanceMethodCallbacks) {
195199
}
196200

197201
v8::Persistent<v8::FunctionTemplate>* ft;
@@ -200,12 +204,12 @@ namespace tns {
200204

201205
struct ExtendedClassCacheData {
202206
ExtendedClassCacheData()
203-
:
204-
extendedCtorFunction(nullptr), node(nullptr) {
207+
:
208+
extendedCtorFunction(nullptr), node(nullptr) {
205209
}
206210
ExtendedClassCacheData(const v8::Local<v8::Function>& extCtorFunc, const std::string& _extendedName, MetadataNode* _node)
207-
:
208-
extendedName(_extendedName), node(_node) {
211+
:
212+
extendedName(_extendedName), node(_node) {
209213
extendedCtorFunction = new v8::Persistent<v8::Function>(extCtorFunc->GetIsolate(), extCtorFunc);
210214
}
211215
v8::Persistent<v8::Function>* extendedCtorFunction;
@@ -220,5 +224,5 @@ namespace tns {
220224

221225
std::map<std::string, MetadataNode::ExtendedClassCacheData> ExtendedCtorFuncCache;
222226
};
223-
};
227+
};
224228
}

0 commit comments

Comments
 (0)