using System; using System.IO; using System.Linq; using System.Collections.Generic; using Newtonsoft.Json; using System.Text; using CommandLine; using System.Xml; using NCalc; using System.Collections.Concurrent; namespace ClassMapGen { public class ModuleDef { public string Name { get; set; } public Dictionary Classes { get; set; } } public class MasterClassMap { public List Modules { get; set; } } class Options { [Option("source-base", Required = true)] public string SourceBase { get; set; } [Option("swig-xml-file", Required = true)] public string SwigXmlFile { get; set; } } class Program { static int Run(Options opts) { var srcBase = opts.SourceBase; if (!Directory.Exists(srcBase)) { Console.WriteLine($"ERROR: Source base directory not found: {srcBase}"); return 1; } if (!File.Exists(opts.SwigXmlFile)) { Console.WriteLine($"ERROR: SWIG xml file not found: {opts.SwigXmlFile}"); return 1; } string phpOut = Path.Combine(srcBase, "Bindings/Php/PhpClassMap.cpp"); string dotNetOut = Path.Combine(srcBase, "Managed/DotNet/Common/MgClassMap.cs"); string javaOut = Path.Combine(srcBase, "Managed/Java/org/osgeo/mapguide/ObjectFactory.java"); var phpTpl = new StringBuilder(File.ReadAllText("Data/Templates/php.txt")); var dotNetTpl = new StringBuilder(File.ReadAllText("Data/Templates/dotnet.txt")); var javaTpl = new StringBuilder(File.ReadAllText("Data/Templates/java.txt")); MasterClassMap clsMap = ReadFromSwigXml(opts.SwigXmlFile); var classMapMaster = new Dictionary(); var classMapMasterReverse = new SortedDictionary(); var addedStats = new ConcurrentDictionary(); foreach (var mod in clsMap.Modules) { foreach (var kvp in mod.Classes) { if (classMapMaster.TryAdd(kvp.Key, kvp.Value)) addedStats.AddOrUpdate(mod.Name, k => 1, (k, cv) => cv + 1); } } //Now populate reverse map foreach (var kvp in classMapMaster) { classMapMasterReverse[kvp.Value] = kvp.Key; } foreach (var kvp in addedStats) { Console.WriteLine($"{kvp.Key}: {kvp.Value} classes added"); } Console.WriteLine($"Class map has {classMapMaster.Count} classes"); var phpClassMaps = new StringBuilder(); string PHP_INDENT = " "; string DOTNET_INDENT = " "; string JAVA_INDENT = " "; //PHP foreach (var kvp in classMapMasterReverse) { phpClassMaps.AppendLine($"{PHP_INDENT}classNameMap[{kvp.Value}] = \"_p_{kvp.Key}\";"); } phpTpl.Replace("$CLASS_NAME_MAP_BODY$", phpClassMaps.ToString()); var javaClassMaps = new StringBuilder(); //Java foreach (var kvp in classMapMasterReverse) { javaClassMaps.AppendLine($"{JAVA_INDENT}classMap.put(new Integer({kvp.Value}), getSWIGCtor(\"{kvp.Key}\"));"); } javaTpl.Replace("$CLASS_NAME_MAP_BODY$", javaClassMaps.ToString()); var dotNetClassMaps = new StringBuilder(); //.net foreach (var mod in clsMap.Modules) { dotNetClassMaps.AppendLine($"{DOTNET_INDENT}if (HasAssemblyInAppDomain(\"OSGeo.MapGuide.{mod.Name}\")) {{"); foreach (var kvp in mod.Classes) { dotNetClassMaps.AppendLine($"{DOTNET_INDENT} classIdCtorMap[{kvp.Key}] = ResolveConstructor(\"OSGeo.MapGuide.{kvp.Value}\");"); } dotNetClassMaps.AppendLine($"{DOTNET_INDENT}}}"); } dotNetTpl.Replace("$CLASS_NAME_MAP_BODY$", dotNetClassMaps.ToString()); File.WriteAllText(phpOut, phpTpl.ToString()); Console.WriteLine($"Written: {phpOut}"); File.WriteAllText(dotNetOut, dotNetTpl.ToString()); Console.WriteLine($"Written: {dotNetOut}"); File.WriteAllText(javaOut, javaTpl.ToString()); Console.WriteLine($"Written: {javaOut}"); return 0; } private static MasterClassMap ReadFromSwigXml(string swigXmlFile) { var doc = new XmlDocument(); doc.Load(swigXmlFile); var constantNodes = doc.GetElementsByTagName("constant"); var modFoundation = new ModuleDef { Name = "Foundation", Classes = new Dictionary() }; var modGeometry = new ModuleDef { Name = "Geometry", Classes = new Dictionary() }; var modPlatformBase = new ModuleDef { Name = "PlatformBase", Classes = new Dictionary() }; var modMapGuideCommon = new ModuleDef { Name = "MapGuideCommon", Classes = new Dictionary() }; var modWeb = new ModuleDef { Name = "Web", Classes = new Dictionary() }; var modsByName = new Dictionary { { modFoundation.Name, modFoundation }, { modGeometry.Name, modGeometry }, { modPlatformBase.Name, modPlatformBase }, { modMapGuideCommon.Name, modMapGuideCommon }, { modWeb.Name, modWeb } }; var clsMap = new MasterClassMap { Modules = new List { modFoundation, modGeometry, modPlatformBase, modMapGuideCommon, modWeb } }; foreach (XmlNode cn in constantNodes) { var attrs = cn.FirstChild?.ChildNodes; if (attrs != null) { string name = null; string valueExpr = null; foreach (XmlNode attNode in attrs) { if (attNode.Name != "attribute") { continue; } var na = attNode.Attributes["name"]?.Value; var va = attNode.Attributes["value"]?.Value; if (na != null && na == "name" && va != null) { name = va; } if (na != null && na == "value" && va != null) { valueExpr = va; } } if (name != null && valueExpr != null) { Console.WriteLine("Checking symbol: " + name); // Name should be of the form: Module_Category_ClassName var qname = name.Split("_"); if (IsCandidateConstant(modsByName, qname, out var modName, out var uqClassName)) { // The class id value should be an arithmetic expression, evaluate it var expr = new Expression(valueExpr); var f = expr.ToLambda(); var classId = f.Invoke(); var mod = modsByName[modName]; var className = uqClassName; //Prefix "Mg" if not done already if (!className.StartsWith("Mg")) className = "Mg" + uqClassName; // Skip class ids for removed exception classes. The ids are remaining in the // original headers so we don't try to reuse them for any new classes we add to the // MapGuide API in the future if (className.EndsWith("Exception") && className != "MgException") { continue; } mod.Classes.Add(classId, className); Console.WriteLine($"Found class id ({classId}) for class: {className}"); } } } } return clsMap; } private static bool IsCandidateConstant(Dictionary modsByName, string[] qname, out string modName, out string className) { modName = ""; className = ""; bool isCandidate = false; if (qname.Length == 3) { if (modsByName.ContainsKey(qname[0])) { modName = qname[0]; className = qname[2]; isCandidate = true; } else { switch (qname[0]) { case "HttpHandler": modName = "Web"; className = qname[2]; isCandidate = true; break; case "MapGuide": modName = "MapGuideCommon"; className = qname[2]; isCandidate = true; break; } } } if (!isCandidate) { if (qname.Length == 2) { switch (qname[0]) { case "CoordinateSystem": case "Geometry": isCandidate = true; modName = "Geometry"; className = qname[1]; break; case "WebApp": isCandidate = true; modName = "Web"; className = qname[1]; break; } } } return isCandidate; } static int Main(string[] args) { int exitCode = 0; Parser.Default.ParseArguments(args) .WithParsed(opts => { exitCode = Run(opts); }); return exitCode; } } }