using System; using Microsoft.SPOT; using System.Collections; using System.Reflection; using IndianaJones.NETMF.String; using IndianaJones.NETMF.Time; using IndianaJones.NETMF.Integer; namespace IndianaJones.NETMF.Json { /// /// Contains a snapshot of all of the classes currently loaded by the Assembly /// and enumerates their class names, properties and property types into a Hashtable. /// The table is then referenced against a decoded Json string to determine what /// data types a given chunk of Json string represents. When a match is found, /// that class is instantiated and its properties populated with the Json data. /// public class PropertyTable { private ArrayList _properties; public PropertyTable() { Snapshot(); } public ArrayList Properties { get { return _properties; } } /// /// Takes a snapshot of the classes and their property names and types /// public void Snapshot() { if(_properties != null) { foreach(Hashtable h in _properties) { h.Clear(); } _properties = null; } // Read in all Assemblies except for the .NET MF DLLs Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); _properties = new ArrayList(); foreach(Assembly a in assemblies) { if( (a.FullName.StartsWith("System")) || (a.FullName.StartsWith("mscorlib")) || (a.FullName.StartsWith("Microsoft.SPOT"))) { continue; } Debug.Print("Adding " + a.FullName + " to the list"); Hashtable hash = GetProperties(a.GetTypes()); DumpObjects(hash, 0); _properties.Add(hash); } //_properties = DumpObjects(_assembly.GetTypes()); } /// /// Searches the Hashtable for an object that contains a property name specified by key. /// The key may be composed of colon-delimited object names representing a class heirarchy. /// /// /// public object FindObject(Hashtable jsonTable) { // Find the Hashtable that matches the specified Hashtable DictionaryEntry classDefinition = FindMatchingDictionary(jsonTable); // Now we have two hashtables: // 1) The jsonTable hashtable, which has all the property names and values // but contains no class names. // 2) The class hashtable, which contains the actual class names // and their property names but no property values. // Create an instance of the highest-level class Type theClassType = Type.GetType(classDefinition.Key.ToString()); ConstructorInfo ctor = theClassType.GetConstructor(new Type[] { }); object theClass = ctor.Invoke(null); // Iterate through all of its properties, setting its values foreach(DictionaryEntry e in classDefinition.Value as Hashtable) { SetProperty(theClass, theClassType, classDefinition.Value as Hashtable, e, jsonTable); } return theClass; } /// /// Searches the ArrayList of Hashtables generated at startup time, and returns /// the Hashtable that matches the specified Hashtable. /// /// /// private DictionaryEntry FindMatchingDictionary(Hashtable jsonTable) { DictionaryEntry tableFound = null; foreach(DictionaryEntry entry in jsonTable) { // Each "Value" in the DictionaryEntry should be a Class object, which is itself a Hashtable // They Key inside each Value is the actual Property name we're checking against foreach(Hashtable curTable in _properties) { foreach (DictionaryEntry e in curTable) { foreach(DictionaryEntry p in e.Value as Hashtable) { Debug.Print("Comparing " + entry.Key + " to " + p.Key.ToString() + " (" + e.Key.ToString() + ")"); if (entry.Key.ToString() == p.Key.ToString()) { // We could easily have matches across multiple Classes, because classes can often // contain properties with the same names (e.g. "Name", "Id", etc). However, the last // match should always contain the Class whose properties MOST matched the table, // so last guy wins. tableFound = new DictionaryEntry(e.Key.ToString(), e.Value); } } } } } return tableFound; } /// /// Sets a single Property on an object. That single Property can be a Value Type, and ArrayList, Hashtable, /// essentially anything that's legal to deserialize. This method will recurse into itself to set all of /// this Property's values, children objects and values, etc. /// /// The owner of this Property /// The Type of the owner object /// The Hashtable that contains this Property's (heirarchical) information /// The current Property Name and Type /// The entire JSON string, converted to a Hasthable private void SetProperty(object parent, Type parentType, Hashtable classDefinition, DictionaryEntry entry, Hashtable jsonTable) { string name = entry.Key.ToString(); // name of property string type = entry.Value.ToString(); // Type of property, as a string object value = GetValueFromJsonHashtable(jsonTable, entry.Key.ToString()); // If it's not a value type, recurse if(value is Hashtable) { if (type.Contains("DictionaryEntry")) { foreach(DictionaryEntry d in value as Hashtable) { MethodInfo method = parentType.GetMethod("set_" + name); method.Invoke(parent, new object[] { d }); break; } } else if(type.Contains("Hashtable")) { } else { // Find the top-level Hashtable entry for this property in properties and Json DictionaryEntry newClassDefinition = FindMatchingDictionary(value as Hashtable); //DumpObjects(newClassDefinition.Value as Hashtable, 8); // Instantiate the class, whose Type is entry.Value Type newParentType = Type.GetType(type); ConstructorInfo ctor = newParentType.GetConstructor(new Type[] { }); object newParent = ctor.Invoke(null); // Fill in all the properties for this class foreach (DictionaryEntry e in newClassDefinition.Value as Hashtable) { SetProperty(newParent, newParentType, newClassDefinition.Value as Hashtable, e, value as Hashtable); } // Assign the newly-instanced class to the parent's property of the class MethodInfo method = parentType.GetMethod("set_" + name); method.Invoke(parent, new object[] { newParent }); } } else if (type == "System.Collections.ArrayList") { MethodInfo method = parentType.GetMethod("set_" + name); method.Invoke(parent, new object[] { value }); } else if (type == "System.DateTime") { // Determine if DATE format is the ASP.NET Ajax form, or not. // "Not" means it assumes the de factor ISO 8601 standard format. DateTime dt = DateTime.MinValue; if(value.ToString().Contains("Date(")) { dt = DateTimeExtensions.FromASPNetAjax(value.ToString()); } else { dt = DateTimeExtensions.FromIso8601(value.ToString()); } MethodInfo method = parentType.GetMethod("set_" + name); method.Invoke(parent, new object[] { dt }); } else if (type == "System.Guid") { Guid g = GuidExtensions.FromString(value.ToString()); MethodInfo method = parentType.GetMethod("set_" + name); method.Invoke(parent, new object[] { g }); } else if (type == "System.Boolean") { bool b = (bool)value; MethodInfo method = parentType.GetMethod("set_" + name); method.Invoke(parent, new object[] { b }); } else { // We have a value type to set, so set it SetTypedValue(parent, parentType, name, type, value); } } /// /// Performs the type conversion that is normally standard stuff for .NET, but missing in .NET MF. /// Basically, the unTypedValue object cannot be converted or cast using boxing or a simple cast, /// except for the actual Type defined inside the object (the object's Parse method ReturnType). /// So this function discovers what the unTypedValue's inner Type really is, compares that with /// the destination Property's real Type, and performs a cast specific to those two types. /// /// The Property's parent object /// Type of the parent object /// Property Name /// Property's real type /// The object containing the Property's value, extracted from a JSON string private void SetTypedValue(object parent, Type parentType, string name, string type, object unTypedValue) { // Find out what Type the object was set to, using the Parse Method's ReturnType MethodInfo parse = unTypedValue.GetType().GetMethod("Parse"); Type typeToUnbox = typeof(System.Int64); // The JSON parser assigned the type to one of three possible Types: // 1) Double // 2) UInt64 // 3) Int64 // From these three values, any form of casting is possible into the Value Types. // Exceptions are specifically handled by the destionation Type. System.UInt64 ui64Value = 0; System.Int64 i64Value = 0; System.Double dblValue = 0; if(parse != null) { if(parse.ReturnType == typeof(System.UInt64)) { ui64Value = (System.UInt64)unTypedValue; typeToUnbox = typeof(System.UInt64); } else if(parse.ReturnType == typeof(System.Int64)) { i64Value = (System.Int64)unTypedValue; typeToUnbox = typeof(System.Int64); } else if(parse.ReturnType == typeof(System.Double)) { dblValue = (System.Double)unTypedValue; typeToUnbox = typeof(System.Double); } } // Perform the actual conversion, based on the real destination Type // and the value's inner object Type. MethodInfo method = parentType.GetMethod("set_" + name); if(type == "System.SByte") { System.SByte value = (System.SByte)(typeToUnbox == typeof(System.UInt64) ? (System.SByte)ui64Value : (typeToUnbox == typeof(System.Int64) ? (System.SByte)i64Value : unTypedValue)); method.Invoke(parent, new object[] { value }); } else if(type == "System.Byte") { System.Byte value = (System.Byte)(typeToUnbox == typeof(System.UInt64) ? (System.Byte)ui64Value : (typeToUnbox == typeof(System.Int64) ? (System.Byte)i64Value : unTypedValue)); method.Invoke(parent, new object[] { value }); } else if (type == "System.Int16") { System.Int16 value = (System.Int16)(typeToUnbox == typeof(System.UInt64) ? (System.Int16)ui64Value : (typeToUnbox == typeof(System.Int64) ? (System.Int16)i64Value : unTypedValue)); method.Invoke(parent, new object[] { value }); } else if(type == "System.UInt16") { System.UInt16 value = (System.UInt16)(typeToUnbox == typeof(System.UInt64) ? (System.UInt16)ui64Value : (typeToUnbox == typeof(System.Int64) ? (System.UInt16)i64Value : unTypedValue)); method.Invoke(parent, new object[] { value }); } else if(type == "System.Int32") { System.Int32 value = (System.Int32)(typeToUnbox == typeof(System.UInt64) ? (System.Int32)ui64Value : (typeToUnbox == typeof(System.Int64) ? (System.Int32)i64Value : unTypedValue)); //System.Int32 value = (System.Int32)i64Value; method.Invoke(parent, new object[] { value }); } else if(type == "System.UInt32") { System.UInt32 value = (System.UInt32)(typeToUnbox == typeof(System.UInt64) ? (System.UInt32)ui64Value : (typeToUnbox == typeof(System.Int64) ? (System.UInt32)i64Value : unTypedValue)); method.Invoke(parent, new object[] { value }); } else if(type == "System.Int64") { System.Int64 value = (System.Int64)(typeToUnbox == typeof(System.UInt64) ? (System.Int64)ui64Value : (typeToUnbox == typeof(System.Int64) ? (System.Int64)i64Value : unTypedValue)); method.Invoke(parent, new object[] { value }); } else if (type == "System.UInt64") { System.UInt64 value = (System.UInt64)(typeToUnbox == typeof(System.UInt64) ? (System.UInt64)ui64Value : (typeToUnbox == typeof(System.Int64) ? (System.UInt64)i64Value : unTypedValue)); method.Invoke(parent, new object[] { value }); } else if (type == "System.Single") { System.Single value = (System.Single)unTypedValue; method.Invoke(parent, new object[] { value }); } else if (type == "System.Double") { System.Double value = (System.Double)unTypedValue; method.Invoke(parent, new object[] { value }); } else { // Cross your fingers... method.Invoke(parent, new object[] { unTypedValue }); } } /// /// Returns the Value that matches the specified name. The Value can be anything: /// a value type (integer, string, etc), or another class, or a Hasthtable, /// DictionaryEntry, or ArrayList. /// /// /// /// private object GetValueFromJsonHashtable(Hashtable jsonTable, string name) { object value = null; foreach(DictionaryEntry entry in jsonTable) { if(entry.Key.ToString() == name) { return entry.Value; } } return value; } /// /// Debugging/Diagnostic tool that dumps objects and their children. /// /// /// public static void DumpObjects(Hashtable hash, int level) { foreach (DictionaryEntry d in hash) { string name = d.Key.ToString(); string value = string.Empty; string tabs = string.Empty; for (int i = 0; i < level; i++) { tabs = tabs + " "; } Debug.Print(tabs + name + " : "); if (d.Value is Hashtable) { DumpObjects(d.Value as Hashtable, level + 4); } if (d.Value is ArrayList) { DumpObjectArray(d.Value as ArrayList, level + 4); } else { Debug.Print(d.Value.ToString()); } } } private static void DumpObjectArray(ArrayList array, int level) { foreach (object o in array) { if (o is Hashtable) { DumpObjects(o as Hashtable, level + 4); } else if (o is ArrayList) { DumpObjectArray(o as ArrayList, level + 4); } else { Debug.Print(o.ToString()); } } } /// /// High-level method that enumerates all Types in the loaded assembly, /// and places their class names and properties in a hashtable. At deserialization /// time, this Hashtable (containing Property Names and Types) is compared to /// the Hashtable containing the JSON Property Names and Values, and the result /// of the two Hashtables is the desired instantiated object. /// /// /// private Hashtable GetProperties(Type[] types) { Hashtable h = new Hashtable(); foreach (Type type in types) { Hashtable entry = GetProperty(type); if (entry != null) { h.Add(type.FullName, entry); } } return h; } /* * The format of the object list is as follows: * Dictionary: * Class Name: Dictionary * Property Name: property return type * Property Name: property return type * Class Name:Dictionary * Property Name: property return type * Property Name: property return type * Property Name: property return type * Property Name: property return type * Property Name: property return type * Property Name: property return type * .... * * Note that the returned object is NOT hierarchical, unlike the objects * that it represents. This is because we do not need to know the * heirarchical relationships between objects, we only need to know what * properties a given object contains, which is the purpose of this table. * */ private Hashtable GetProperty(Type t) { //Debug.Print("----------------------------------------------"); //// Type dump //Debug.Print("Name: " + t.Name); //Debug.Print(" IsClass: " + t.IsClass); //Debug.Print(" IsArray: " + t.IsArray); //Debug.Print(" IsEnum: " + t.IsEnum); //Debug.Print(" IsAbstract: " + t.IsAbstract); //Debug.Print(""); // If it's a class, then it's something we care about if (t.IsClass) { Hashtable properties = new Hashtable(); MethodInfo[] methods = t.GetMethods(); foreach (MethodInfo method in methods) { //Debug.Print(" Name: " + method.Name); //Debug.Print(" IsVirtual: " + method.IsVirtual); //Debug.Print(" IsStatic: " + method.IsStatic); //Debug.Print(" IsPublic: " + method.IsPublic); //Debug.Print(" IsFinal: " + method.IsFinal); //Debug.Print(" IsAbstract: " + method.IsAbstract); //Debug.Print(" MemberType: " + method.MemberType); //Debug.Print(" DeclaringType: " + method.DeclaringType); //Debug.Print(" ReturnType: " + method.ReturnType); // If the Name.StartsWith "get_" and/or "set_", // and it's not Abstract && not Virtual // then it's a Property to save if ((method.Name.StartsWith("get_")) && (method.IsAbstract == false) && (method.IsVirtual == false)) { // Ignore abstract and virtual objects if ((method.IsAbstract || (method.IsVirtual) || (method.ReturnType.IsAbstract))) { continue; } // Ignore delegates and MethodInfos if ((method.ReturnType == typeof(System.Delegate)) || (method.ReturnType == typeof(System.MulticastDelegate)) || (method.ReturnType == typeof(System.Reflection.MethodInfo))) { continue; } // Ditto for DeclaringType if ((method.DeclaringType == typeof(System.Delegate)) || (method.DeclaringType == typeof(System.MulticastDelegate))) { continue; } // Don't need these types either if ((method.Name.StartsWith("System.Globalization"))) { continue; } // If the property returns a Hashtable //if (method.ReturnType == typeof(System.Collections.Hashtable)) //{ // return GetProperty(method.ReturnType); //} // If the property is another Class, return the Class name as the type, // then we'll look elsewhere in the PropertyTable for that class //if ((method.ReturnType.IsClass) && // (method.ReturnType.IsArray == false) && // (method.ReturnType.ToString().StartsWith("System.Collections") == false) && // (method.ReturnType.ToString().StartsWith("System.String") == false)) //{ // properties.Add(method.Name.Substring(4), method.Name.Substring(4)); // continue; //} // If the property returns an array //if (method.ReturnType == typeof(System.Collections.ArrayList)) //{ // return DumpObject(method.ReturnType); //} //foreach (object s in properties.Keys) //{ // Debug.Print(s.ToString()); //} Debug.Print("****************** " + method.ReturnType.ToString()); properties.Add(method.Name.Substring(4), method.ReturnType); } } return properties; } return null; //Debug.Print("----------------------------------------------"); } } }