// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Threading; using Microsoft.UI.Xaml.Media; namespace Microsoft.ReactNative.Managed { public delegate void ReadValueDelegate(IJSValueReader reader, out T value); // A value can be read from IJSValueReader in one of two ways: // 1. There is a ReadValue extension for IJSValueReader interface that matches the type. // 2. We can auto-generate the read method for the type. public static class JSValueReader { private static IReadOnlyDictionary s_readerDelegates = new Dictionary(); public static void ReadValue(this IJSValueReader reader, out string value) { switch (reader.ValueType) { case JSValueType.String: value = reader.GetString(); break; case JSValueType.Boolean: value = reader.GetBoolean() ? "true" : "false"; break; case JSValueType.Int64: value = reader.GetInt64().ToString(); break; case JSValueType.Double: value = reader.GetDouble().ToString(); break; default: value = ""; break; } } public static void ReadValue(this IJSValueReader reader, out bool value) { switch (reader.ValueType) { case JSValueType.String: value = !string.IsNullOrEmpty(reader.GetString()); break; case JSValueType.Boolean: value = reader.GetBoolean(); break; case JSValueType.Int64: value = reader.GetInt64() != 0; break; case JSValueType.Double: value = reader.GetDouble() != 0; break; default: value = false; break; } } public static void ReadValue(this IJSValueReader reader, out sbyte value) { reader.ReadValue(out long val); value = (sbyte)val; } public static void ReadValue(this IJSValueReader reader, out short value) { reader.ReadValue(out long val); value = (short)val; } public static void ReadValue(this IJSValueReader reader, out int value) { reader.ReadValue(out long val); value = (int)val; } public static void ReadValue(this IJSValueReader reader, out long value) { switch (reader.ValueType) { case JSValueType.String: { if (!long.TryParse(reader.GetString(), out value)) { value = 0; } break; } case JSValueType.Boolean: value = reader.GetBoolean() ? 1 : 0; break; case JSValueType.Int64: value = reader.GetInt64(); break; case JSValueType.Double: value = (long)reader.GetDouble(); break; default: value = 0; break; } } public static void ReadValue(this IJSValueReader reader, out byte value) { reader.ReadValue(out long val); value = (byte)val; } public static void ReadValue(this IJSValueReader reader, out ushort value) { reader.ReadValue(out long val); value = (ushort)val; } public static void ReadValue(this IJSValueReader reader, out uint value) { reader.ReadValue(out long val); value = (uint)val; } public static void ReadValue(this IJSValueReader reader, out ulong value) { reader.ReadValue(out long val); value = (ulong)val; } public static void ReadValue(this IJSValueReader reader, out float value) { reader.ReadValue(out double val); value = (float)val; } public static void ReadValue(this IJSValueReader reader, out double value) { switch (reader.ValueType) { case JSValueType.String: { if (!double.TryParse(reader.GetString(), out value)) { value = 0; } break; } case JSValueType.Boolean: value = reader.GetBoolean() ? 1 : 0; break; case JSValueType.Int64: value = reader.GetInt64(); break; case JSValueType.Double: value = reader.GetDouble(); break; default: value = 0; break; } } public static void ReadValue(this IJSValueReader reader, out JSValue value) { value = JSValue.ReadFrom(reader); } public static void ReadValue(this IJSValueReader reader, out Brush value) { JSValue jsValue = JSValue.ReadFrom(reader); value = XamlHelper.BrushFrom((writer) => jsValue.WriteTo(writer)); } public static void ReadValue(this IJSValueReader reader, out Dictionary value) { value = JSValue.ReadObjectFrom(reader); } public static void ReadValue(this IJSValueReader reader, out IDictionary value) { value = JSValue.ReadObjectFrom(reader); } public static void ReadValue(this IJSValueReader reader, out ICollection> value) { value = JSValue.ReadObjectFrom(reader); } public static void ReadValue(this IJSValueReader reader, out IEnumerable> value) { value = JSValue.ReadObjectFrom(reader); } public static void ReadValue(this IJSValueReader reader, out ReadOnlyDictionary value) { value = new ReadOnlyDictionary(JSValue.ReadObjectFrom(reader)); } public static void ReadValue(this IJSValueReader reader, out IReadOnlyDictionary value) { value = new ReadOnlyDictionary(JSValue.ReadObjectFrom(reader)); } public static void ReadValue(this IJSValueReader reader, out IReadOnlyCollection> value) { value = new ReadOnlyDictionary(JSValue.ReadObjectFrom(reader)); } public static void ReadValue(this IJSValueReader reader, out List value) { value = JSValue.ReadArrayFrom(reader); } public static void ReadValue(this IJSValueReader reader, out IList value) { value = JSValue.ReadArrayFrom(reader); } public static void ReadValue(this IJSValueReader reader, out ICollection value) { value = JSValue.ReadArrayFrom(reader); } public static void ReadValue(this IJSValueReader reader, out IEnumerable value) { value = JSValue.ReadArrayFrom(reader); } public static void ReadValue(this IJSValueReader reader, out ReadOnlyCollection value) { value = new ReadOnlyCollection(JSValue.ReadArrayFrom(reader)); } public static void ReadValue(this IJSValueReader reader, out IReadOnlyList value) { value = new ReadOnlyCollection(JSValue.ReadArrayFrom(reader)); } public static void ReadValue(this IJSValueReader reader, out IReadOnlyCollection value) { value = new ReadOnlyCollection(JSValue.ReadArrayFrom(reader)); } public static void ReadValue(this IJSValueReader reader, out T? value) where T : struct { if (reader.ValueType != JSValueType.Null) { value = reader.ReadValue(); } else { value = null; } } public static void ReadValue(this IJSValueReader reader, out Dictionary value) { value = new Dictionary(); if (reader.ValueType == JSValueType.Object) { while (reader.GetNextObjectProperty(out string propertyName)) { value.Add(propertyName, reader.ReadValue()); } } } public static void ReadValue(this IJSValueReader reader, out IDictionary value) { reader.ReadValue(out Dictionary dictionary); value = dictionary; } public static void ReadValue(this IJSValueReader reader, out ICollection> value) { reader.ReadValue(out Dictionary dictionary); value = dictionary; } public static void ReadValue(this IJSValueReader reader, out IEnumerable> value) { reader.ReadValue(out Dictionary dictionary); value = dictionary; } public static void ReadValue(this IJSValueReader reader, out ReadOnlyDictionary value) { reader.ReadValue(out Dictionary dictionary); value = new ReadOnlyDictionary(dictionary); } public static void ReadValue(this IJSValueReader reader, out IReadOnlyDictionary value) { reader.ReadValue(out ReadOnlyDictionary dictionary); value = dictionary; } public static void ReadValue(this IJSValueReader reader, out IReadOnlyCollection> value) { reader.ReadValue(out ReadOnlyDictionary dictionary); value = dictionary; } public static void ReadValue(this IJSValueReader reader, out List value) { value = new List(); if (reader.ValueType == JSValueType.Array) { while (reader.GetNextArrayItem()) { value.Add(reader.ReadValue()); } } } public static void ReadValue(this IJSValueReader reader, out IList value) { ReadValue(reader, out List list); value = list; } public static void ReadValue(this IJSValueReader reader, out ICollection value) { ReadValue(reader, out List list); value = list; } public static void ReadValue(this IJSValueReader reader, out IEnumerable value) { ReadValue(reader, out List list); value = list; } public static void ReadValue(this IJSValueReader reader, out ReadOnlyCollection value) { ReadValue(reader, out List list); value = new ReadOnlyCollection(list); } public static void ReadValue(this IJSValueReader reader, out IReadOnlyList value) { ReadValue(reader, out ReadOnlyCollection collection); value = collection; } public static void ReadValue(this IJSValueReader reader, out IReadOnlyCollection value) { ReadValue(reader, out ReadOnlyCollection collection); value = collection; } public static void ReadValue(this IJSValueReader reader, out T[] value) { ReadValue(reader, out List list); value = list.ToArray(); } public static bool SkipArrayToEnd(this IJSValueReader reader) { while (reader.GetNextArrayItem()) { reader.ReadValue(); // Read and ignore the value } return true; } public static void ReadValue(this IJSValueReader reader, out Tuple value) { reader.ReadArgs(out T1 v1); value = Tuple.Create(v1); } public static void ReadValue(this IJSValueReader reader, out Tuple value) { reader.ReadArgs(out T1 v1, out T2 v2); value = Tuple.Create(v1, v2); } public static void ReadValue(this IJSValueReader reader, out Tuple value) { reader.ReadArgs(out T1 v1, out T2 v2, out T3 v3); value = Tuple.Create(v1, v2, v3); } public static void ReadValue(this IJSValueReader reader, out Tuple value) { reader.ReadArgs(out T1 v1, out T2 v2, out T3 v3, out T4 v4); value = Tuple.Create(v1, v2, v3, v4); } public static void ReadValue( this IJSValueReader reader, out Tuple value) { reader.ReadArgs(out T1 v1, out T2 v2, out T3 v3, out T4 v4, out T5 v5); value = Tuple.Create(v1, v2, v3, v4, v5); } public static void ReadValue( this IJSValueReader reader, out Tuple value) { reader.ReadArgs(out T1 v1, out T2 v2, out T3 v3, out T4 v4, out T5 v5, out T6 v6); value = Tuple.Create(v1, v2, v3, v4, v5, v6); } public static void ReadValue( this IJSValueReader reader, out Tuple value) { reader.ReadArgs(out T1 v1, out T2 v2, out T3 v3, out T4 v4, out T5 v5, out T6 v6, out T7 v7); value = Tuple.Create(v1, v2, v3, v4, v5, v6, v7); } public static bool GetNextArrayItem(this IJSValueReader reader, ref bool success) { return success = success && reader.GetNextArrayItem(); } public static void ReadArgs(this IJSValueReader reader) { bool success = reader.ValueType == JSValueType.Array; _ = success && reader.SkipArrayToEnd(); } public static void ReadArgs(this IJSValueReader reader, out T1 arg1) { bool success = reader.ValueType == JSValueType.Array; arg1 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T1); _ = success && reader.SkipArrayToEnd(); } public static void ReadArgs(this IJSValueReader reader, out T1 arg1, out T2 arg2) { bool success = reader.ValueType == JSValueType.Array; arg1 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T1); arg2 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T2); _ = success && reader.SkipArrayToEnd(); } public static void ReadArgs(this IJSValueReader reader, out T1 arg1, out T2 arg2, out T3 arg3) { bool success = reader.ValueType == JSValueType.Array; arg1 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T1); arg2 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T2); arg3 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T3); _ = success && reader.SkipArrayToEnd(); } public static void ReadArgs(this IJSValueReader reader, out T1 arg1, out T2 arg2, out T3 arg3, out T4 arg4) { bool success = reader.ValueType == JSValueType.Array; arg1 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T1); arg2 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T2); arg3 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T3); arg4 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T4); _ = success && reader.SkipArrayToEnd(); } public static void ReadArgs(this IJSValueReader reader, out T1 arg1, out T2 arg2, out T3 arg3, out T4 arg4, out T5 arg5) { bool success = reader.ValueType == JSValueType.Array; arg1 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T1); arg2 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T2); arg3 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T3); arg4 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T4); arg5 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T5); _ = success && reader.SkipArrayToEnd(); } public static void ReadArgs(this IJSValueReader reader, out T1 arg1, out T2 arg2, out T3 arg3, out T4 arg4, out T5 arg5, out T6 arg6) { bool success = reader.ValueType == JSValueType.Array; arg1 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T1); arg2 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T2); arg3 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T3); arg4 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T4); arg5 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T5); arg6 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T6); _ = success && reader.SkipArrayToEnd(); } public static void ReadArgs(this IJSValueReader reader, out T1 arg1, out T2 arg2, out T3 arg3, out T4 arg4, out T5 arg5, out T6 arg6, out T7 arg7) { bool success = reader.ValueType == JSValueType.Array; arg1 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T1); arg2 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T2); arg3 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T3); arg4 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T4); arg5 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T5); arg6 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T6); arg7 = reader.GetNextArrayItem(ref success) ? reader.ReadValue() : default(T7); _ = success && reader.SkipArrayToEnd(); } public static void ReadValue(this IJSValueReader reader, out T value) { JSValueReaderOf.ReadValue(reader, out value); } public static T ReadValue(this IJSValueReader reader) { ReadValue(reader, out T value); return value; } public static void ReadValue(this JSValue jsValue, out T value) { ReadValue(new JSValueTreeReader(jsValue), out value); } public static T ReadValue(this JSValue jsValue) { return ReadValue(new JSValueTreeReader(jsValue)); } public static ReadValueDelegate GetReadValueDelegate() { return (ReadValueDelegate)GetReadValueDelegate(typeof(T)); } public static Delegate GetReadValueDelegate(Type valueType) { // We want to generate the delegate only once even if we cannot insert it // to s_readerDelegates from the first attempt. var generatedDelegate = new Lazy( () => JSValueReaderGenerator.GenerateReadValueDelegate(valueType)); // We try to add the generated ReadValue delegate in a loop // in a lock-free thread-safe way. We do not change existing s_readerDelegates. // Instead we clone it, update the clone, then replace the s_readerDelegates with the updated clone. while (true) { var readerDelegates = s_readerDelegates; // Get the local pointer first. if (readerDelegates.TryGetValue(valueType, out Delegate readDelegate)) { return readDelegate; } // The ReadValue delegate is not found. Generate it add try to add to the dictionary atomically. var newDelegate = generatedDelegate.Value; // Generates delegate when we are here a first time. // This is a quick shortcut in case if s_readerDelegates changed while we generated the value. // It helps to avoid cloning dictionary in a number of cases. if (readerDelegates != s_readerDelegates) continue; // Clone the dictionary, update the clone, and try to replace the s_readerDelegates. var updatedReaderDelegates = new Dictionary(readerDelegates as IDictionary) { [valueType] = newDelegate }; Interlocked.CompareExchange(ref s_readerDelegates, updatedReaderDelegates, readerDelegates); } } } // This class provides constant time access to the ReadValue delegate. public static class JSValueReaderOf { private static ReadValueDelegate s_readValue = null; public static ReadValueDelegate ReadValue { get { return s_readValue ?? (s_readValue = JSValueReader.GetReadValueDelegate()); } set { s_readValue = value; } } } }