]>
Commit | Line | Data |
---|---|---|
e17ad658 | 1 | /* Cycript - Optimizing JavaScript Compiler/Runtime |
c1d3e52e | 2 | * Copyright (C) 2009-2015 Jay Freeman (saurik) |
e17ad658 JF |
3 | */ |
4 | ||
f95d2598 | 5 | /* GNU Affero General Public License, Version 3 {{{ */ |
e17ad658 | 6 | /* |
f95d2598 JF |
7 | * This program is free software: you can redistribute it and/or modify |
8 | * it under the terms of the GNU Affero General Public License as published by | |
9 | * the Free Software Foundation, either version 3 of the License, or | |
10 | * (at your option) any later version. | |
11 | ||
12 | * This program is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
e17ad658 | 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
f95d2598 JF |
15 | * GNU Affero General Public License for more details. |
16 | ||
17 | * You should have received a copy of the GNU Affero General Public License | |
18 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
e17ad658 JF |
19 | **/ |
20 | /* }}} */ | |
21 | ||
dbf05bfd JF |
22 | #include <map> |
23 | #include <sstream> | |
24 | #include <vector> | |
25 | ||
def90846 JF |
26 | #ifdef __APPLE__ |
27 | #include <JavaVM/jni.h> | |
28 | #else | |
e17ad658 | 29 | #include <jni.h> |
def90846 | 30 | #endif |
dbf05bfd JF |
31 | |
32 | #include "cycript.hpp" | |
33 | #include "Execute.hpp" | |
34 | #include "Internal.hpp" | |
35 | #include "JavaScript.hpp" | |
36 | #include "Pooling.hpp" | |
37 | ||
38 | #define _jnicall(expr) ({ \ | |
39 | jint _value(expr); \ | |
40 | if (_value != JNI_OK) \ | |
41 | CYThrow("_jnicall(%s) == %d", #expr, _value); \ | |
42 | }) | |
43 | ||
44 | #define _envcall(jni, expr) ({ \ | |
45 | __typeof__(jni->expr) _value(jni->expr); \ | |
46 | if (jthrowable _error = jni->ExceptionOccurred()) { \ | |
47 | jni->ExceptionClear(); \ | |
48 | CYThrow("_envcall(%s): %p", #expr, _error); \ | |
49 | } \ | |
50 | _value; }) | |
51 | ||
52 | #define _envcallv(jni, expr) do { \ | |
53 | jni->expr; \ | |
54 | if (jthrowable _error = jni->ExceptionOccurred()) { \ | |
55 | jni->ExceptionClear(); \ | |
56 | CYThrow("_envcall(%s): %p", #expr, _error); \ | |
57 | } \ | |
58 | } while (false) | |
59 | ||
60 | extern "C" { | |
61 | // Android's jni.h seriously doesn't declare these :/ | |
62 | jint JNI_CreateJavaVM(JavaVM **, void **, void *); | |
63 | jint JNI_GetCreatedJavaVMs(JavaVM **, jsize, jsize *); | |
64 | } | |
65 | ||
66 | JNIEnv *GetJNI() { | |
67 | static JavaVM *jvm(NULL); | |
68 | static JNIEnv *jni(NULL); | |
69 | ||
70 | if (jni != NULL) | |
71 | return jni; | |
72 | jint version(JNI_VERSION_1_4); | |
73 | ||
74 | jsize capacity(16); | |
75 | JavaVM *jvms[capacity]; | |
76 | jsize size; | |
77 | _jnicall(JNI_GetCreatedJavaVMs(jvms, capacity, &size)); | |
78 | ||
79 | if (size != 0) { | |
80 | jvm = jvms[0]; | |
81 | _jnicall(jvm->GetEnv(reinterpret_cast<void **>(&jni), version)); | |
82 | } else { | |
83 | JavaVMInitArgs args; | |
84 | memset(&args, 0, sizeof(args)); | |
85 | args.version = version; | |
86 | _jnicall(JNI_CreateJavaVM(&jvm, reinterpret_cast<void **>(&jni), &args)); | |
87 | } | |
88 | ||
89 | return jni; | |
90 | } | |
91 | ||
92 | class CYJavaUTF8String : | |
93 | public CYUTF8String | |
94 | { | |
95 | private: | |
96 | JNIEnv *jni_; | |
97 | jstring value_; | |
98 | ||
99 | public: | |
100 | CYJavaUTF8String(JNIEnv *jni, jstring value) : | |
101 | jni_(jni), | |
102 | value_(value) | |
103 | { | |
104 | size = jni_->GetStringUTFLength(value_); | |
105 | data = jni_->GetStringUTFChars(value_, NULL); | |
106 | } | |
107 | ||
108 | ~CYJavaUTF8String() { | |
109 | if (value_ != NULL) | |
110 | jni_->ReleaseStringUTFChars(value_, data); | |
111 | } | |
112 | ||
113 | CYJavaUTF8String(const CYJavaUTF8String &) = delete; | |
114 | ||
115 | CYJavaUTF8String(CYJavaUTF8String &&rhs) : | |
116 | jni_(rhs.jni_), | |
117 | value_(rhs.value_) | |
118 | { | |
119 | rhs.value_ = NULL; | |
120 | } | |
121 | }; | |
122 | ||
123 | CYJavaUTF8String CYCastUTF8String(JNIEnv *jni, jstring value) { | |
124 | return CYJavaUTF8String(jni, value); | |
125 | } | |
126 | ||
127 | JSStringRef CYCopyJSString(JNIEnv *jni, jstring value) { | |
128 | return CYCopyJSString(CYCastUTF8String(jni, value)); | |
129 | } | |
130 | ||
131 | template <typename Value_> | |
132 | struct CYJavaGlobal { | |
133 | JNIEnv *jni_; | |
134 | Value_ value_; | |
135 | ||
136 | CYJavaGlobal() : | |
137 | jni_(NULL), | |
138 | value_(NULL) | |
139 | { | |
140 | } | |
141 | ||
142 | CYJavaGlobal(JNIEnv *jni, Value_ value) : | |
143 | jni_(jni), | |
144 | value_(static_cast<Value_>(_envcall(jni_, NewGlobalRef(value)))) | |
145 | { | |
146 | } | |
147 | ||
148 | CYJavaGlobal(const CYJavaGlobal &value) : | |
149 | CYJavaGlobal(value.jni_, value.value_) | |
150 | { | |
151 | } | |
152 | ||
153 | CYJavaGlobal(CYJavaGlobal &&value) : | |
154 | jni_(value.jni_), | |
155 | value_(value.value_) | |
156 | { | |
157 | value.value_ = NULL; | |
158 | } | |
159 | ||
160 | ~CYJavaGlobal() { | |
161 | if (value_ != NULL) | |
162 | _envcallv(jni_, DeleteGlobalRef(value_)); | |
163 | } | |
164 | ||
165 | operator bool() const { | |
166 | return value_ != NULL; | |
167 | } | |
168 | ||
169 | operator JNIEnv *() const { | |
170 | return jni_; | |
171 | } | |
172 | ||
173 | operator Value_() const { | |
174 | return value_; | |
175 | } | |
176 | }; | |
177 | ||
178 | template <typename Internal_, typename Value_> | |
179 | struct CYJavaValue : | |
180 | CYPrivate<Internal_> | |
181 | { | |
182 | CYJavaGlobal<Value_> value_; | |
183 | ||
184 | CYJavaValue(JNIEnv *jni, Value_ value) : | |
185 | value_(jni, value) | |
186 | { | |
187 | } | |
188 | ||
189 | CYJavaValue(const CYJavaValue &) = delete; | |
190 | }; | |
191 | ||
192 | #define CYJavaForEachPrimitive \ | |
193 | CYJavaForEachPrimitive_(Z, Boolean, Boolean) \ | |
194 | CYJavaForEachPrimitive_(B, Byte, Byte) \ | |
195 | CYJavaForEachPrimitive_(C, Char, Character) \ | |
196 | CYJavaForEachPrimitive_(S, Short, Short) \ | |
197 | CYJavaForEachPrimitive_(I, Int, Integer) \ | |
198 | CYJavaForEachPrimitive_(J, Long, Long) \ | |
199 | CYJavaForEachPrimitive_(F, Float, Float) \ | |
200 | CYJavaForEachPrimitive_(D, Double, Double) | |
201 | ||
202 | enum CYJavaPrimitive : char { | |
203 | CYJavaPrimitiveObject, | |
204 | CYJavaPrimitiveVoid, | |
205 | #define CYJavaForEachPrimitive_(T, Typ, Type) \ | |
206 | CYJavaPrimitive ## Type, | |
207 | CYJavaForEachPrimitive | |
208 | #undef CYJavaForEachPrimitive_ | |
209 | }; | |
210 | ||
211 | template <typename Type_> | |
212 | static _finline JSValueRef CYJavaCastJSValue(JSContextRef context, Type_ value) { | |
213 | return CYCastJSValue(context, value); | |
214 | } | |
215 | ||
216 | static _finline JSValueRef CYJavaCastJSValue(JSContextRef context, jboolean value) { | |
217 | return CYCastJSValue(context, static_cast<bool>(value)); | |
218 | } | |
219 | ||
220 | static std::map<std::string, CYJavaPrimitive> Primitives_; | |
221 | ||
222 | static CYJavaPrimitive CYJavaGetPrimitive(JNIEnv *jni, jobject type, jmethodID Class$get$$Name) { | |
223 | CYJavaUTF8String name(jni, static_cast<jstring>(_envcall(jni, CallObjectMethod(type, Class$get$$Name)))); | |
224 | auto primitive(Primitives_.find(name)); | |
225 | return primitive != Primitives_.end() ? primitive->second : CYJavaPrimitiveObject; | |
226 | } | |
227 | ||
228 | typedef std::vector<CYJavaPrimitive> CYJavaShorty; | |
229 | ||
230 | static CYJavaShorty CYJavaGetShorty(JNIEnv *jni, jobjectArray types, jmethodID Class$get$$Name) { | |
231 | size_t count(_envcall(jni, GetArrayLength(types))); | |
232 | CYJavaShorty shorty(count); | |
233 | for (size_t index(0); index != count; ++index) | |
234 | shorty[index] = CYJavaGetPrimitive(jni, _envcall(jni, GetObjectArrayElement(types, index)), Class$get$$Name); | |
235 | return shorty; | |
236 | } | |
237 | ||
238 | struct CYJavaField { | |
239 | jfieldID field_; | |
240 | CYJavaPrimitive primitive_; | |
241 | }; | |
242 | ||
243 | typedef std::map<std::string, CYJavaField> CYJavaFieldMap; | |
244 | ||
245 | struct CYJavaSignature { | |
246 | CYJavaGlobal<jobject> method; | |
247 | CYJavaPrimitive primitive; | |
248 | CYJavaShorty shorty; | |
249 | ||
250 | CYJavaSignature(JNIEnv *jni, jobject method, CYJavaPrimitive primitive, const CYJavaShorty &shorty) : | |
251 | method(jni, method), | |
252 | primitive(primitive), | |
253 | shorty(shorty) | |
254 | { | |
255 | } | |
256 | ||
257 | CYJavaSignature(unsigned count) : | |
258 | shorty(count) | |
259 | { | |
260 | } | |
261 | ||
262 | bool operator <(const CYJavaSignature &rhs) const { | |
263 | return shorty.size() < rhs.shorty.size(); | |
264 | } | |
265 | }; | |
266 | ||
267 | typedef std::multiset<CYJavaSignature> CYJavaOverload; | |
268 | ||
269 | struct CYJavaMethod : | |
270 | CYPrivate<CYJavaMethod> | |
271 | { | |
272 | JNIEnv *jni_; | |
273 | CYJavaOverload overload_; | |
274 | ||
275 | // XXX: figure out move constructors on Apple's crappy toolchain | |
276 | CYJavaMethod(JNIEnv *jni, const CYJavaOverload &overload) : | |
277 | jni_(jni), | |
278 | overload_(overload) | |
279 | { | |
280 | } | |
281 | }; | |
282 | ||
283 | struct CYJavaClass : | |
284 | CYJavaValue<CYJavaClass, jclass> | |
285 | { | |
286 | CYJavaFieldMap static_; | |
287 | CYJavaFieldMap instance_; | |
288 | CYJavaOverload overload_; | |
289 | ||
290 | CYJavaClass(JNIEnv *jni, jclass value) : | |
291 | CYJavaValue(jni, value) | |
292 | { | |
293 | } | |
294 | }; | |
295 | ||
296 | struct CYJavaObject : | |
297 | CYJavaValue<CYJavaObject, jobject> | |
298 | { | |
299 | CYJavaObject(JNIEnv *jni, jobject value) : | |
300 | CYJavaValue(jni, value) | |
301 | { | |
302 | } | |
303 | ||
304 | JSValueRef GetPrototype(JSContextRef context) const; | |
305 | }; | |
306 | ||
307 | struct CYJavaPackage : | |
308 | CYPrivate<CYJavaPackage> | |
309 | { | |
310 | typedef std::vector<std::string> Path; | |
311 | Path package_; | |
312 | ||
313 | _finline CYJavaPackage(const Path &package) : | |
314 | package_(package) | |
315 | { | |
316 | } | |
317 | }; | |
318 | ||
319 | static JSObjectRef CYGetJavaClass(JSContextRef context, JNIEnv *jni, jclass _class); | |
320 | ||
321 | JSValueRef CYJavaObject::GetPrototype(JSContextRef context) const { | |
322 | JNIEnv *jni(value_); | |
323 | return CYGetProperty(context, CYGetJavaClass(context, jni, _envcall(jni, GetObjectClass(value_))), prototype_s); | |
324 | } | |
325 | ||
326 | static JSValueRef CYCastJSValue(JSContextRef context, JNIEnv *jni, jobject value) { | |
327 | if (value == NULL) | |
328 | return CYJSNull(context); | |
329 | ||
330 | return CYJavaObject::Make(context, jni, value); | |
331 | } | |
332 | ||
333 | static jstring CYCastJavaString(JNIEnv *jni, CYUTF16String value) { | |
334 | return _envcall(jni, NewString(value.data, value.size)); | |
335 | } | |
336 | ||
337 | static jstring CYCastJavaString(JNIEnv *jni, JSStringRef value) { | |
338 | return CYCastJavaString(jni, CYCastUTF16String(value)); | |
339 | } | |
340 | ||
341 | #define CYCastJava$(T, Type, jtype, Cast) \ | |
342 | _disused static jobject CYCastJava ## Type(JNIEnv *jni, JSContextRef context, JSValueRef value) { \ | |
343 | jclass Type$(_envcall(jni, FindClass("java/lang/" #Type))); \ | |
344 | jmethodID Type$init$(_envcall(jni, GetMethodID(Type$, "<init>", "(" #T ")V"))); \ | |
345 | return _envcall(jni, NewObject(Type$, Type$init$, static_cast<jtype>(Cast(context, value)))); \ | |
346 | } | |
347 | ||
348 | CYCastJava$(Z, Boolean, jboolean, CYCastBool) | |
349 | CYCastJava$(B, Byte, jbyte, CYCastDouble) | |
350 | CYCastJava$(C, Character, jchar, CYCastDouble) | |
351 | CYCastJava$(S, Short, jshort, CYCastDouble) | |
352 | CYCastJava$(I, Integer, jint, CYCastDouble) | |
353 | CYCastJava$(J, Long, jlong, CYCastDouble) | |
354 | CYCastJava$(F, Float, jfloat, CYCastDouble) | |
355 | CYCastJava$(D, Double, jdouble, CYCastDouble) | |
356 | ||
357 | static jobject CYCastJavaObject(JNIEnv *jni, JSContextRef context, JSObjectRef value) { | |
358 | JSObjectRef object(CYCastJSObject(context, value)); | |
359 | if (JSValueIsObjectOfClass(context, value, CYJavaObject::Class_)) { | |
360 | CYJavaObject *internal(reinterpret_cast<CYJavaObject *>(JSObjectGetPrivate(object))); | |
361 | return internal->value_; | |
362 | } | |
363 | ||
364 | _assert(false); | |
365 | } | |
366 | ||
367 | static jobject CYCastJavaObject(JNIEnv *jni, JSContextRef context, JSValueRef value) { | |
368 | switch (JSValueGetType(context, value)) { | |
369 | case kJSTypeNull: | |
370 | return NULL; | |
371 | case kJSTypeBoolean: | |
372 | return CYCastJavaBoolean(jni, context, value); | |
373 | case kJSTypeNumber: | |
374 | return CYCastJavaDouble(jni, context, value); | |
375 | case kJSTypeString: | |
376 | return CYCastJavaString(jni, CYJSString(context, value)); | |
377 | case kJSTypeObject: | |
378 | return CYCastJavaObject(jni, context, CYCastJSObject(context, value)); | |
379 | ||
380 | case kJSTypeUndefined: | |
381 | default: | |
382 | _assert(false); | |
383 | } | |
384 | } | |
385 | ||
386 | CYJavaClass *CYGetJavaTable(JSContextRef context, JNIEnv *jni, jobject object) { | |
387 | // XXX: this implementation is absolutely unacceptable :/ | |
388 | return reinterpret_cast<CYJavaClass *>(JSObjectGetPrivate(CYGetJavaClass(context, jni, _envcall(jni, GetObjectClass(object))))); | |
389 | } | |
390 | ||
391 | static JSObjectRef CYGetJavaClass(JSContextRef context, JNIEnv *jni, jclass value) { | |
392 | JSObjectRef global(CYGetGlobalObject(context)); | |
393 | JSObjectRef cy(CYCastJSObject(context, CYGetProperty(context, global, cy_s))); | |
394 | ||
395 | jclass Class$(_envcall(jni, FindClass("java/lang/Class"))); | |
396 | jmethodID Class$getCanonicalName(_envcall(jni, GetMethodID(Class$, "getCanonicalName", "()Ljava/lang/String;"))); | |
397 | ||
398 | CYJSString name(jni, static_cast<jstring>(_envcall(jni, CallObjectMethod(value, Class$getCanonicalName)))); | |
399 | JSValueRef cached(CYGetProperty(context, cy, name)); | |
400 | if (!JSValueIsUndefined(context, cached)) | |
401 | return CYCastJSObject(context, cached); | |
402 | ||
403 | jmethodID Class$getDeclaredConstructors(_envcall(jni, GetMethodID(Class$, "getDeclaredConstructors", "()[Ljava/lang/reflect/Constructor;"))); | |
404 | jmethodID Class$getDeclaredFields(_envcall(jni, GetMethodID(Class$, "getDeclaredFields", "()[Ljava/lang/reflect/Field;"))); | |
405 | jmethodID Class$getDeclaredMethods(_envcall(jni, GetMethodID(Class$, "getDeclaredMethods", "()[Ljava/lang/reflect/Method;"))); | |
406 | ||
407 | jclass Constructor$(_envcall(jni, FindClass("java/lang/reflect/Constructor"))); | |
408 | //jmethodID Constructor$getModifiers(_envcall(jni, GetMethodID(Constructor$, "getModifiers", "()I"))); | |
409 | jmethodID Constructor$getParameterTypes(_envcall(jni, GetMethodID(Constructor$, "getParameterTypes", "()[Ljava/lang/Class;"))); | |
410 | ||
411 | jclass Field$(_envcall(jni, FindClass("java/lang/reflect/Field"))); | |
412 | jmethodID Field$getModifiers(_envcall(jni, GetMethodID(Field$, "getModifiers", "()I"))); | |
413 | jmethodID Field$getName(_envcall(jni, GetMethodID(Field$, "getName", "()Ljava/lang/String;"))); | |
414 | jmethodID Field$getType(_envcall(jni, GetMethodID(Field$, "getType", "()Ljava/lang/Class;"))); | |
415 | ||
416 | jclass Method$(_envcall(jni, FindClass("java/lang/reflect/Method"))); | |
417 | jmethodID Method$getModifiers(_envcall(jni, GetMethodID(Method$, "getModifiers", "()I"))); | |
418 | jmethodID Method$getName(_envcall(jni, GetMethodID(Method$, "getName", "()Ljava/lang/String;"))); | |
419 | jmethodID Method$getParameterTypes(_envcall(jni, GetMethodID(Method$, "getParameterTypes", "()[Ljava/lang/Class;"))); | |
420 | jmethodID Method$getReturnType(_envcall(jni, GetMethodID(Method$, "getReturnType", "()Ljava/lang/Class;"))); | |
421 | ||
422 | jclass Modifier$(_envcall(jni, FindClass("java/lang/reflect/Modifier"))); | |
423 | jmethodID Modifier$isStatic(_envcall(jni, GetStaticMethodID(Modifier$, "isStatic", "(I)Z"))); | |
424 | ||
425 | CYJavaClass *table(new CYJavaClass(jni, value)); | |
426 | ||
427 | for (jclass prototype(value); prototype != NULL; prototype = _envcall(jni, GetSuperclass(prototype))) { | |
428 | jobjectArray fields(static_cast<jobjectArray>(_envcall(jni, CallObjectMethod(prototype, Class$getDeclaredFields)))); | |
429 | ||
430 | for (jsize i(0), e(_envcall(jni, GetArrayLength(fields))); i != e; ++i) { | |
431 | jobject field(_envcall(jni, GetObjectArrayElement(fields, e - i - 1))); | |
432 | jint modifiers(_envcall(jni, CallIntMethod(field, Field$getModifiers))); | |
433 | bool instance(!_envcall(jni, CallStaticBooleanMethod(Modifier$, Modifier$isStatic, modifiers))); | |
434 | auto &map(instance ? table->instance_ : table->static_); | |
435 | CYJavaUTF8String name(jni, static_cast<jstring>(_envcall(jni, CallObjectMethod(field, Field$getName)))); | |
436 | jfieldID id(_envcall(jni, FromReflectedField(field))); | |
437 | jobject type(_envcall(jni, CallObjectMethod(field, Field$getType))); | |
438 | map.insert(std::make_pair(std::string(name), CYJavaField{id, CYJavaGetPrimitive(jni, type, Class$getCanonicalName)})); | |
439 | } | |
440 | } | |
441 | ||
442 | JSObjectRef constructor(JSObjectMake(context, CYJavaClass::Class_, table)); | |
443 | JSObjectRef indirect(JSObjectMake(context, NULL, NULL)); | |
444 | CYSetPrototype(context, constructor, indirect); | |
445 | ||
446 | JSObjectRef prototype(JSObjectMake(context, NULL, NULL)); | |
447 | CYSetProperty(context, constructor, prototype_s, prototype, kJSPropertyAttributeDontEnum); | |
448 | ||
449 | jobjectArray constructors(static_cast<jobjectArray>(_envcall(jni, CallObjectMethod(value, Class$getDeclaredConstructors)))); | |
450 | ||
451 | for (jsize i(0), e(_envcall(jni, GetArrayLength(constructors))); i != e; ++i) { | |
452 | jobject constructor(_envcall(jni, GetObjectArrayElement(constructors, i))); | |
453 | jobjectArray parameters(static_cast<jobjectArray>(_envcall(jni, CallObjectMethod(constructor, Constructor$getParameterTypes)))); | |
454 | CYJavaShorty shorty(CYJavaGetShorty(jni, parameters, Class$getCanonicalName)); | |
455 | table->overload_.insert(CYJavaSignature(jni, constructor, CYJavaPrimitiveObject, shorty)); | |
456 | } | |
457 | ||
458 | jobjectArray methods(static_cast<jobjectArray>(_envcall(jni, CallObjectMethod(value, Class$getDeclaredMethods)))); | |
459 | ||
460 | std::map<std::pair<bool, std::string>, CYJavaOverload> entries; | |
461 | ||
462 | for (jsize i(0), e(_envcall(jni, GetArrayLength(methods))); i != e; ++i) { | |
463 | jobject method(_envcall(jni, GetObjectArrayElement(methods, i))); | |
464 | jint modifiers(_envcall(jni, CallIntMethod(method, Method$getModifiers))); | |
465 | bool instance(!_envcall(jni, CallStaticBooleanMethod(Modifier$, Modifier$isStatic, modifiers))); | |
466 | CYJavaUTF8String name(jni, static_cast<jstring>(_envcall(jni, CallObjectMethod(method, Method$getName)))); | |
467 | jobjectArray parameters(static_cast<jobjectArray>(_envcall(jni, CallObjectMethod(method, Method$getParameterTypes)))); | |
468 | CYJavaShorty shorty(CYJavaGetShorty(jni, parameters, Class$getCanonicalName)); | |
469 | jobject type(_envcall(jni, CallObjectMethod(method, Method$getReturnType))); | |
470 | auto primitive(CYJavaGetPrimitive(jni, type, Class$getCanonicalName)); | |
471 | entries[std::make_pair(instance, std::string(name))].insert(CYJavaSignature(jni, method, primitive, shorty)); | |
472 | } | |
473 | ||
474 | for (const auto &entry : entries) { | |
475 | bool instance(entry.first.first); | |
476 | CYJSString name(entry.first.second); | |
477 | auto &overload(entry.second); | |
478 | auto target(instance ? prototype : indirect); | |
479 | JSValueRef wrapper(CYJavaMethod::Make(context, jni, overload)); | |
480 | CYSetProperty(context, target, name, wrapper, kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete); | |
481 | } | |
482 | ||
483 | // XXX: for some reason kJSPropertyAttributeDontEnum doesn't work if there's already a property with the same name | |
484 | // by not linking the prototypes until after we set the properties, we hide the parent property from this issue :( | |
485 | ||
486 | if (jclass super = _envcall(jni, GetSuperclass(value))) { | |
487 | JSObjectRef parent(CYGetJavaClass(context, jni, super)); | |
488 | CYSetPrototype(context, indirect, CYGetPrototype(context, parent)); | |
489 | CYSetPrototype(context, prototype, CYGetProperty(context, parent, prototype_s)); | |
490 | } | |
491 | ||
492 | CYSetProperty(context, cy, name, constructor); | |
493 | return constructor; | |
494 | } | |
495 | ||
496 | static jobjectArray CYCastJavaArguments(JNIEnv *jni, const CYJavaShorty &shorty, JSContextRef context, const JSValueRef arguments[], jclass Object$) { | |
497 | jobjectArray array(_envcall(jni, NewObjectArray(shorty.size(), Object$, NULL))); | |
498 | for (size_t index(0); index != shorty.size(); ++index) { | |
499 | jobject argument; | |
500 | switch (shorty[index]) { | |
501 | case CYJavaPrimitiveObject: | |
502 | argument = CYCastJavaObject(jni, context, arguments[index]); | |
503 | break; | |
504 | #define CYJavaForEachPrimitive_(T, Typ, Type) \ | |
505 | case CYJavaPrimitive ## Type: \ | |
506 | argument = CYCastJava ## Type(jni, context, arguments[index]); \ | |
507 | break; | |
508 | CYJavaForEachPrimitive | |
509 | #undef CYJavaForEachPrimitive_ | |
510 | default: | |
511 | _assert(false); | |
512 | } | |
513 | _envcallv(jni, SetObjectArrayElement(array, index, argument)); | |
514 | } | |
515 | ||
516 | return array; | |
517 | } | |
518 | ||
519 | static JSValueRef JavaMethod_callAsFunction(JSContextRef context, JSObjectRef object, JSObjectRef _this, size_t count, const JSValueRef arguments[], JSValueRef *exception) { CYTry { | |
520 | CYJavaMethod *internal(reinterpret_cast<CYJavaMethod *>(JSObjectGetPrivate(object))); | |
521 | JNIEnv *jni(internal->jni_); | |
522 | ||
523 | jclass Object$(_envcall(jni, FindClass("java/lang/Object"))); | |
524 | ||
525 | jclass Method$(_envcall(jni, FindClass("java/lang/reflect/Method"))); | |
526 | jmethodID Method$invoke(_envcall(jni, GetMethodID(Method$, "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"))); | |
527 | ||
528 | jobject self(CYCastJavaObject(jni, context, _this)); | |
529 | ||
530 | CYJavaSignature bound(count); | |
531 | for (auto overload(internal->overload_.lower_bound(bound)), e(internal->overload_.upper_bound(bound)); overload != e; ++overload) { | |
532 | jobjectArray array(CYCastJavaArguments(jni, overload->shorty, context, arguments, Object$)); | |
533 | jobject object(_envcall(jni, CallObjectMethod(overload->method, Method$invoke, self, array))); | |
534 | return CYCastJSValue(context, jni, object); | |
535 | } | |
536 | ||
537 | CYThrow("invalid method call"); | |
538 | } CYCatch(NULL) } | |
539 | ||
540 | static JSObjectRef JavaClass_callAsConstructor(JSContextRef context, JSObjectRef object, size_t count, const JSValueRef arguments[], JSValueRef *exception) { CYTry { | |
541 | CYJavaClass *table(reinterpret_cast<CYJavaClass *>(JSObjectGetPrivate(object))); | |
542 | JNIEnv *jni(table->value_); | |
543 | ||
544 | jclass Object$(_envcall(jni, FindClass("java/lang/Object"))); | |
545 | ||
546 | jclass Constructor$(_envcall(jni, FindClass("java/lang/reflect/Constructor"))); | |
547 | jmethodID Constructor$newInstance(_envcall(jni, GetMethodID(Constructor$, "newInstance", "([Ljava/lang/Object;)Ljava/lang/Object;"))); | |
548 | ||
549 | CYJavaSignature bound(count); | |
550 | for (auto overload(table->overload_.lower_bound(bound)), e(table->overload_.upper_bound(bound)); overload != e; ++overload) { | |
551 | jobjectArray array(CYCastJavaArguments(jni, overload->shorty, context, arguments, Object$)); | |
552 | jobject object(_envcall(jni, CallObjectMethod(overload->method, Constructor$newInstance, array))); | |
553 | return CYCastJSObject(context, CYCastJSValue(context, jni, object)); | |
554 | } | |
555 | ||
556 | CYThrow("invalid constructor call"); | |
557 | } CYCatch(NULL) } | |
558 | ||
559 | static bool JavaClass_hasProperty(JSContextRef context, JSObjectRef object, JSStringRef property) { | |
560 | CYJavaClass *table(reinterpret_cast<CYJavaClass *>(JSObjectGetPrivate(object))); | |
561 | CYPool pool; | |
562 | auto name(CYPoolUTF8String(pool, context, property)); | |
563 | auto field(table->static_.find(name)); | |
564 | if (field == table->static_.end()) | |
565 | return false; | |
566 | return true; | |
567 | } | |
568 | ||
569 | static JSValueRef JavaClass_getProperty(JSContextRef context, JSObjectRef object, JSStringRef property, JSValueRef *exception) { CYTry { | |
570 | CYJavaClass *table(reinterpret_cast<CYJavaClass *>(JSObjectGetPrivate(object))); | |
571 | JNIEnv *jni(table->value_); | |
572 | CYPool pool; | |
573 | auto name(CYPoolUTF8String(pool, context, property)); | |
574 | auto field(table->static_.find(name)); | |
575 | if (field == table->static_.end()) | |
576 | return NULL; | |
577 | ||
578 | switch (field->second.primitive_) { | |
579 | case CYJavaPrimitiveObject: | |
580 | return CYCastJSValue(context, jni, _envcall(jni, GetStaticObjectField(table->value_, field->second.field_))); | |
581 | #define CYJavaForEachPrimitive_(T, Typ, Type) \ | |
582 | case CYJavaPrimitive ## Type: \ | |
583 | return CYJavaCastJSValue(context, _envcall(jni, GetStatic ## Typ ## Field(table->value_, field->second.field_))); | |
584 | CYJavaForEachPrimitive | |
585 | #undef CYJavaForEachPrimitive_ | |
586 | default: _assert(false); | |
587 | } | |
588 | } CYCatch(NULL) } | |
589 | ||
590 | static bool JavaClass_setProperty(JSContextRef context, JSObjectRef object, JSStringRef property, JSValueRef value, JSValueRef *exception) { CYTry { | |
591 | CYJavaClass *table(reinterpret_cast<CYJavaClass *>(JSObjectGetPrivate(object))); | |
592 | JNIEnv *jni(table->value_); | |
593 | CYPool pool; | |
594 | auto name(CYPoolUTF8String(pool, context, property)); | |
595 | auto field(table->static_.find(name)); | |
596 | if (field == table->static_.end()) | |
597 | return false; | |
598 | ||
599 | switch (field->second.primitive_) { | |
600 | case CYJavaPrimitiveObject: | |
601 | _envcallv(jni, SetStaticObjectField(table->value_, field->second.field_, CYCastJavaObject(jni, context, value))); | |
602 | #define CYJavaForEachPrimitive_(T, Typ, Type) \ | |
603 | case CYJavaPrimitive ## Type: \ | |
604 | _envcallv(jni, SetStatic ## Typ ## Field(table->value_, field->second.field_, CYCastDouble(context, value))); \ | |
605 | break; | |
606 | CYJavaForEachPrimitive | |
607 | #undef CYJavaForEachPrimitive_ | |
608 | default: _assert(false); | |
609 | } | |
610 | ||
611 | return true; | |
612 | } CYCatch(false) } | |
613 | ||
614 | static void JavaClass_getPropertyNames(JSContextRef context, JSObjectRef object, JSPropertyNameAccumulatorRef names) { | |
615 | CYJavaClass *table(reinterpret_cast<CYJavaClass *>(JSObjectGetPrivate(object))); | |
616 | for (const auto &field : table->static_) | |
617 | JSPropertyNameAccumulatorAddName(names, CYJSString(field.first)); | |
618 | } | |
619 | ||
620 | static JSValueRef JavaClass_getProperty_class(JSContextRef context, JSObjectRef object, JSStringRef property, JSValueRef *exception) { CYTry { | |
621 | CYJavaClass *table(reinterpret_cast<CYJavaClass *>(JSObjectGetPrivate(object))); | |
622 | return CYCastJSValue(context, table->value_, table->value_); | |
623 | } CYCatch(NULL) } | |
624 | ||
625 | static bool JavaObject_hasProperty(JSContextRef context, JSObjectRef object, JSStringRef property) { | |
626 | CYJavaObject *internal(reinterpret_cast<CYJavaObject *>(JSObjectGetPrivate(object))); | |
627 | JNIEnv *jni(internal->value_); | |
628 | CYJavaClass *table(CYGetJavaTable(context, jni, internal->value_)); | |
629 | CYPool pool; | |
630 | auto name(CYPoolUTF8String(pool, context, property)); | |
631 | auto field(table->instance_.find(name)); | |
632 | if (field == table->instance_.end()) | |
633 | return false; | |
634 | return true; | |
635 | } | |
636 | ||
637 | static JSValueRef JavaObject_getProperty(JSContextRef context, JSObjectRef object, JSStringRef property, JSValueRef *exception) { CYTry { | |
638 | CYJavaObject *internal(reinterpret_cast<CYJavaObject *>(JSObjectGetPrivate(object))); | |
639 | JNIEnv *jni(internal->value_); | |
640 | CYJavaClass *table(CYGetJavaTable(context, jni, internal->value_)); | |
641 | CYPool pool; | |
642 | auto name(CYPoolUTF8String(pool, context, property)); | |
643 | auto field(table->instance_.find(name)); | |
644 | if (field == table->instance_.end()) | |
645 | return NULL; | |
646 | ||
647 | switch (field->second.primitive_) { | |
648 | case CYJavaPrimitiveObject: | |
649 | return CYCastJSValue(context, jni, _envcall(jni, GetObjectField(internal->value_, field->second.field_))); | |
650 | #define CYJavaForEachPrimitive_(T, Typ, Type) \ | |
651 | case CYJavaPrimitive ## Type: \ | |
652 | return CYJavaCastJSValue(context, _envcall(jni, Get ## Typ ## Field(internal->value_, field->second.field_))); | |
653 | CYJavaForEachPrimitive | |
654 | #undef CYJavaForEachPrimitive_ | |
655 | default: _assert(false); | |
656 | } | |
657 | } CYCatch(NULL) } | |
658 | ||
659 | static bool JavaObject_setProperty(JSContextRef context, JSObjectRef object, JSStringRef property, JSValueRef value, JSValueRef *exception) { CYTry { | |
660 | CYJavaObject *internal(reinterpret_cast<CYJavaObject *>(JSObjectGetPrivate(object))); | |
661 | JNIEnv *jni(internal->value_); | |
662 | CYJavaClass *table(CYGetJavaTable(context, jni, internal->value_)); | |
663 | CYPool pool; | |
664 | auto name(CYPoolUTF8String(pool, context, property)); | |
665 | auto field(table->instance_.find(name)); | |
666 | if (field == table->instance_.end()) | |
667 | return false; | |
668 | ||
669 | switch (field->second.primitive_) { | |
670 | case CYJavaPrimitiveObject: | |
671 | _envcallv(jni, SetObjectField(table->value_, field->second.field_, CYCastJavaObject(jni, context, value))); | |
672 | #define CYJavaForEachPrimitive_(T, Typ, Type) \ | |
673 | case CYJavaPrimitive ## Type: \ | |
674 | _envcallv(jni, Set ## Typ ## Field(table->value_, field->second.field_, CYCastDouble(context, value))); \ | |
675 | break; | |
676 | CYJavaForEachPrimitive | |
677 | #undef CYJavaForEachPrimitive_ | |
678 | default: _assert(false); | |
679 | } | |
680 | ||
681 | return true; | |
682 | } CYCatch(false) } | |
683 | ||
684 | static void JavaObject_getPropertyNames(JSContextRef context, JSObjectRef object, JSPropertyNameAccumulatorRef names) { | |
685 | CYJavaObject *internal(reinterpret_cast<CYJavaObject *>(JSObjectGetPrivate(object))); | |
686 | JNIEnv *jni(internal->value_); | |
687 | CYJavaClass *table(CYGetJavaTable(context, jni, internal->value_)); | |
688 | for (const auto &field : table->instance_) | |
689 | JSPropertyNameAccumulatorAddName(names, CYJSString(field.first)); | |
690 | } | |
691 | ||
692 | static JSValueRef JavaObject_getProperty_constructor(JSContextRef context, JSObjectRef object, JSStringRef property, JSValueRef *exception) { CYTry { | |
693 | CYJavaObject *internal(reinterpret_cast<CYJavaObject *>(JSObjectGetPrivate(object))); | |
694 | JNIEnv *jni(internal->value_); | |
695 | return CYGetJavaClass(context, jni, _envcall(jni, GetObjectClass(internal->value_))); | |
696 | } CYCatch(NULL) } | |
697 | ||
698 | static bool CYJavaPackage_hasProperty(JSContextRef context, JSObjectRef object, JSStringRef property) { | |
699 | return true; | |
700 | } | |
701 | ||
702 | static JSValueRef CYJavaPackage_getProperty(JSContextRef context, JSObjectRef object, JSStringRef property, JSValueRef *exception) { CYTry { | |
703 | CYJavaPackage *internal(reinterpret_cast<CYJavaPackage *>(JSObjectGetPrivate(object))); | |
704 | CYJavaPackage::Path package(internal->package_); | |
705 | ||
706 | CYPool pool; | |
707 | const char *next(CYPoolCString(pool, context, property)); | |
708 | ||
709 | std::ostringstream name; | |
710 | for (auto &package : internal->package_) | |
711 | name << package << '/'; | |
712 | name << next; | |
713 | ||
714 | JNIEnv *jni(GetJNI()); | |
715 | if (jclass _class = jni->FindClass(name.str().c_str())) | |
716 | return CYGetJavaClass(context, jni, _class); | |
717 | jni->ExceptionClear(); | |
718 | ||
719 | package.push_back(next); | |
720 | return CYJavaPackage::Make(context, package); | |
721 | } CYCatch(NULL) } | |
722 | ||
723 | static JSStaticValue JavaClass_staticValues[2] = { | |
724 | {"class", &JavaClass_getProperty_class, NULL, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, | |
725 | {NULL, NULL, NULL, 0} | |
726 | }; | |
727 | ||
728 | static JSStaticValue JavaObject_staticValues[2] = { | |
729 | {"constructor", &JavaObject_getProperty_constructor, NULL, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete}, | |
730 | {NULL, NULL, NULL, 0} | |
731 | }; | |
732 | ||
733 | static JSStaticFunction JavaPackage_staticFunctions[1] = { | |
734 | {NULL, NULL, 0} | |
735 | }; | |
736 | ||
737 | void CYJava_Initialize() { | |
738 | Primitives_.insert(std::make_pair("void", CYJavaPrimitiveVoid)); | |
739 | Primitives_.insert(std::make_pair("boolean", CYJavaPrimitiveBoolean)); | |
740 | Primitives_.insert(std::make_pair("byte", CYJavaPrimitiveByte)); | |
741 | Primitives_.insert(std::make_pair("char", CYJavaPrimitiveCharacter)); | |
742 | Primitives_.insert(std::make_pair("short", CYJavaPrimitiveShort)); | |
743 | Primitives_.insert(std::make_pair("int", CYJavaPrimitiveInteger)); | |
744 | Primitives_.insert(std::make_pair("long", CYJavaPrimitiveLong)); | |
745 | Primitives_.insert(std::make_pair("float", CYJavaPrimitiveFloat)); | |
746 | Primitives_.insert(std::make_pair("double", CYJavaPrimitiveDouble)); | |
747 | ||
748 | JSClassDefinition definition; | |
749 | ||
750 | definition = kJSClassDefinitionEmpty; | |
751 | definition.className = "JavaClass"; | |
752 | definition.staticValues = JavaClass_staticValues; | |
753 | definition.hasProperty = &JavaClass_hasProperty; | |
754 | definition.getProperty = &JavaClass_getProperty; | |
755 | definition.setProperty = &JavaClass_setProperty; | |
756 | definition.getPropertyNames = &JavaClass_getPropertyNames; | |
757 | definition.callAsConstructor = &JavaClass_callAsConstructor; | |
758 | definition.finalize = &CYFinalize; | |
759 | CYJavaClass::Class_ = JSClassCreate(&definition); | |
760 | ||
761 | definition = kJSClassDefinitionEmpty; | |
762 | definition.className = "JavaMethod"; | |
763 | definition.callAsFunction = &JavaMethod_callAsFunction; | |
764 | definition.finalize = &CYFinalize; | |
765 | CYJavaMethod::Class_ = JSClassCreate(&definition); | |
766 | ||
767 | definition = kJSClassDefinitionEmpty; | |
768 | definition.attributes = kJSClassAttributeNoAutomaticPrototype; | |
769 | definition.className = "JavaObject"; | |
770 | definition.staticValues = JavaObject_staticValues; | |
771 | definition.hasProperty = &JavaObject_hasProperty; | |
772 | definition.getProperty = &JavaObject_getProperty; | |
773 | definition.setProperty = &JavaObject_setProperty; | |
774 | definition.getPropertyNames = &JavaObject_getPropertyNames; | |
775 | definition.finalize = &CYFinalize; | |
776 | CYJavaObject::Class_ = JSClassCreate(&definition); | |
777 | ||
778 | definition = kJSClassDefinitionEmpty; | |
779 | definition.className = "JavaPackage"; | |
780 | definition.staticFunctions = JavaPackage_staticFunctions; | |
781 | definition.hasProperty = &CYJavaPackage_hasProperty; | |
782 | definition.getProperty = &CYJavaPackage_getProperty; | |
783 | definition.finalize = &CYFinalize; | |
784 | CYJavaPackage::Class_ = JSClassCreate(&definition); | |
785 | } | |
786 | ||
787 | void CYJava_SetupContext(JSContextRef context) { | |
788 | JSObjectRef global(CYGetGlobalObject(context)); | |
789 | //JSObjectRef cy(CYCastJSObject(context, CYGetProperty(context, global, cy_s))); | |
790 | JSObjectRef cycript(CYCastJSObject(context, CYGetProperty(context, global, CYJSString("Cycript")))); | |
791 | JSObjectRef all(CYCastJSObject(context, CYGetProperty(context, cycript, CYJSString("all")))); | |
792 | //JSObjectRef alls(CYCastJSObject(context, CYGetProperty(context, cycript, CYJSString("alls")))); | |
793 | ||
794 | JSObjectRef Java(JSObjectMake(context, NULL, NULL)); | |
795 | CYSetProperty(context, cycript, CYJSString("Java"), Java); | |
796 | ||
797 | JSObjectRef Packages(CYJavaPackage::Make(context, CYJavaPackage::Path())); | |
798 | CYSetProperty(context, all, CYJSString("Packages"), Packages); | |
799 | ||
800 | for (auto name : (const char *[]) {"java", "javax", "android", "com", "net", "org"}) { | |
801 | CYJSString js(name); | |
802 | CYSetProperty(context, all, js, CYGetProperty(context, Packages, js)); | |
803 | } | |
804 | } | |
805 | ||
806 | static CYHook CYJavaHook = { | |
807 | NULL, | |
808 | NULL, | |
809 | NULL, | |
810 | &CYJava_Initialize, | |
811 | &CYJava_SetupContext, | |
812 | NULL, | |
813 | }; | |
814 | ||
815 | CYRegisterHook CYJava(&CYJavaHook); |