vector<string> NativeScriptRuntime::GetTypeMetadata(const string& name, int index)
{
	JEnv env;

	string canonicalName = Util::ConvertFromJniToCanonicalName(name);

	JniLocalRef className(env.NewStringUTF(canonicalName.c_str()));
	jint idx = index;

	JniLocalRef pubApi(env.CallStaticObjectMethod(PlatformClass, GET_TYPE_METADATA, (jstring) className, idx));

	jsize length = env.GetArrayLength(pubApi);

	assert(length > 0);

	vector<string> result;

	for (jsize i=0; i<length; i++)
	{
		JniLocalRef s(env.GetObjectArrayElement(pubApi, i));
		const char *pc = env.GetStringUTFChars(s, nullptr);
		result.push_back(string(pc));
		env.ReleaseStringUTFChars(s, pc);
	}

	return result;
}
std::string ArgConverter::jstringToString(jstring value)
{
	if (value == nullptr) {
		return string();
	}

	jsize utfLength;
	bool readInBuffer = ReadJStringInBuffer(value, utfLength);
	if(readInBuffer) {
		string s(charBuffer, utfLength);
		return s;
	}

	JEnv env;

	jboolean f = false;
	const char* chars = env.GetStringUTFChars(value, &f);
	string s(chars);
	env.ReleaseStringUTFChars(value, chars);

	return s;
}
void FieldAccessor::SetJavaField(Isolate *isolate, const Local<Object>& target, const Local<Value>& value, FieldCallbackData *fieldData)
{
	JEnv env;

	HandleScope handleScope(isolate);
	auto runtime = Runtime::GetRuntime(isolate);
	auto objectManager = runtime->GetObjectManager();

	JniLocalRef targetJavaObject;

	const auto& fieldTypeName = fieldData->signature;
	auto isStatic = fieldData->isStatic;

	auto isPrimitiveType = fieldTypeName.size() == 1;
	auto isFieldArray = fieldTypeName[0] == '[';

	if (fieldData->fid == nullptr)
	{
		auto fieldJniSig = isPrimitiveType
							? fieldTypeName
								:
								(isFieldArray
									? fieldTypeName
										:
										("L" + fieldTypeName + ";"));

		if (isStatic)
		{
			fieldData->clazz = env.FindClass(fieldData->declaringType);
			assert(fieldData->clazz != nullptr);
			fieldData->fid = env.GetStaticFieldID(fieldData->clazz, fieldData->name, fieldJniSig);
			assert(fieldData->fid != nullptr);
		}
		else
		{
			fieldData->clazz = env.FindClass(fieldData->declaringType);
			assert(fieldData->clazz != nullptr);
			fieldData->fid = env.GetFieldID(fieldData->clazz, fieldData->name, fieldJniSig);
			assert(fieldData->fid != nullptr);
		}
	}

	if (!isStatic)
	{
		targetJavaObject = objectManager->GetJavaObjectByJsObject(target);

		if (targetJavaObject.IsNull())
		{
			stringstream ss;
			ss << "Cannot access property '" << fieldData->name << "' because there is no corresponding Java object";
			throw NativeScriptException(ss.str());
		}
	}

	auto fieldId = fieldData->fid;
	auto clazz = fieldData->clazz;

	if (isPrimitiveType)
	{
		switch (fieldTypeName[0])
		{
			case 'Z': //bool
			{
				//TODO: validate value is a boolean before calling
				if (isStatic)
				{
					env.SetStaticBooleanField(clazz, fieldId, value->BooleanValue());
				}
				else
				{
					env.SetBooleanField(targetJavaObject, fieldId, value->BooleanValue());
				}
				break;
			}
			case 'B': //byte
			{
				//TODO: validate value is a byte before calling
				if (isStatic)
				{
					env.SetStaticByteField(clazz, fieldId, value->Int32Value());
				}
				else
				{
					env.SetByteField(targetJavaObject, fieldId, value->Int32Value());
				}
				break;
			}
			case 'C': //char
			{
				//TODO: validate value is a single char
				String::Utf8Value stringValue(value->ToString());
				JniLocalRef value(env.NewStringUTF(*stringValue));
				const char* chars = env.GetStringUTFChars(value, 0);

				if (isStatic)
				{
					env.SetStaticCharField(clazz, fieldId, chars[0]);
				}
				else
				{
					env.SetCharField(targetJavaObject, fieldId, chars[0]);
				}
				env.ReleaseStringUTFChars(value, chars);
				break;
			}
			case 'S': //short
			{
				//TODO: validate value is a short before calling
				if (isStatic)
				{
					env.SetStaticShortField(clazz, fieldId, value->Int32Value());
				}
				else
				{
					env.SetShortField(targetJavaObject, fieldId, value->Int32Value());
				}
				break;
			}
			case 'I': //int
			{
				//TODO: validate value is a int before calling
				if (isStatic)
				{
					env.SetStaticIntField(clazz, fieldId, value->Int32Value());
				}
				else
				{
					env.SetIntField(targetJavaObject, fieldId, value->Int32Value());
				}
				break;

			}
			case 'J': //long
			{
				jlong longValue = static_cast<jlong>(ArgConverter::ConvertToJavaLong(value));
				if (isStatic)
				{
					env.SetStaticLongField(clazz, fieldId, longValue);
				}
				else
				{
					env.SetLongField(targetJavaObject, fieldId, longValue);
				}
				break;
			}
			case 'F': //float
			{
				if (isStatic)
				{
					env.SetStaticFloatField(clazz, fieldId, static_cast<jfloat>(value->NumberValue()));
				}
				else
				{
					env.SetFloatField(targetJavaObject, fieldId, static_cast<jfloat>(value->NumberValue()));
				}
				break;
			}
			case 'D': //double
			{
				if (isStatic)
				{
					env.SetStaticDoubleField(clazz, fieldId, value->NumberValue());
				}
				else
				{
					env.SetDoubleField(targetJavaObject, fieldId, value->NumberValue());
				}
				break;
			}
			default:
				{
		stringstream ss;
				ss << "(InternalError): in FieldAccessor::SetJavaField: Unknown field type: '" << fieldTypeName[0] << "'";
				throw NativeScriptException(ss.str());
			}
		}
	}
	else
	{
		bool isString = fieldTypeName == "java/lang/String";
		JniLocalRef result;

		if (!value->IsNull())
		{
			if (isString)
			{
				//TODO: validate valie is a string;
				result = ConvertToJavaString(value);
			}
			else
			{
				auto objectWithHiddenID = value->ToObject();
				result = objectManager->GetJavaObjectByJsObject(objectWithHiddenID);
			}
		}

		if (isStatic)
		{
			env.SetStaticObjectField(clazz, fieldId, result);
		}
		else
		{
			env.SetObjectField(targetJavaObject, fieldId, result);
		}
	}
}
Local<Value> FieldAccessor::GetJavaField(Isolate *isolate, const Local<Object>& target, FieldCallbackData *fieldData)
{
	JEnv env;

	EscapableHandleScope handleScope(isolate);
	auto runtime = Runtime::GetRuntime(isolate);
	auto objectManager = runtime->GetObjectManager();

	Local<Value> fieldResult;

	JniLocalRef targetJavaObject;

	const auto& fieldTypeName = fieldData->signature;
	auto isStatic = fieldData->isStatic;

	auto isPrimitiveType = fieldTypeName.size() == 1;
	auto isFieldArray = fieldTypeName[0] == '[';

	if (fieldData->fid == nullptr)
	{
		auto fieldJniSig = isPrimitiveType
							? fieldTypeName
								:
								(isFieldArray
									? fieldTypeName
										:
										("L" + fieldTypeName + ";"));

		if (isStatic)
		{
			fieldData->clazz = env.FindClass(fieldData->declaringType);
			fieldData->fid = env.GetStaticFieldID(fieldData->clazz, fieldData->name, fieldJniSig);
		}
		else
		{
			fieldData->clazz = env.FindClass(fieldData->declaringType);
			fieldData->fid = env.GetFieldID(fieldData->clazz, fieldData->name, fieldJniSig);
		}
	}

	if (!isStatic)
	{
		targetJavaObject = objectManager->GetJavaObjectByJsObject(target);

		if (targetJavaObject.IsNull())
		{
			stringstream ss;
			ss << "Cannot access property '" << fieldData->name << "' because there is no corresponding Java object";
			throw NativeScriptException(ss.str());
		}
	}

	auto fieldId = fieldData->fid;
	auto clazz = fieldData->clazz;

	if (isPrimitiveType)
	{
		switch (fieldTypeName[0])
		{
			case 'Z': //bool
			{
				jboolean result;
				if (isStatic)
				{
					result = env.GetStaticBooleanField(clazz, fieldId);
				}
				else
				{
					result = env.GetBooleanField(targetJavaObject, fieldId);
				}
				fieldResult = Boolean::New(isolate, (result == JNI_TRUE));
				break;
			}
			case 'B': //byte
			{
				jbyte result;
				if (isStatic)
				{
					result = env.GetStaticByteField(clazz, fieldId);
				}
				else
				{
					result = env.GetByteField(targetJavaObject, fieldId);
				}
				fieldResult = handleScope.Escape(Int32::New(isolate, result));
				break;
			}
			case 'C': //char
			{
				jchar result;
				if (isStatic)
				{
					result = env.GetStaticCharField(clazz, fieldId);
				}
				else
				{
					result = env.GetCharField(targetJavaObject, fieldId);
				}

				JniLocalRef str(env.NewString(&result, 1));
				jboolean bol = true;
				const char* resP = env.GetStringUTFChars(str, &bol);
				fieldResult = handleScope.Escape(ConvertToV8String(resP, 1));
				env.ReleaseStringUTFChars(str, resP);
				break;
			}
			case 'S': //short
			{
				jshort result;
				if (isStatic)
				{
					result = env.GetStaticShortField(clazz, fieldId);
				}
				else
				{
					result = env.GetShortField(targetJavaObject, fieldId);
				}
				fieldResult = handleScope.Escape(Int32::New(isolate, result));
				break;
			}
			case 'I': //int
			{
				jint result;
				if (isStatic)
				{
					result = env.GetStaticIntField(clazz, fieldId);
				}
				else
				{
					result = env.GetIntField(targetJavaObject, fieldId);
				}
				fieldResult = handleScope.Escape(Int32::New(isolate, result));
				break;

			}
			case 'J': //long
			{
				jlong result;
				if (isStatic)
				{
					result = env.GetStaticLongField(clazz, fieldId);
				}
				else
				{
					result = env.GetLongField(targetJavaObject, fieldId);
				}
				fieldResult = handleScope.Escape(ArgConverter::ConvertFromJavaLong(isolate, result));
				break;
			}
			case 'F': //float
			{
				jfloat result;
				if (isStatic)
				{
					result = env.GetStaticFloatField(clazz, fieldId);
				}
				else
				{
					result = env.GetFloatField(targetJavaObject, fieldId);
				}
				fieldResult = handleScope.Escape(Number::New(isolate, (double) result));
				break;
			}
			case 'D': //double
			{
				jdouble result;
				if (isStatic)
				{
					result = env.GetStaticDoubleField(clazz, fieldId);
				}
				else
				{
					result = env.GetDoubleField(targetJavaObject, fieldId);
				}
				fieldResult = handleScope.Escape(Number::New(isolate, result));
				break;
			}
			default:
				{
					stringstream ss;
					ss << "(InternalError): in FieldAccessor::GetJavaField: Unknown field type: '" << fieldTypeName[0] << "'";
					throw NativeScriptException(ss.str());
			}
		}
	}
	else
	{
		jobject result;

		if (isStatic)
		{
			result = env.GetStaticObjectField(clazz, fieldId);
		}
		else
		{
			result = env.GetObjectField(targetJavaObject, fieldId);
		}

		if (result != nullptr)
		{

			bool isString = fieldTypeName == "java/lang/String";
			if (isString)
			{
				auto resultV8Value = ArgConverter::jstringToV8String((jstring) result);
				fieldResult = handleScope.Escape(resultV8Value);
			}
			else
			{
				int javaObjectID = objectManager->GetOrCreateObjectId(result);
				auto objectResult = objectManager->GetJsObjectByJavaObject(javaObjectID);

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

				fieldResult = handleScope.Escape(objectResult);
			}
			env.DeleteLocalRef(result);
		}
		else
		{
			fieldResult = handleScope.Escape(Null(isolate));
		}
	}
	return fieldResult;
}
void FieldAccessor::SetJavaField(const Local<Object>& target, const Local<Value>& value, FieldCallbackData *fieldData)
{
	JEnv env;

	auto isolate = Isolate::GetCurrent();
	HandleScope handleScope(isolate);

	jweak targetJavaObject;

	const auto& fieldTypeName = fieldData->signature;
	auto isStatic = fieldData->isStatic;

	auto isPrimitiveType = fieldTypeName.size() == 1;
	auto isFieldArray = fieldTypeName[0] == '[';


	if (fieldData->fid == nullptr)
	{
		auto fieldJniSig = isPrimitiveType
								? fieldTypeName
								: (isFieldArray
									? fieldTypeName
									: ("L" + fieldTypeName + ";"));

		if (isStatic)
		{
			fieldData->clazz = env.FindClass(fieldData->declaringType);
			assert(fieldData->clazz != nullptr);
			fieldData->fid = env.GetStaticFieldID(fieldData->clazz, fieldData->name, fieldJniSig);
			assert(fieldData->fid != nullptr);
		}
		else
		{
			fieldData->clazz = env.FindClass(fieldData->declaringType);
			assert(fieldData->clazz != nullptr);
			fieldData->fid = env.GetFieldID(fieldData->clazz, fieldData->name, fieldJniSig);
			assert(fieldData->fid != nullptr);
		}
	}

	if (!isStatic)
	{
		targetJavaObject = objectManager->GetJavaObjectByJsObject(target);
	}

	auto fieldId = fieldData->fid;
	auto clazz = fieldData->clazz;

	if (isPrimitiveType)
	{
		switch (fieldTypeName[0])
		{
			case 'Z': //bool
			{
				//TODO: validate value is a boolean before calling
				if (isStatic)
				{
					env.SetStaticBooleanField(clazz, fieldId, value->BooleanValue());
				}
				else
				{
					env.SetBooleanField(targetJavaObject, fieldId, value->BooleanValue());
				}
				break;
			}
			case 'B': //byte
			{
				//TODO: validate value is a byte before calling
				if (isStatic)
				{
					env.SetStaticByteField(clazz, fieldId, value->Int32Value());
				}
				else
				{
					env.SetByteField(targetJavaObject, fieldId, value->Int32Value());
				}
				break;
			}
			case 'C': //char
			{
				//TODO: validate value is a single char
				String::Utf8Value stringValue(value->ToString());
				JniLocalRef value(env.NewStringUTF(*stringValue));
				const char* chars = env.GetStringUTFChars(value, 0);

				if (isStatic)
				{
					env.SetStaticCharField(clazz, fieldId, chars[0]);
				}
				else
				{
					env.SetCharField(targetJavaObject, fieldId, chars[0]);
				}
				env.ReleaseStringUTFChars(value, chars);
				break;
			}
			case 'S': //short
			{
				//TODO: validate value is a short before calling
				if (isStatic)
				{
					env.SetStaticShortField(clazz, fieldId, value->Int32Value());
				}
				else
				{
					env.SetShortField(targetJavaObject, fieldId, value->Int32Value());
				}
				break;
			}
			case 'I': //int
			{
				//TODO: validate value is a int before calling
				if (isStatic)
				{
					env.SetStaticIntField(clazz, fieldId, value->Int32Value());
				}
				else
				{
					env.SetIntField(targetJavaObject, fieldId, value->Int32Value());
				}
				break;

			}
			case 'J': //long
			{
				jlong longValue = static_cast<jlong>(ArgConverter::ConvertToJavaLong(value));
				if (isStatic)
				{
					env.SetStaticLongField(clazz, fieldId, longValue);
				}
				else
				{
					env.SetLongField(targetJavaObject, fieldId, longValue);
				}
				break;
			}
			case 'F': //float
			{
				if (isStatic)
				{
					env.SetStaticFloatField(clazz, fieldId, static_cast<jfloat>(value->NumberValue()));
				}
				else
				{
					env.SetFloatField(targetJavaObject, fieldId, static_cast<jfloat>(value->NumberValue()));
				}
				break;
			}
			case 'D': //double
			{
				if (isStatic)
				{
					env.SetStaticDoubleField(clazz, fieldId, value->NumberValue());
				}
				else
				{
					env.SetDoubleField(targetJavaObject, fieldId, value->NumberValue());
				}
				break;
			}
			default:
			{
				// TODO:
				ASSERT_FAIL("Unknown field type");
				break;
			}
		}
	}
	else
	{
		bool isString = fieldTypeName == "java/lang/String";
		jobject result = nullptr;

		if(!value->IsNull()) {
			if (isString)
			{
				//TODO: validate valie is a string;
				result = ConvertToJavaString(value);
			}
			else
			{
				auto objectWithHiddenID = value->ToObject();
				result =objectManager->GetJavaObjectByJsObject(objectWithHiddenID);
			}
		}

		if (isStatic)
		{
			env.SetStaticObjectField(clazz, fieldId, result);
		}
		else
		{
			env.SetObjectField(targetJavaObject, fieldId, result);
		}

		if (isString)
		{
			env.DeleteLocalRef(result);
		}
	}
}
Local<Value> ArrayElementAccessor::GetArrayElement(const Local<Object>& array, uint32_t index, const string& arraySignature)
{
	JEnv env;

	Isolate* isolate = Isolate::GetCurrent();
	EscapableHandleScope handleScope(isolate);

	jweak arr = objectManager->GetJavaObjectByJsObject(array);

	Local<Value> value;
	jsize startIndex = index;
	const jsize length = 1;

	const string elementSignature = arraySignature.substr(1);
	jboolean isCopy = false;

	if (elementSignature == "Z")
	{
		jbooleanArray boolArr = reinterpret_cast<jbooleanArray>(arr);
		jboolean boolArrValue;
		env.GetBooleanArrayRegion(boolArr, startIndex, length, &boolArrValue);
		value = ConvertToJsValue(env, elementSignature, &boolArrValue);
	}
	else if (elementSignature == "B")
	{
		jbyteArray byteArr = reinterpret_cast<jbyteArray>(arr);
		jbyte byteArrValue;
		env.GetByteArrayRegion(byteArr, startIndex, length, &byteArrValue);
		value = ConvertToJsValue(env, elementSignature, &byteArrValue);
	}
	else if (elementSignature == "C")
	{
		jcharArray charArr = reinterpret_cast<jcharArray>(arr);
		jchar charArrValue;
		env.GetCharArrayRegion(charArr, startIndex, length, &charArrValue);
		JniLocalRef s(env.NewString(&charArrValue, 1));
		const char* singleChar = env.GetStringUTFChars(s, &isCopy);
		value = ConvertToJsValue(env, elementSignature, singleChar);
		env.ReleaseStringUTFChars(s, singleChar);
	}
	else if (elementSignature == "S")
	{
		jshortArray shortArr = reinterpret_cast<jshortArray>(arr);
		jshort shortArrValue;
		env.GetShortArrayRegion(shortArr, startIndex, length, &shortArrValue);
		value = ConvertToJsValue(env, elementSignature, &shortArrValue);
	}
	else if (elementSignature == "I")
	{
		jintArray intArr = reinterpret_cast<jintArray>(arr);
		jint intArrValue;
		env.GetIntArrayRegion(intArr, startIndex, length, &intArrValue);
		value = ConvertToJsValue(env, elementSignature, &intArrValue);
	}
	else if (elementSignature == "J")
	{
		jlongArray longArr = reinterpret_cast<jlongArray>(arr);
		jlong longArrValue;
		env.GetLongArrayRegion(longArr, startIndex, length, &longArrValue);
		value = ConvertToJsValue(env, elementSignature, &longArrValue);
	}
	else if (elementSignature == "F")
	{
		jfloatArray floatArr = reinterpret_cast<jfloatArray>(arr);
		jfloat floatArrValue;
		env.GetFloatArrayRegion(floatArr, startIndex, length, &floatArrValue);
		value = ConvertToJsValue(env, elementSignature, &floatArrValue);
	}
	else if (elementSignature == "D")
	{
		jdoubleArray doubleArr = reinterpret_cast<jdoubleArray>(arr);
		jdouble doubleArrValue;
		env.GetDoubleArrayRegion(doubleArr, startIndex, length, &doubleArrValue);
		value = ConvertToJsValue(env, elementSignature, &doubleArrValue);
	}
	else
	{
		jobject result = env.GetObjectArrayElement(reinterpret_cast<jobjectArray>(arr), index);
		value = ConvertToJsValue(env, elementSignature, &result);
		env.DeleteLocalRef(result);
	}

	return handleScope.Escape(value);
}
void ArrayElementAccessor::SetArrayElement(const Local<Object>& array, uint32_t index, const string& arraySignature, Local<Value>& value)
{
	JEnv env;

	Isolate* isolate = Isolate::GetCurrent();
	HandleScope handleScope(isolate);

	jweak arr = objectManager->GetJavaObjectByJsObject(array);

	const string elementSignature = arraySignature.substr(1);
	jboolean isCopy = false;

	if (elementSignature == "Z") //bool
	{
		jboolean boolElementValue = (jboolean) value->BooleanValue();
		jbooleanArray boolArr = reinterpret_cast<jbooleanArray>(arr);
		env.SetBooleanArrayRegion(boolArr, index, 1, &boolElementValue);
	}
	else if (elementSignature == "B") //byte
	{
		jbyte byteElementValue = (jbyte) value->Int32Value();
		jbyteArray byteArr = reinterpret_cast<jbyteArray>(arr);
		env.SetByteArrayRegion(byteArr, index, 1, &byteElementValue);
	}
	else if (elementSignature == "C") //char
	{
		String::Utf8Value utf8(value->ToString());
		JniLocalRef s(env.NewString((jchar*) *utf8, 1));
		const char* singleChar = env.GetStringUTFChars(s, &isCopy);
		jchar charElementValue = *singleChar;
		env.ReleaseStringUTFChars(s, singleChar);
		jcharArray charArr = reinterpret_cast<jcharArray>(arr);
		env.SetCharArrayRegion(charArr, index, 1, &charElementValue);
	}
	else if (elementSignature == "S") //short
	{
		jshort shortElementValue = (jshort) value->Int32Value();
		jshortArray shortArr = reinterpret_cast<jshortArray>(arr);
		env.SetShortArrayRegion(shortArr, index, 1, &shortElementValue);
	}
	else if (elementSignature == "I") //int
	{
		jint intElementValue = value->Int32Value();
		jintArray intArr = reinterpret_cast<jintArray>(arr);
		env.SetIntArrayRegion(intArr, index, 1, &intElementValue);
	}
	else if (elementSignature == "J") //long
	{
		jlong longElementValue;
		if (value->IsObject())
		{
			longElementValue = (jlong) ArgConverter::ConvertToJavaLong(value);
		}
		else
		{
			longElementValue = (jlong) value->IntegerValue();
		}
		jlongArray longArr = reinterpret_cast<jlongArray>(arr);
		env.SetLongArrayRegion(longArr, index, 1, &longElementValue);
	}
	else if (elementSignature == "F") //float
	{
		jfloat floatElementValue = (jfloat) value->NumberValue();
		jfloatArray floatArr = reinterpret_cast<jfloatArray>(arr);
		env.SetFloatArrayRegion(floatArr, index, 1, &floatElementValue);
	}
	else if (elementSignature == "D") //double
	{
		jdouble doubleElementValue = (jdouble) value->NumberValue();
		jdoubleArray doubleArr = reinterpret_cast<jdoubleArray>(arr);
		env.SetDoubleArrayRegion(doubleArr, index, 1, &doubleElementValue);
	}
	else //string or object
	{
		bool isReferenceType = value->IsObject() || value->IsString();
		if (isReferenceType)
		{
			auto object = value.As<Object>();

			JsArgToArrayConverter argConverter(value, false, (int)Type::Null);
			if (argConverter.IsValid())
			{
				jobjectArray objArr = reinterpret_cast<jobjectArray>(arr);
				jobject objectElementValue = argConverter.GetConvertedArg();
				env.SetObjectArrayElement(objArr, index, objectElementValue);
			}
			else
			{
				JsArgToArrayConverter::Error err = argConverter.GetError();
				throw NativeScriptException(string(err.msg));
			}
		}
		else
		{
			throw NativeScriptException(string("Cannot assign primitive value to array of objects."));
		}
	}
}
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);
	}
}