ObjectManager::ObjectManager()
:
		m_numberOfGC(0), m_currentObjectId(0), m_cache(NewWeakGlobalRefCallback, DeleteWeakGlobalRefCallback, 1000, this)
{
	JEnv env;

	PlatformClass = env.FindClass("com/tns/Platform");
	assert(PlatformClass != nullptr);

	GET_JAVAOBJECT_BY_ID_METHOD_ID = env.GetStaticMethodID(PlatformClass, "getJavaObjectByID", "(I)Ljava/lang/Object;");
	assert(GET_JAVAOBJECT_BY_ID_METHOD_ID != nullptr);

	GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID = env.GetStaticMethodID(PlatformClass, "getOrCreateJavaObjectID", "(Ljava/lang/Object;)I");
	assert(GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID != nullptr);

	MAKE_INSTANCE_WEAK_BATCH_METHOD_ID = env.GetStaticMethodID(PlatformClass, "makeInstanceWeak", "(Ljava/nio/ByteBuffer;IZ)V");
	assert(MAKE_INSTANCE_WEAK_BATCH_METHOD_ID != nullptr);

	CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID = env.GetStaticMethodID(PlatformClass, "checkWeakObjectAreAlive", "(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;I)V");
	assert(CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID != nullptr);

	JAVA_LANG_CLASS = env.FindClass("java/lang/Class");
	assert(JAVA_LANG_CLASS != nullptr);

	GET_NAME_METHOD_ID = env.GetMethodID(JAVA_LANG_CLASS, "getName", "()Ljava/lang/String;");
	assert(GET_NAME_METHOD_ID != nullptr);

	ObjectManager::instance = this;
}
void Module::Init(Isolate *isolate)
{
	JEnv env;

	MODULE_CLASS = env.FindClass("com/tns/Module");
	assert(MODULE_CLASS != nullptr);

	RESOLVE_PATH_METHOD_ID = env.GetStaticMethodID(MODULE_CLASS, "resolvePath", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
	assert(RESOLVE_PATH_METHOD_ID != nullptr);

	string requireFactoryScript =
	"(function () { "
	"	function require_factory(requireInternal, dirName) { "
	"		return function require(modulePath) { "
	"			if(global.__requireOverride) { "
	"				var result = global.__requireOverride(modulePath, dirName); "
	"				if(result) { "
	"					return result; "
	"				} "
	"			} "
	"			return requireInternal(modulePath, dirName); "
	"		} "
	"	} "
	"	return require_factory; "
	"})()";

	auto source = ConvertToV8String(requireFactoryScript);
	auto context = isolate->GetCurrentContext();

	Local<Script> script;
	auto maybeScript = Script::Compile(context, source).ToLocal(&script);

	assert(!script.IsEmpty());

	Local<Value> result;
	auto maybeResult = script->Run(context).ToLocal(&result);

	assert(!result.IsEmpty() && result->IsFunction());

	auto requireFactoryFunction = result.As<Function>();

	auto cache = GetCache(isolate);

	cache->RequireFactoryFunction = new Persistent<Function>(isolate, requireFactoryFunction);

	auto requireFuncTemplate = FunctionTemplate::New(isolate, RequireCallback);
	auto requireFunc = requireFuncTemplate->GetFunction();
	cache->RequireFunction = new Persistent<Function>(isolate, requireFunc);

	auto global = isolate->GetCurrentContext()->Global();
	auto globalRequire = GetRequireFunction(isolate, Constants::APP_ROOT_FOLDER_PATH);
	global->Set(ConvertToV8String("require"), globalRequire);
}
void NativeScriptRuntime::DumpReferenceTablesMethodCallback(const v8::FunctionCallbackInfo<v8::Value>& args)
{
	JEnv env;
	jclass vmDbgClass = env.FindClass("dalvik/system/VMDebug");
	if (vmDbgClass != nullptr)
	{
		jmethodID mid = env.GetStaticMethodID(vmDbgClass, "dumpReferenceTables", "()V");
		if (mid != 0)
		{
			env.CallStaticVoidMethod(vmDbgClass, mid);
		}
	}
}
void NativeScriptRuntime::Init(JavaVM *jvm, ObjectManager *objectManager)
{
	NativeScriptRuntime::jvm = jvm;

	JEnv env;

	JAVA_LANG_STRING = env.FindClass("java/lang/String");
	assert(JAVA_LANG_STRING != nullptr);

	PlatformClass = env.FindClass("com/tns/Platform");
	assert(PlatformClass != nullptr);

	RESOLVE_CLASS_METHOD_ID = env.GetStaticMethodID(PlatformClass, "resolveClass", "(Ljava/lang/String;[Ljava/lang/String;)Ljava/lang/Class;");
	assert(RESOLVE_CLASS_METHOD_ID != nullptr);

	CREATE_INSTANCE_METHOD_ID = env.GetStaticMethodID(PlatformClass, "createInstance", "([Ljava/lang/Object;II)Ljava/lang/Object;");
	assert(CREATE_INSTANCE_METHOD_ID != nullptr);

	CACHE_CONSTRUCTOR_METHOD_ID = env.GetStaticMethodID(PlatformClass, "cacheConstructor", "(Ljava/lang/Class;[Ljava/lang/Object;)I");
	assert(CACHE_CONSTRUCTOR_METHOD_ID != nullptr);

	GET_TYPE_METADATA = env.GetStaticMethodID(PlatformClass, "getTypeMetadata", "(Ljava/lang/String;I)[Ljava/lang/String;");
	assert(GET_TYPE_METADATA != nullptr);

	ENABLE_VERBOSE_LOGGING_METHOD_ID = env.GetStaticMethodID(PlatformClass, "enableVerboseLogging", "()V");
	assert(ENABLE_VERBOSE_LOGGING_METHOD_ID != nullptr);

	DISABLE_VERBOSE_LOGGING_METHOD_ID = env.GetStaticMethodID(PlatformClass, "disableVerboseLogging", "()V");
	assert(ENABLE_VERBOSE_LOGGING_METHOD_ID != nullptr);

	GET_CHANGE_IN_BYTES_OF_USED_MEMORY_METHOD_ID = env.GetStaticMethodID(PlatformClass, "getChangeInBytesOfUsedMemory", "()J");
	assert(GET_CHANGE_IN_BYTES_OF_USED_MEMORY_METHOD_ID != nullptr);

	MetadataNode::Init(objectManager);

	NativeScriptRuntime::objectManager = objectManager;

	fieldAccessor.Init(jvm, objectManager);

	arrayElementAccessor.Init(jvm, objectManager);

	JavaObjectArrayCache::Init(jvm);

	MethodCache::Init();
}
void NativeScriptRuntime::DumpReferenceTablesMethodCallback(const v8::FunctionCallbackInfo<v8::Value>& args)
{
	try {
	JEnv env;
	jclass vmDbgClass = env.FindClass("dalvik/system/VMDebug");
	if (vmDbgClass != nullptr)
	{
		jmethodID mid = env.GetStaticMethodID(vmDbgClass, "dumpReferenceTables", "()V");
		if (mid != 0)
		{
			env.CallStaticVoidMethod(vmDbgClass, mid);
		}
	}
	} catch (NativeScriptException& e) {
		e.ReThrowToV8();
	}
	catch (exception e) {
		DEBUG_WRITE("Error: c++ exception: %s", e.what());
	}
	catch (...) {
		DEBUG_WRITE("Error: c++ exception!");
	}
}
Handle<Object> NativeScriptRuntime::FindClass(const string& className)
{
	Handle<Object> clazz;
	JEnv env;

	jmethodID mid = env.GetStaticMethodID(PlatformClass, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;");
	assert(mid != nullptr);
	JniLocalRef name(env.NewStringUTF(className.c_str()));
	jclass c = (jclass)env.CallStaticObjectMethod(PlatformClass, mid, (jstring)name);
	if (env.ExceptionCheck() == JNI_FALSE)
	{
		jint javaObjectID = objectManager->GetOrCreateObjectId(c);
		clazz = objectManager->GetJsObjectByJavaObject(javaObjectID);

		if (clazz.IsEmpty())
		{
			clazz = objectManager->CreateJSWrapper(javaObjectID, "Ljava/lang/Class;", c);
		}

		env.DeleteLocalRef(c);
	}
	return clazz;
}
void NativeScriptRuntime::CallJavaMethod(const Local<Object>& caller, const string& className, const string& methodName, MetadataEntry *entry, bool isStatic, bool isSuper, const v8::FunctionCallbackInfo<v8::Value>& args)
{
	SET_PROFILER_FRAME();

	JEnv env;

	jclass clazz;
	jmethodID mid;
	string *sig = nullptr;
	string *returnType = nullptr;
	auto retType = MethodReturnType::Unknown;
	MethodCache::CacheMethodInfo mi;

	if ((entry != nullptr) && entry->isResolved)
	{
		isStatic = entry->isStatic;

		if (entry->memberId == nullptr)
		{
			clazz = env.FindClass(className);
			if (clazz == nullptr)
			{
				MetadataNode* callerNode = MetadataNode::GetNodeFromHandle(caller);
				const string callerClassName = callerNode->GetName();

				DEBUG_WRITE("Cannot resolve class: %s while calling method: %s callerClassName: %s", className.c_str(), methodName.c_str(), callerClassName.c_str());
				clazz = env.FindClass(callerClassName);
				if (clazz == nullptr)
				{
					//todo: plamen5kov: throw exception here
					DEBUG_WRITE("Cannot resolve caller's class name: %s", callerClassName.c_str());
					return;
				}

				entry->memberId = isStatic ?
						env.GetStaticMethodID(clazz, methodName, entry->sig) :
						env.GetMethodID(clazz, methodName, entry->sig);

				if (entry->memberId == nullptr)
				{
					//todo: plamen5kov: throw exception here
					DEBUG_WRITE("Cannot resolve a method %s on caller class: %s", methodName.c_str(), callerClassName.c_str());
					return;
				}
			}
			else
			{
				entry->memberId = isStatic ?
									env.GetStaticMethodID(clazz, methodName, entry->sig) :
									env.GetMethodID(clazz, methodName, entry->sig);

				if (entry->memberId == nullptr)
				{
					//todo: plamen5kov: throw exception here
					DEBUG_WRITE("Cannot resolve a method %s on class: %s", methodName.c_str(), className.c_str());
					return;
				}
			}
			entry->clazz = clazz;
		}

		mid = reinterpret_cast<jmethodID>(entry->memberId);
		clazz = entry->clazz;
		sig = &entry->sig;
		returnType = &entry->returnType;
		retType = entry->retType;
	}
	else
	{
		DEBUG_WRITE("Resolving method: %s on className %s", methodName.c_str(), className.c_str());

		clazz = env.FindClass(className);
		if (clazz != nullptr)
		{
			mi = MethodCache::ResolveMethodSignature(className, methodName, args, isStatic);
			if (mi.mid == nullptr)
			{
				DEBUG_WRITE("Cannot resolve class=%s, method=%s, isStatic=%d, isSuper=%d", className.c_str(), methodName.c_str(), isStatic, isSuper);
				return;
			}
		}
		else
		{
			MetadataNode* callerNode = MetadataNode::GetNodeFromHandle(caller);
			const string callerClassName = callerNode->GetName();
			DEBUG_WRITE("Resolving method on caller class: %s.%s on className %s", callerClassName.c_str(), methodName.c_str(), className.c_str());
			mi = MethodCache::ResolveMethodSignature(callerClassName, methodName, args, isStatic);
			if (mi.mid == nullptr)
			{
				DEBUG_WRITE("Cannot resolve class=%s, method=%s, isStatic=%d, isSuper=%d, callerClass=%s", className.c_str(), methodName.c_str(), isStatic, isSuper, callerClassName.c_str());
				return;
			}
		}

		clazz = mi.clazz;
		mid = mi.mid;
		sig = &mi.signature;
		returnType = &mi.returnType;
		retType = mi.retType;
	}


	if (!isStatic)
	{
		DEBUG_WRITE("CallJavaMethod called %s.%s. Instance id: %d, isSuper=%d", className.c_str(), methodName.c_str(), caller.IsEmpty() ? -42 : caller->GetIdentityHash(), isSuper);
	}
	else
	{
		DEBUG_WRITE("CallJavaMethod called %s.%s. static method", className.c_str(), methodName.c_str());
	}

	JsArgConverter argConverter(args, false, *sig, entry);

	if (!argConverter.IsValid())
	{
		JsArgConverter::Error err = argConverter.GetError();
		throw NativeScriptException(err.msg);
	}

	auto isolate = Isolate::GetCurrent();

	jweak callerJavaObject;

	jvalue* javaArgs = argConverter.ToArgs();

	if (!isStatic)
	{
		callerJavaObject = objectManager->GetJavaObjectByJsObject(caller);
		if(callerJavaObject == nullptr)
		{
			stringstream ss;
			ss << "No java object found on which to call \"" << methodName << "\" method. It is possible your Javascript object is not linked with the corresponding Java class. Try passing context(this) to the constructor function.";
			throw NativeScriptException(ss.str());
		}
	}

	switch (retType)
	{
		case MethodReturnType::Void:
		{
			if (isStatic)
			{
				env.CallStaticVoidMethodA(clazz, mid, javaArgs);
			}
			else if (isSuper)
			{
				env.CallNonvirtualVoidMethodA(callerJavaObject, clazz, mid, javaArgs);
			}
			else
			{
				env.CallVoidMethodA(callerJavaObject, mid, javaArgs);
			}
			break;
		}
		case MethodReturnType::Boolean:
		{
			jboolean result;
			if (isStatic)
			{
				result = env.CallStaticBooleanMethodA(clazz, mid, javaArgs);
			}
			else if (isSuper)
			{
				result = env.CallNonvirtualBooleanMethodA(callerJavaObject, clazz, mid, javaArgs);
			}
			else
			{
				result = env.CallBooleanMethodA(callerJavaObject, mid, javaArgs);
			}
			args.GetReturnValue().Set(result != 0 ? True(isolate) : False(isolate));
			break;
		}
		case MethodReturnType::Byte:
		{
			jbyte result;
			if (isStatic)
			{
				result = env.CallStaticByteMethodA(clazz, mid, javaArgs);
			}
			else if (isSuper)
			{
				result = env.CallNonvirtualByteMethodA(callerJavaObject, clazz, mid, javaArgs);
			}
			else
			{
				result = env.CallByteMethodA(callerJavaObject, mid, javaArgs);
			}
			args.GetReturnValue().Set(result);
			break;
		}
		case MethodReturnType::Char:
		{
			jchar result;
			if (isStatic)
			{
				result = env.CallStaticCharMethodA(clazz, mid, javaArgs);
			}
			else if (isSuper)
			{
				result = env.CallNonvirtualCharMethodA(callerJavaObject, clazz, mid, javaArgs);
			}
			else
			{
				result = env.CallCharMethodA(callerJavaObject, mid, javaArgs);
			}

			JniLocalRef str(env.NewString(&result, 1));
			jboolean bol = true;
			const char* resP = env.GetStringUTFChars(str, &bol);
			args.GetReturnValue().Set(ConvertToV8String(resP, 1));
			env.ReleaseStringUTFChars(str, resP);
			break;
		}
		case MethodReturnType::Short:
		{
			jshort result;
			if (isStatic)
			{
				result = env.CallStaticShortMethodA(clazz, mid, javaArgs);
			}
			else if (isSuper)
			{
				result = env.CallNonvirtualShortMethodA(callerJavaObject, clazz, mid, javaArgs);
			}
			else
			{
				result = env.CallShortMethodA(callerJavaObject, mid, javaArgs);
			}
			args.GetReturnValue().Set(result);
			break;
		}
		case MethodReturnType::Int:
		{
			jint result;
			if (isStatic)
			{
				result = env.CallStaticIntMethodA(clazz, mid, javaArgs);
			}
			else if (isSuper)
			{
				result = env.CallNonvirtualIntMethodA(callerJavaObject, clazz, mid, javaArgs);
			}
			else
			{
				result = env.CallIntMethodA(callerJavaObject, mid, javaArgs);
			}
			args.GetReturnValue().Set(result);
			break;

		}
		case MethodReturnType::Long:
		{
			jlong result;
			if (isStatic)
			{
				result = env.CallStaticLongMethodA(clazz, mid, javaArgs);
			}
			else if (isSuper)
			{
				result = env.CallNonvirtualLongMethodA(callerJavaObject, clazz, mid, javaArgs);
			}
			else
			{
				result = env.CallLongMethodA(callerJavaObject, mid, javaArgs);
			}
			auto jsLong = ArgConverter::ConvertFromJavaLong(result);
			args.GetReturnValue().Set(jsLong);
			break;
		}
		case MethodReturnType::Float:
		{
			jfloat result;
			if (isStatic)
			{
				result = env.CallStaticFloatMethodA(clazz, mid, javaArgs);
			}
			else if (isSuper)
			{
				result = env.CallNonvirtualFloatMethodA(callerJavaObject, clazz, mid, javaArgs);
			}
			else
			{
				result = env.CallFloatMethodA(callerJavaObject, mid, javaArgs);
			}
			args.GetReturnValue().Set((double) result); //TODO: handle float value here correctly.
			break;
		}
		case MethodReturnType::Double:
		{
			jdouble result;
			if (isStatic)
			{
				result = env.CallStaticDoubleMethodA(clazz, mid, javaArgs);
			}
			else if (isSuper)
			{
				result = env.CallNonvirtualDoubleMethodA(callerJavaObject, clazz, mid, javaArgs);
			}
			else
			{
				result = env.CallDoubleMethodA(callerJavaObject, mid, javaArgs);
			}
			args.GetReturnValue().Set(result);
			break;
		}
		case MethodReturnType::String:
		{
			jobject result = nullptr;
			bool exceptionOccurred;

			if (isStatic)
			{
				result = env.CallStaticObjectMethodA(clazz, mid, javaArgs);
			}
			else if (isSuper)
			{
				result = env.CallNonvirtualObjectMethodA(callerJavaObject, clazz, mid, javaArgs);
			}
			else
			{
				result = env.CallObjectMethodA(callerJavaObject, mid, javaArgs);
			}

			if (result != nullptr)
			{
				auto objectResult = ArgConverter::jstringToV8String(static_cast<jstring>(result));
				args.GetReturnValue().Set(objectResult);
				env.DeleteLocalRef(result);
			}
			else
			{
				args.GetReturnValue().Set(Null(isolate));
			}

			break;
		}
		case MethodReturnType::Object:
		{
			jobject result = nullptr;
			bool exceptionOccurred;

			if (isStatic)
			{
				result = env.CallStaticObjectMethodA(clazz, mid, javaArgs);
			}
			else if (isSuper)
			{
				result = env.CallNonvirtualObjectMethodA(callerJavaObject, clazz, mid, javaArgs);
			}
			else
			{
				result = env.CallObjectMethodA(callerJavaObject, mid, javaArgs);
			}

			if (result != nullptr)
			{
				auto isString = env.IsInstanceOf(result, JAVA_LANG_STRING);

				Local<Value> objectResult;
				if (isString)
				{
					objectResult = ArgConverter::jstringToV8String((jstring)result);
				}
				else
				{
					jint javaObjectID = objectManager->GetOrCreateObjectId(result);
					objectResult = objectManager->GetJsObjectByJavaObject(javaObjectID);

					if (objectResult.IsEmpty())
					{
						objectResult = objectManager->CreateJSWrapper(javaObjectID, *returnType, result);
					}
				}

				args.GetReturnValue().Set(objectResult);
				env.DeleteLocalRef(result);
			}
			else
			{
				args.GetReturnValue().Set(Null(isolate));
			}

			break;
		}
		default:
		{
			assert(false);
			break;
		}
	}

	static uint32_t adjustMemCount = 0;

	if ((++adjustMemCount % 2) == 0)
	{
		AdjustAmountOfExternalAllocatedMemory(env, isolate);
	}
}
void NativeScriptRuntime::CallJavaMethod(const Handle<Object>& caller, const string& className, const string& methodName, MetadataEntry *entry, bool isStatic, bool isSuper, const v8::FunctionCallbackInfo<v8::Value>& args)
{
	SET_PROFILER_FRAME();

	JEnv env;

	jclass clazz;
	jmethodID mid;
	string sig;

	if ((entry != nullptr) && entry->isResolved)
	{
		isStatic = entry->isStatic;

		if (entry->memberId == nullptr)
		{
			entry->clazz = env.FindClass(className);
			if (entry->clazz == nullptr)
			{
				MetadataNode* callerNode = MetadataNode::GetNodeFromHandle(caller);
				const string callerClassName = callerNode->GetName();

				DEBUG_WRITE("Cannot resolve class: %s while calling method: %s callerClassName: %s", className.c_str(), methodName.c_str(), callerClassName.c_str());
				clazz = env.FindClass(callerClassName);
				if (clazz == nullptr)
				{
					DEBUG_WRITE("Cannot resolve caller's class name: %s", callerClassName.c_str());
					return;
				}

				mid = isStatic ?
						env.GetStaticMethodID(clazz, methodName, entry->sig) :
						env.GetMethodID(clazz, methodName, entry->sig);

				if (mid == nullptr)
				{
					DEBUG_WRITE("Cannot resolve a method %s on caller class: %s", methodName.c_str(), callerClassName.c_str());
					return;
				}

			}
			else
			{
				entry->memberId = isStatic ?
									env.GetStaticMethodID(entry->clazz, methodName, entry->sig) :
									env.GetMethodID(entry->clazz, methodName, entry->sig);

				if (entry->memberId == nullptr)
				{
					DEBUG_WRITE("Cannot resolve a method %s on class: %s", methodName.c_str(), className.c_str());
					return;
				}
			}
		}

		if (entry->clazz != nullptr)
		{
			clazz = entry->clazz;
			mid = reinterpret_cast<jmethodID>(entry->memberId);
		}

		sig = entry->sig;
	}
	else
	{
		DEBUG_WRITE("Resolving method: %s on className %s", methodName.c_str(), className.c_str());
		MethodCache::CacheMethodInfo mi;

		clazz = env.FindClass(className);
		if (clazz != nullptr)
		{
			mi = MethodCache::ResolveMethodSignature(className, methodName, args, isStatic);
			if (mi.mid == nullptr)
			{
				DEBUG_WRITE("Cannot resolve class=%s, method=%s, isStatic=%d, isSuper=%d", className.c_str(), methodName.c_str(), isStatic, isSuper);
				return;
			}
		}
		else
		{
			MetadataNode* callerNode = MetadataNode::GetNodeFromHandle(caller);
			const string callerClassName = callerNode->GetName();
			DEBUG_WRITE("Resolving method on caller class: %s.%s on className %s", callerClassName.c_str(), methodName.c_str(), className.c_str());
			mi = MethodCache::ResolveMethodSignature(callerClassName, methodName, args, isStatic);
			if (mi.mid == nullptr)
			{
				DEBUG_WRITE("Cannot resolve class=%s, method=%s, isStatic=%d, isSuper=%d, callerClass=%s", className.c_str(), methodName.c_str(), isStatic, isSuper, callerClassName.c_str());
				return;
			}
		}


		clazz = mi.clazz;
		mid = mi.mid;
		sig = mi.signature;
	}






	if (!isStatic)
	{
		DEBUG_WRITE("CallJavaMethod called %s.%s. Instance id: %d, isSuper=%d", className.c_str(), methodName.c_str(), caller.IsEmpty() ? -42 : caller->GetIdentityHash(), isSuper);
	}
	else
	{
		DEBUG_WRITE("CallJavaMethod called %s.%s. static method", className.c_str(), methodName.c_str());
	}

	JsArgConverter argConverter(args, false, sig);

	if (!argConverter.IsValid())
	{
		JsArgConverter::Error err = argConverter.GetError();
		ExceptionUtil::GetInstance()->ThrowExceptionToJs(err.msg);
		return;
	}

	auto isolate = Isolate::GetCurrent();

	jweak callerJavaObject;

	jvalue* javaArgs = argConverter.ToArgs();

	if (!isStatic)
	{
		callerJavaObject = objectManager->GetJavaObjectByJsObject(caller);
		if(callerJavaObject == nullptr)
		{
			stringstream ss;
			ss << "No java object found on which to call \"" << methodName << "\" method. It is possible your Javascript object is not linked with the corresponding Java class. Try passing context(this) to the constructor function.";
			string exceptionMessage = ss.str();
			isolate->ThrowException(v8::Exception::ReferenceError(ConvertToV8String(exceptionMessage)));
			return;
		}
	}

	auto returnType = GetReturnType(sig);

	switch (returnType[0])
	{
		case 'V': //void
		{
			if (isStatic)
			{
				env.CallStaticVoidMethodA(clazz, mid, javaArgs);
			}
			else if (isSuper)
			{
				env.CallNonvirtualVoidMethodA(callerJavaObject, clazz, mid, javaArgs);
			}
			else
			{
				env.CallVoidMethodA(callerJavaObject, mid, javaArgs);
			}
			break;
		}
		case 'Z': //bool
		{
			jboolean result;
			if (isStatic)
			{
				result = env.CallStaticBooleanMethodA(clazz, mid, javaArgs);
			}
			else if (isSuper)
			{
				result = env.CallNonvirtualBooleanMethodA(callerJavaObject, clazz, mid, javaArgs);
			}
			else
			{
				result = env.CallBooleanMethodA(callerJavaObject, mid, javaArgs);
			}
			args.GetReturnValue().Set(result != 0 ? True(isolate) : False(isolate));
			break;
		}
		case 'B': //byte
		{
			jbyte result;
			if (isStatic)
			{
				result = env.CallStaticByteMethodA(clazz, mid, javaArgs);
			}
			else if (isSuper)
			{
				result = env.CallNonvirtualByteMethodA(callerJavaObject, clazz, mid, javaArgs);
			}
			else
			{
				result = env.CallByteMethodA(callerJavaObject, mid, javaArgs);
			}
			args.GetReturnValue().Set(result);
			break;
		}
		case 'C': //char
		{
			jchar result;
			if (isStatic)
			{
				result = env.CallStaticCharMethodA(clazz, mid, javaArgs);
			}
			else if (isSuper)
			{
				result = env.CallNonvirtualCharMethodA(callerJavaObject, clazz, mid, javaArgs);
			}
			else
			{
				result = env.CallCharMethodA(callerJavaObject, mid, javaArgs);
			}

			JniLocalRef str(env.NewString(&result, 1));
			jboolean bol = true;
			const char* resP = env.GetStringUTFChars(str, &bol);
			args.GetReturnValue().Set(ConvertToV8String(resP, 1));
			env.ReleaseStringUTFChars(str, resP);
			break;
		}
		case 'S': //short
		{
			jshort result;
			if (isStatic)
			{
				result = env.CallStaticShortMethodA(clazz, mid, javaArgs);
			}
			else if (isSuper)
			{
				result = env.CallNonvirtualShortMethodA(callerJavaObject, clazz, mid, javaArgs);
			}
			else
			{
				result = env.CallShortMethodA(callerJavaObject, mid, javaArgs);
			}
			args.GetReturnValue().Set(result);
			break;
		}
		case 'I': //int
		{
			jint result;
			if (isStatic)
			{
				result = env.CallStaticIntMethodA(clazz, mid, javaArgs);
			}
			else if (isSuper)
			{
				result = env.CallNonvirtualIntMethodA(callerJavaObject, clazz, mid, javaArgs);
			}
			else
			{
				result = env.CallIntMethodA(callerJavaObject, mid, javaArgs);
			}
			args.GetReturnValue().Set(result);
			break;

		}
		case 'J': //long
		{
			jlong result;
			if (isStatic)
			{
				result = env.CallStaticLongMethodA(clazz, mid, javaArgs);
			}
			else if (isSuper)
			{
				result = env.CallNonvirtualLongMethodA(callerJavaObject, clazz, mid, javaArgs);
			}
			else
			{
				result = env.CallLongMethodA(callerJavaObject, mid, javaArgs);
			}
			auto jsLong = ArgConverter::ConvertFromJavaLong(result);
			args.GetReturnValue().Set(jsLong);
			break;
		}
		case 'F': //float
		{
			jfloat result;
			if (isStatic)
			{
				result = env.CallStaticFloatMethodA(clazz, mid, javaArgs);
			}
			else if (isSuper)
			{
				result = env.CallNonvirtualFloatMethodA(callerJavaObject, clazz, mid, javaArgs);
			}
			else
			{
				result = env.CallFloatMethodA(callerJavaObject, mid, javaArgs);
			}
			args.GetReturnValue().Set((double) result); //TODO: handle float value here correctly.
			break;
		}
		case 'D': //double
		{
			jdouble result;
			if (isStatic)
			{
				result = env.CallStaticDoubleMethodA(clazz, mid, javaArgs);
			}
			else if (isSuper)
			{
				result = env.CallNonvirtualDoubleMethodA(callerJavaObject, clazz, mid, javaArgs);
			}
			else
			{
				result = env.CallDoubleMethodA(callerJavaObject, mid, javaArgs);
			}
			args.GetReturnValue().Set(result);
			break;

		}
		case '[':
		case 'L':
		{
			bool isString = returnType == "Ljava/lang/String;";

			jobject result = nullptr;
			bool exceptionOccurred;

			if (isString)
			{
				if (isStatic)
				{
					result = env.CallStaticObjectMethodA(clazz, mid, javaArgs);
				}
				else if (isSuper)
				{
					result = env.CallNonvirtualObjectMethodA(callerJavaObject, clazz, mid, javaArgs);
				}
				else
				{
					result = env.CallObjectMethodA(callerJavaObject, mid, javaArgs);
				}

				exceptionOccurred = env.ExceptionCheck() == JNI_TRUE;

				if (!exceptionOccurred)
				{
					auto resultV8String = ArgConverter::jstringToV8String((jstring) result);
					args.GetReturnValue().Set(resultV8String);
				}
			}
			else
			{
				if (isStatic)
				{
					result = env.CallStaticObjectMethodA(clazz, mid, javaArgs);
				}
				else if (isSuper)
				{
					result = env.CallNonvirtualObjectMethodA(callerJavaObject, clazz, mid, javaArgs);
				}
				else
				{
					result = env.CallObjectMethodA(callerJavaObject, mid, javaArgs);
				}

				exceptionOccurred = env.ExceptionCheck() == JNI_TRUE;

				if (!exceptionOccurred)
				{
					if (result != nullptr)
					{
						isString = env.IsInstanceOf(result, JAVA_LANG_STRING);

						Handle<Value> objectResult;
						if (isString)
						{
							objectResult = ArgConverter::jstringToV8String((jstring)result);
						}
						else
						{
							jint javaObjectID = objectManager->GetOrCreateObjectId(result);
							objectResult = objectManager->GetJsObjectByJavaObject(javaObjectID);

							if (objectResult.IsEmpty())
							{
								objectResult = objectManager->CreateJSWrapper(javaObjectID, returnType, result);
							}
						}

						args.GetReturnValue().Set(objectResult);
					}
					else
					{
						args.GetReturnValue().Set(Null(isolate));
					}
				}
			}

			if (!exceptionOccurred)
			{
				env.DeleteLocalRef(result);
			}

			break;
		}
		default:
		{
			// TODO:
			ASSERT_FAIL("Unknown return type");
			break;
		}
	}

	if (!ExceptionUtil::GetInstance()->CheckForJavaException(env))
	{
		AdjustAmountOfExternalAllocatedMemory(env, isolate);
	}
}