using System.Collections.Generic; using System.Reflection; using System.IO; using System; using System.Linq; using Newtonsoft.Json; using JsonSubTypes; using System.Collections; using UnityEngine; using Newtonsoft.Json.Linq; using static CommunicationEvents; // I would go for static virtual methods, but C#9 does not allow me... // static methods cannot be overwritten -> virtual public interface IJSONsavable<T> where T : IJSONsavable<T>, new() { // stand-in for non static methods public static readonly IJSONsavable<T> Instance = new T(); public static readonly FieldInfo[] JsonSeperateFields = typeof(T) .GetFields( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ) .Where((field) => field.GetCustomAttributes().Any((attribute) => attribute.GetType() == typeof(JSONManager.JsonSeparateAttribute)) && field.FieldType.GetInterfaces().Any((inter) => inter.IsGenericType && inter.GetGenericTypeDefinition() == typeof(IJSONsavable<>))) .ToArray(); // TODO: this? public string name { get; set; } public string path { get; set; } protected static List<Directories> hierarchie = new List<Directories> { Directories.misc }; #region OverridableMethods public virtual string _IJGetName(string name) => name; public virtual List<Directories> _IJGetHierarchie(List<Directories> hierarchie_base) { hierarchie_base ??= new List<Directories>(); return hierarchie_base.Concat(hierarchie).ToList(); } public virtual bool _IJGetRawObject(out T payload, string path) => JSONManager.ReadFromJsonFile<T>(out payload, path); public virtual T _IJPreProcess(T payload) => payload; public virtual T _IJPostProcess(T payload) => payload; #endregion OverridableMethods #region MethodTemplates public bool store(List<Directories> hierarchie, string name, bool use_install_folder = false, bool overwrite = true, bool deep_store = true) => store(hierarchie, name, (T) this, use_install_folder, overwrite, deep_store); public static bool store(List<Directories> hierarchie, string name, T payload, bool use_install_folder = false, bool overwrite = true, bool deep_store = true) { var new_hierarchie = Instance._IJGetHierarchie(hierarchie); var new_name = Instance._IJGetName(name); string path = CreatePathToFile(out bool exists, new_name, "JSON", new_hierarchie, use_install_folder); if (exists && !overwrite) return false; // store fields decorated with JsonSeparateAttribute and implementing IJSONsavable<> separately if (deep_store && !store_children(hierarchie, name, payload, use_install_folder: false, overwrite, deep_store: true)) return false; // store this string path_o = payload.path; payload.path = path; var new_payload = Instance._IJPreProcess(payload); payload.path = path_o; JSONManager.WriteToJsonFile(path, new_payload); return true; } public bool store_children(List<Directories> hierarchie, string name, bool use_install_folder = false, bool overwrite = true, bool deep_store = true) => store_children(hierarchie, name, (T) this, use_install_folder, overwrite, deep_store); public static bool store_children(List<Directories> hierarchie, string name, T payload, bool use_install_folder = false, bool overwrite = true, bool deep_store = true) { var new_hierarchie = Instance._IJGetHierarchie(hierarchie); var new_name = Instance._IJGetName(name); for ((int max_i, bool success) = (0, true); max_i < JsonSeperateFields.Count(); max_i++) { var field = JsonSeperateFields[max_i]; dynamic save_me = field.GetValue(payload); // is S:IJSONsavable<S> Type interface_type = typeof(IJSONsavable<>).MakeGenericType(field.FieldType); Type[] store_args_type = new Type[] { typeof(List<Directories>), typeof(string), field.FieldType, typeof(bool), typeof(bool), typeof(bool) }; object[] store_args = new object[] { new_hierarchie, new_name, save_me, use_install_folder, overwrite, deep_store }; var method = interface_type.GetMethod("store", store_args_type); success &= (bool)method.Invoke(null, store_args); // in case of no success: delete it again if (!success) { delete_children(hierarchie, name, use_install_folder, JsonSeperateFields.Count() - max_i); return false; } } return true; } public static bool load_children(List<Directories> hierarchie, string name, ref T raw_payload, bool use_install_folder = false, bool deep_load = true, bool post_process = true) { var new_hierarchie = Instance._IJGetHierarchie(hierarchie); var new_name = Instance._IJGetName(name); bool success = true; for (int max_i = 0; max_i < JsonSeperateFields.Count(); max_i++) { var field = JsonSeperateFields[max_i]; Type interface_type = typeof(IJSONsavable<>).MakeGenericType(field.FieldType); Type[] load_args_type = new Type[] { typeof(List<Directories>), typeof(string), field.FieldType.MakeByRefType(), typeof(bool), typeof(bool), typeof(bool) }; object[] load_args = new object[] { new_hierarchie, new_name, null, use_install_folder, deep_load, post_process }; var method = interface_type.GetMethod("load", BindingFlags.Public | BindingFlags.Static, null, load_args_type, null); bool success_i = (bool)method.Invoke(null, load_args); field.SetValue(raw_payload, success_i ? load_args[2] : Activator.CreateInstance(field.FieldType)); success &= success_i; } return success; } public static bool load(List<Directories> hierarchie, string name, out T payload, bool use_install_folder = false, bool deep_load = true, bool post_process = true) { payload = default(T); bool success = true; var new_hierarchie = Instance._IJGetHierarchie(hierarchie); var new_name = Instance._IJGetName(name); string path = CreatePathToFile(out bool loadable, new_name, "JSON", new_hierarchie, use_install_folder); if (!loadable) return false; if (!Instance._IJGetRawObject(out T raw_payload, path)) return false; raw_payload.name = new_name; // load fields decorated with JsonSeparateAttribute and implementing IJSONsavable<> separately if (deep_load && !load_children(hierarchie, name, ref raw_payload, false /*use_install_folder*/)) success = false; payload = post_process ? Instance._IJPostProcess(raw_payload) : raw_payload; return success; } public static void delete_children(List<Directories> hierarchie, string name, bool use_install_folder = false, int skip_last_children = 0) { var new_hierarchie = Instance._IJGetHierarchie(hierarchie); var new_name = Instance._IJGetName(name); for(int i = 0; i < JsonSeperateFields.Count() - skip_last_children; i++) { var field = JsonSeperateFields[i]; Type interface_type = typeof(IJSONsavable<>).MakeGenericType(field.FieldType); Type[] delete_args_type = new Type[] { typeof(List<Directories>), typeof(string), typeof(bool) }; object[] delete_args = new object[] { new_hierarchie, new_name, use_install_folder }; var method = interface_type.GetMethod("delete", delete_args_type); method.Invoke(null, delete_args); } } public static bool delete(List<Directories> hierarchie, string name, bool use_install_folder = false) { var new_hierarchie = Instance._IJGetHierarchie(hierarchie); var new_name = Instance._IJGetName(name); string path = CreatePathToFile(out bool _, new_name, "JSON", new_hierarchie, use_install_folder); if (!delete(path)) return false; delete_children(hierarchie, name, use_install_folder); return true; } // does not delete children! private static bool delete(string path) { if (!File.Exists(path)) return false; File.Delete(path); return true; } // public bool delete() => delete(hierarchie, name); #endregion MethodTemplates } public class MMTURICollection { public string Point = "http://mathhub.info/MitM/core/geometry?3DGeometry?point"; public string Tuple = "http://gl.mathhub.info/MMT/LFX/Sigma?Symbols?Tuple"; public string LineType = "http://mathhub.info/MitM/core/geometry?Geometry/Common?line_type"; public string LineOf = "http://mathhub.info/MitM/core/geometry?Geometry/Common?lineOf"; public string OnLine = "http://mathhub.info/MitM/core/geometry?Geometry/Common?onLine"; public string Ded = "http://mathhub.info/MitM/Foundation?Logic?ded"; public string Eq = "http://mathhub.info/MitM/Foundation?Logic?eq"; public string Metric = "http://mathhub.info/MitM/core/geometry?Geometry/Common?metric"; public string Angle = "http://mathhub.info/MitM/core/geometry?Geometry/Common?angle_between"; public string Sketch = "http://mathhub.info/MitM/Foundation?InformalProofs?proofsketch"; public string RealLit = "http://mathhub.info/MitM/Foundation?RealLiterals?real_lit"; } public static class JSONManager { //could init the strings of MMTURIs with JSON or other settings file instead public static MMTURICollection MMTURIs = new MMTURICollection(); public class URI { public string uri; public URI(string uri) { this.uri = uri; } } [JsonConverter(typeof(JsonSubtypes), "kind")] public class MMTTerm { string kind; } public class OMA : MMTTerm { public MMTTerm applicant; public List<MMTTerm> arguments; public string kind = "OMA"; public OMA(MMTTerm applicant, List<MMTTerm> arguments) { this.applicant = applicant; this.arguments = arguments; } } public class OMS : MMTTerm { public string uri; public string kind = "OMS"; public OMS(string uri) { this.uri = uri; } } public class OMSTR : MMTTerm { [JsonProperty("float")] public string s; public string kind = "OMSTR"; public OMSTR(string s) { this.s = s; } } public class OMF : MMTTerm { [JsonProperty("float")] public float f; public string kind = "OMF"; public OMF(float f) { this.f = f; } } public class MMTDeclaration { public string label; public static MMTDeclaration FromJson(string json) { MMTDeclaration mmtDecl = JsonConvert.DeserializeObject<MMTDeclaration>(json); if (mmtDecl.label == null) mmtDecl.label = string.Empty; return mmtDecl; } public static string ToJson(MMTDeclaration mmtDecl) { if (mmtDecl.label == null) mmtDecl.label = string.Empty; string json = JsonConvert.SerializeObject(mmtDecl); return json; } } /** * MMTSymbolDeclaration: Class for facts without values, e.g. Points */ public class MMTSymbolDeclaration : MMTDeclaration { public string kind = "general"; public MMTTerm tp; public MMTTerm df; /** * Constructor used for sending new declarations to mmt */ public MMTSymbolDeclaration(string label, MMTTerm tp, MMTTerm df) { this.label = label; this.tp = tp; this.df = df; } } /** * MMTValueDeclaration: Class for facts with values, e.g. Distances or Angles */ public class MMTValueDeclaration : MMTDeclaration { public string kind = "veq"; public MMTTerm lhs; public MMTTerm valueTp; public MMTTerm value; /** * Constructor used for sending new declarations to mmt */ public MMTValueDeclaration(string label, MMTTerm lhs, MMTTerm valueTp, MMTTerm value) { this.label = label; this.lhs = lhs; this.valueTp = valueTp; this.value = value; } } [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] public sealed class JsonSeparateAttribute : Attribute { public JsonSeparateAttribute() { } } // TODO? /// <para>If there are public properties/variables that you do not want written to the file, decorate them with the [JsonIgnore] attribute.</para> /// <summary> /// Writes the given object instance to a Json file, recursively to set depth, including all members. /// <para>Object type must have a parameterless constructor.</para> /// <para>Only All properties and variables will be written to the file. These can be any type though, even other non-abstract classes.</para> /// </summary> /// <param name="filePath">The file path to write the object instance to.</param> /// <param name="objectToWrite">The object instance to write to the file.</param> /// <param name="max_depth">The depth recursion will occur. Default = 0.</param> public static bool WriteToJsonFile(string filePath, object objectToWrite, int max_depth = 0) { // This tells your serializer that multiple references are okay. var settings = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }; TextWriter writer = null; try { string payload = JsonConvert.SerializeObject(objectToWrite, settings); writer = new StreamWriter(filePath); writer.Write(payload); return true; } catch (Exception e) { Debug.LogError(e); return false; } finally { if (writer != null) writer.Close(); } } /// <summary> /// Reads an object instance from an Json file. /// <para>Object type must have a parameterless constructor.</para> /// </summary> /// <typeparam name="T">The type of object to read from the file.</typeparam> /// <param name="filePath">The file path to read the object instance from.</param> /// <returns>Returns a new instance of the object read from the Json file.</returns> public static bool ReadFromJsonFile<T>(out T payload, string filePath) where T : new() { payload = default(T); TextReader reader = null; try { reader = new StreamReader(filePath); var fileContents = reader.ReadToEnd(); payload = JsonConvert.DeserializeObject<T>(fileContents); return true; } catch (Exception e) { Debug.LogError(e); return false; } finally { if (reader != null) reader.Close(); } } }