From a12234204b1f06c4590da6229be08f4ee6c82c5e Mon Sep 17 00:00:00 2001 From: "Adam Gerlach (CIV)" Date: Thu, 25 Jan 2024 11:09:58 -0500 Subject: [PATCH 1/7] Add Julia Support --- src/avtas/lmcp/lmcpgen/JuliaMethods.java | 588 +++++++++++++++++++++ src/avtas/lmcp/lmcpgen/LmcpGen.java | 6 + src/avtas/lmcp/lmcpgen/LmcpGenGUI.java | 2 + src/avtas/lmcp/lmcpgen/MDMReader.java | 2 +- src/avtas/lmcp/lmcpgen/gui_setup.xml | 1 + src/templates/julia.tl | 22 + src/templates/julia/AbstractLmcpMessage.jl | 261 +++++++++ src/templates/julia/JavaConnection.jl | 74 +++ src/templates/julia/LMCPFactory.jl | 127 +++++ src/templates/julia/LmcpClient.jl | 22 + src/templates/julia/LmcpServer.jl | 31 ++ src/templates/julia/Project.toml | 12 + src/templates/julia/Readme.html | 58 ++ src/templates/julia/SeriesStruct.jl | 47 ++ src/templates/julia/enum.jl | 29 + src/templates/julia/installToRegistry.sh | 6 + src/templates/julia/lmcp.jl | 29 + src/templates/julia/module.jl | 24 + src/templates/julia/precompilePack.jl | 16 + src/templates/julia/precompileXML.jl | 16 + src/templates/julia/runtests.jl | 93 ++++ src/templates/julia/test_project.toml | 4 + 22 files changed, 1469 insertions(+), 1 deletion(-) create mode 100644 src/avtas/lmcp/lmcpgen/JuliaMethods.java create mode 100644 src/templates/julia.tl create mode 100644 src/templates/julia/AbstractLmcpMessage.jl create mode 100644 src/templates/julia/JavaConnection.jl create mode 100644 src/templates/julia/LMCPFactory.jl create mode 100644 src/templates/julia/LmcpClient.jl create mode 100644 src/templates/julia/LmcpServer.jl create mode 100644 src/templates/julia/Project.toml create mode 100644 src/templates/julia/Readme.html create mode 100644 src/templates/julia/SeriesStruct.jl create mode 100644 src/templates/julia/enum.jl create mode 100755 src/templates/julia/installToRegistry.sh create mode 100644 src/templates/julia/lmcp.jl create mode 100644 src/templates/julia/module.jl create mode 100644 src/templates/julia/precompilePack.jl create mode 100644 src/templates/julia/precompileXML.jl create mode 100644 src/templates/julia/runtests.jl create mode 100644 src/templates/julia/test_project.toml diff --git a/src/avtas/lmcp/lmcpgen/JuliaMethods.java b/src/avtas/lmcp/lmcpgen/JuliaMethods.java new file mode 100644 index 0000000..95332ae --- /dev/null +++ b/src/avtas/lmcp/lmcpgen/JuliaMethods.java @@ -0,0 +1,588 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package avtas.lmcp.lmcpgen; + +import java.io.File; +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.Dictionary; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.List; +import java.util.Set; +import java.util.Vector; + +/** + * + * @author colintaylor + */ +public class JuliaMethods { + + private static class DependencyContainer { + MDMInfo mdmInfo; + StructInfo structInfo; + Set dependencies; + + DependencyContainer(MDMInfo mdmInfo, StructInfo structInfo) { + this.mdmInfo = mdmInfo; + this.structInfo = structInfo; + this.dependencies = new HashSet(); + } + + + DependencyContainer find(Vector containers, String structName, String seriesName ) { + for (DependencyContainer dc : containers) { + if (dc.structInfo.name.equals(structName) && dc.structInfo.seriesName.equals(seriesName)) { + return dc; + } + } + return null; + } + } + + private static Vector BuildDependencyGraph(MDMInfo[] infos) { + + Vector missingDependencies = new Vector(); + + //capture all possible dependencies + for (MDMInfo mi : infos) { + for (StructInfo si : mi.structs) { + missingDependencies.add(new DependencyContainer(mi, si)); + //System.out.println(si.seriesName); + } + } + + //build the dependency graph + for (DependencyContainer dc : missingDependencies) { + StructInfo si = dc.structInfo; + + if (!si.extends_name.isBlank()) { //check dependencies on interfaces + DependencyContainer extendsStruct = dc.find(missingDependencies, si.extends_name, si.extends_series); + dc.dependencies.add(extendsStruct); + //System.out.println(String.format("%s depends on %s", si.name, extendsStruct.structInfo.name)); + + } + for (FieldInfo fi : si.fields) { + if (!isTypePrimitive(fi.type) && !fi.isEnum) { //check field types + DependencyContainer fieldStruct = dc.find(missingDependencies, fi.type, fi.seriesName); + dc.dependencies.add(fieldStruct); + //System.out.println(String.format("%s depends on %s", si.name, fi.type)); + } + } + } + return missingDependencies; + } + + public static String series_dir(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + return ws + info.namespace; + } + + public static String datatype_name(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + return ws + st.name; + } + public static String enumtype_name(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + return ws + en.name; + } + + public static String classname(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + return ws + st.name; + } + + public static String typeName(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + return ws + st.name; + } + + public static String extends_check(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + return ws + (st.extends_name.isBlank() ? "LMCP.LmcpBase" : st.extends_name); + } + public static String extends_impl(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + return ws + (st.extends_name.isBlank() ? "LmcpMessage" : st.extends_name); + } + public static String extends_series(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + return ws + (st.extends_name.isBlank() ? "LMCP." : st.extends_series + "."); + } + + public static String list_imports(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + return " "; + } + + public static String lmcp_type(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + return ws + st.name; + } + public static String lmcp_type_id(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + return ws + st.id + ""; + } + + public static String full_datatype_name(MDMInfo[] infos, MDMInfo info, File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + String str = ""; + String[] tmp = info.namespace.split("/"); + for (int i = 0; i < tmp.length; i++) { + str += tmp[i] + "."; + } + return ws + str + st.name; + } + + public static String series_name(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + return ws + info.seriesName; + } + public static String series_name_id(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + return ws + info.seriesNameAsLong; + } + public static String series_version(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + return ws + info.version; + } + public static String creation_date(MDMInfo[] infos, MDMInfo info, File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + return ws + DateFormat.getDateInstance(DateFormat.FULL).format(new Date()); + } + + public static String unpack_vars(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + return ws + "\"" + info.seriesName + "\""; + } + + public static String print_vars(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + return ws + "\"" + info.seriesName + "\""; + } + + public static String enum_name(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + return ws + en.name; + } + + public static String enumType(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + return ws + en.name; + } + + public static String enum_gen_entries(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + StringBuffer buf = new StringBuffer(); + for(EnumInfo.EnumEntry entry : en.entries) { + buf.append(ws + entry.name + " = " + entry.value + "\n"); + } + return buf.toString(); + } + + public static String enum_from_string(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + StringBuffer buf = new StringBuffer(); + for(EnumInfo.EnumEntry entry : en.entries) { + buf.append(ws + "if str == \"" + entry.name + "\" return " + entry.name + " end\n"); + } + return buf.toString(); + } + + public static String enum_from_int(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + StringBuffer buf = new StringBuffer(); + for(EnumInfo.EnumEntry entry : en.entries) { + buf.append(ws + "if val == " + entry.name + " return \"" + entry.name + "\"" + " end \n"); + } + buf.append(ws + "return " + en.entries.get(0).name + "\n"); + return buf.toString(); + } + public static String testPack(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + String str = ""; + + for (MDMInfo mdmInfo : infos) { + str += ws + String.format("import LMCP.%s\n", mdmInfo.seriesName); + for (StructInfo structInfo : mdmInfo.structs) { + str += ws + String.format("tmp = LMCP.%s()\n", structInfo.seriesName + "." + structInfo.name); + str += ws + String.format("buffer = LMCP.pack_message(tmp)\n"); + str += ws + String.format("tmp2 = LMCP.unpack_message(buffer)\n"); + //str += ws + "@assert(tmp == tmp2)\n"; + } + str += "\n"; + } + return str; + } + + public static String testXML(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + String str = ""; + + for (MDMInfo mdmInfo : infos) { + str += ws + String.format("import LMCP.%s\n", mdmInfo.seriesName); + for (StructInfo structInfo : mdmInfo.structs) { + str += ws + String.format("tmp = LMCP.%s()\n", structInfo.seriesName + "." + structInfo.name); + str += ws + String.format("xml = LMCP.to_xml(tmp)\n"); + str += ws + String.format("tmp2 = LMCP.from_xml(xml)\n"); + //str += ws + "@assert(tmp == tmp2)\n"; + } + str += "\n"; + + } + return str; + } + + public static String tests(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + String str = ""; + + for (MDMInfo mdmInfo : infos) { + str += ws + String.format("import LMCP.%s\n", mdmInfo.seriesName); + for (StructInfo structInfo : mdmInfo.structs) { + //str += ws + String.format("println(\"Testing %s\")\n", structInfo.name); + str += ws + String.format("@test test_pack_unpack(LMCP.%s.%s())\n", mdmInfo.seriesName, structInfo.name); + str += ws + String.format("@test test_xml(LMCP.%s.%s())\n", mdmInfo.seriesName, structInfo.name); + } + str += "\n"; + + } + return str; + } + + public static String testClient(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + String str = ""; + + for (MDMInfo mdmInfo : infos) { + for (StructInfo structInfo : mdmInfo.structs) { + str += ws + String.format("tmp = LMCP.%s.%s()\n", mdmInfo.seriesName, structInfo.name); + str += ws + String.format("bytes = LMCP.pack_message(tmp)\n"); + str += ws + String.format("tmp = write(clientSocket, bytes)\n"); + } + } + return str; + } + + public static List getParentFields(MDMInfo[] infos, String parentName, String parentSeries) { + ArrayList ret = new ArrayList(); + List parentStructs = findAllParentStructs(infos, parentName, parentSeries); + + for (StructInfo si : parentStructs) { + for (FieldInfo fi : si.fields) { + ret.add(fi); + } + } + return ret; + } + + public static List findAllParentStructs(MDMInfo[] infos, String parentName, String parentSeries) { + ArrayList ret = new ArrayList(); + StructInfo parent = findParentStruct(infos, parentName, parentSeries); + while (parent != null) { + ret.add(0, parent); + parent = findParentStruct(infos, parent.extends_name, parent.extends_series); + } + return ret; + } + + public static StructInfo findParentStruct(MDMInfo[] infos, String parentName, String parentSeries) { + + for (MDMInfo info : infos) { + if (!info.seriesName.equalsIgnoreCase(parentSeries)) { + continue; + } + for (StructInfo si : info.structs) { + if (si.name.equalsIgnoreCase(parentName)) { + return si; + } + } + } + return null; + } + + public static String define_vars(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + String str = ""; + + + List fields = getParentFields(infos, st.extends_name, st.extends_series); + fields.addAll(Arrays.asList(st.fields)); + + for (FieldInfo f : fields) { + String name = f.name; + String type = getJuliaType(f.type); + String qualifier = f.isEnum ? f.seriesName + "." : ""; + if (f.isArray) { + //String qualifier = isTypePrimitive(f.type) ? "" : type + "Module."; + if (f.isEnum) { + str += ws + name + "::Vector{" + qualifier + type + "Module." + type + "}"; + } + else if (f.isStruct){ + type = f.seriesName + ".Abstract" + type; + str += ws + name + "::Vector{" + type + "}"; + } + else { + str += ws + name + "::Vector{" + type + "}"; + } + } + else if (f.isEnum) { + str += ws + name + "::" + qualifier + type + "Module." + type; + } + else if (f.isStruct) { + type = f.seriesName + ".Abstract" + type; + if (f.isOptional || f.defaultVal.equalsIgnoreCase("null")) { + type = "Union{" + type + ", Nothing}"; + } + str += ws + name + "::" + type; + } + else + { + str += ws + name + "::" + type; + } + str += "\n"; + } + return str; + } + + public static String check_large_arrays(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + String ret = ""; + for (FieldInfo f : st.fields) { + if (f.isLargeArray) { + ret += String.format("push!(LMCP.LARGE_ARRAY_REGISTRY, (%s, Symbol(\"%s\")))\n", st.name, f.name); + } + } + return ret; + } + + public static String doc_string_mdm(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + return info.comment; + } + + public static String doc_string_struct(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + return st.comment; + } + + public static String doc_string_enum(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + return en.comment; + } + + public static String LMCPFactoryModuleSetup(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + String str = ""; + + return str; + } + + public static String root_module_includes(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + + Vector missingDependencies = BuildDependencyGraph(infos); + + + String str = ""; + HashSet mdmInsertions = new HashSet(); + Vector mdms = new Vector(); + for (MDMInfo mdmInfo : infos) + mdms.add(mdmInfo); + +// for (MDMInfo mdmInfo : infos) { +// for (String dep: mdmInfo.mdmDependencies) { +// System.out.println(String.format("%s %s", mdmInfo.seriesName, dep)); +// } +// } + + + while (!mdms.isEmpty()) { + //loop through MDMs + for (MDMInfo mdmInfo : mdms) { + if (mdmInsertions.containsAll(mdmInfo.mdmDependencies)) { + //str += "# " + mdmInfo.seriesName + "\n"; + //String module = ""; +// for (EnumInfo f : mdmInfo.enums) { //knock out enums. These have no dependencies +// //Enums have no dependencies. +// module = String.format("\"%s/%sModule.jl\"", mdmInfo.namespace, f.name); +// String newInclude = String.format("include(%s)\n", module); +// str += newInclude; +// } + str += String.format("include(\"%s/%s.jl\")\n", mdmInfo.namespace, mdmInfo.seriesName); + + //get all structs in this MDM + Vector mdmStructs = new Vector(); + for (DependencyContainer dc : missingDependencies) { + if (dc.mdmInfo.equals(mdmInfo)) { + mdmStructs.add(dc); + } + } + + //unroll + while (!mdmStructs.isEmpty()) { + int startCount = mdmStructs.size(); + for (DependencyContainer dc : mdmStructs) { + if (dc.dependencies.isEmpty()) { + String dependentStruct = String.format("\"%s/%s.jl\"", dc.mdmInfo.namespace, dc.structInfo.name); + String dependentInclude = String.format("include(%s)\n", dependentStruct); + //str += dependentInclude; + + mdmStructs.remove(dc); + for(DependencyContainer innerDc : missingDependencies) { + innerDc.dependencies.remove(dc); + } + break; + } + } + if (startCount == mdmStructs.size()) { + String desc = ""; + for (DependencyContainer dc : mdmStructs) { + for (DependencyContainer innerDc : dc.dependencies) { + System.out.print(String.format("ERROR: Dep %s not found for %s\n", innerDc.structInfo.name, dc.structInfo.name)); + } + } + break; + } + } + mdmInsertions.add(mdmInfo.seriesName); + mdms.remove(mdmInfo); + break; + } + + } + + } + + //System.out.println(str); + + return str; + } + public static String module_includes(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + + String str = ""; + for (EnumInfo f : info.enums) { //knock out enums. These have no dependencies + //Enums have no dependencies. + String newInclude = String.format("include(\"%sModule.jl\")\n", f.name); + str += newInclude; + } + + Vector missingDependencies = BuildDependencyGraph(new MDMInfo[]{info}); + + while (!missingDependencies.isEmpty()) { + + DependencyContainer justRemoved = null; + for (DependencyContainer dc : missingDependencies) { + if (dc.dependencies.isEmpty()) { + str += String.format("include(\"%s.jl\")\n", dc.structInfo.name); + justRemoved = dc; + break; + } + } + missingDependencies.remove(justRemoved); + for (DependencyContainer dc : missingDependencies) { + dc.dependencies.remove(justRemoved); + } + } + + return str; + } + + public static String struct_imports(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + + //imports for single struct + + HashSet deps = new HashSet(); + for (FieldInfo fi : st.fields) { + deps.add(fi.seriesName); + } + deps.add(st.extends_series); + deps.remove(st.seriesName); //redundant + + String ret = ""; + for (String s : deps) { + ret += String.format("import ..%s\n", s); + } + return ret; + } + + public static String define_defaults(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + String str = ws; + + List fields = getParentFields(infos, st.extends_name, st.extends_series); + fields.addAll(Arrays.asList(st.fields)); + + if (fields.isEmpty()) { + return str; + } + + for (FieldInfo f : fields) { + String type = getJuliaType(f.type); + String qualifier = f.isEnum ? f.seriesName + "." : ""; + if (!f.isArray) { + if (f.type.equalsIgnoreCase("string")) { + str += f.defaultVal.isBlank() ? "\"\"," : "\"" + f.defaultVal + "\", "; + } + else if (f.isStruct) { + str += f.defaultVal.equalsIgnoreCase("null") || f.isOptional ? "nothing, " : f.seriesName + "." + f.type + "(),"; + } + else if (f.isEnum) { + str += qualifier + f.type + "Module." + f.defaultVal + ", "; + } + else { + String defaultVal = f.defaultVal; + if (defaultVal.isBlank()) { + defaultVal = getDefaultValue(f.type); + } + str += defaultVal + ", "; + } + + // variable length array + } else { + String qualifiedName = type; + if (f.isEnum) { + qualifiedName = qualifier + type + "Module." + type; + } + else if (f.isStruct) { + qualifiedName = f.seriesName + "." + type; + } + if (f.length < 0) { + str += "Vector{" + qualifiedName + "}(), "; + } else { + String initializer = isTypePrimitive(f.type) ? "0" : ""; + str += String.format("[%s(%s) for x in 1:%s], ", qualifiedName, initializer, f.length); + //str += "Vector{" + qualifiedName + "}(undef, "+ f.length + "), "; + } + } + } + str = String.format("%s() = %s(%s)", st.name, st.name, str); + return str; + } + + private static boolean isTypePrimitive(String type) { + return type.toLowerCase().matches("(bool)|(string)|(byte)|(char)|(real64)|(real32)|(int64)|(int32)|(int16)|(uint32)|(uint16)"); + } + + private static String getDefaultValue(String type) { + if (type.toLowerCase().equals("bool")) { + return "false"; + } + if (type.toLowerCase().equals("string")) { + return "\"\""; + } + else { + return "0"; + } + } + + private static String getJuliaType(String type) { + + if (type.toLowerCase().equals("bool")) { + return "Bool"; + } + if (type.toLowerCase().equals("string")) { + return "String"; + } + if (type.toLowerCase().equals("byte")) { + return "UInt8"; + } + if (type.toLowerCase().equals("char")) { + return "Char"; + } + if (type.toLowerCase().equals("real64")) { + return "Float64"; + } + if (type.toLowerCase().equals("real32")) { + return "Float32"; + } + if (type.toLowerCase().equals("int64")) { + return "Int64"; + } + if (type.toLowerCase().equals("int32")) { + return "Int32"; + } + if (type.toLowerCase().equals("int16")) { + return "Int16"; + } + if (type.toLowerCase().equals("uint32")) { + return "UInt32"; + } + if (type.toLowerCase().equals("uint16")) { + return "UInt16"; + } + if (type.equalsIgnoreCase(MDMInfo.LMCP_OBJECT_NAME)) { + return "LMCPObject"; + } + return type; + } +} diff --git a/src/avtas/lmcp/lmcpgen/LmcpGen.java b/src/avtas/lmcp/lmcpgen/LmcpGen.java index a6c5637..c93304e 100644 --- a/src/avtas/lmcp/lmcpgen/LmcpGen.java +++ b/src/avtas/lmcp/lmcpgen/LmcpGen.java @@ -330,6 +330,11 @@ else if (args[i].equalsIgnoreCase("-ada")) { methodClassName = "avtas.lmcp.lmcpgen.AdaMethods"; i -= 1; } + else if (args[i].equalsIgnoreCase("-jl")) { + template = LmcpGen.class.getResource("/templates/julia.tl"); + methodClassName = "avtas.lmcp.lmcpgen.JuliaMethods"; + i -= 1; + } else if (args[i].equalsIgnoreCase("-xsd")) { template = LmcpGen.class.getResource("/templates/xsd.tl"); methodClassName = "avtas.lmcp.lmcpgen.XsdMethods"; @@ -407,6 +412,7 @@ private static String getHelpString() { buf.append("-cs Adds proper template and method name for C# output.\n\n"); buf.append("-py Adds proper template and method name for Python output.\n\n"); buf.append("-aadl Adds proper template and method name for AADL output.\n\n"); + buf.append("-jl Adds proper template and method name for Julia output.\n\n"); buf.append("-xsd Adds proper template and method name for XML schema output.\n\n"); buf.append("-doc Adds proper template and method name for documentation output.\n\n"); buf.append("-dir path to the directory where files are to be written\n"); diff --git a/src/avtas/lmcp/lmcpgen/LmcpGenGUI.java b/src/avtas/lmcp/lmcpgen/LmcpGenGUI.java index 524d507..55bf15e 100644 --- a/src/avtas/lmcp/lmcpgen/LmcpGenGUI.java +++ b/src/avtas/lmcp/lmcpgen/LmcpGenGUI.java @@ -84,6 +84,8 @@ public class LmcpGenGUI { JCheckBox htmlBox = new JCheckBox("Docs"); JCheckBox pythonBox = new JCheckBox("Python"); JCheckBox adaBox = new JCheckBox("Ada"); + JCheckBox juliaBox = new JCheckBox("Julia"); + JCheckBox customBox = new JCheckBox("Custom Language"); MDMInfo[] infos = null; File outputDir = null; diff --git a/src/avtas/lmcp/lmcpgen/MDMReader.java b/src/avtas/lmcp/lmcpgen/MDMReader.java index eb516e1..2b2c1a2 100644 --- a/src/avtas/lmcp/lmcpgen/MDMReader.java +++ b/src/avtas/lmcp/lmcpgen/MDMReader.java @@ -350,7 +350,7 @@ else if (isEnum(f.type, f.seriesName, infos)) { for (StructInfo s : info.structs) { info.mdmDependencies.add(s.extends_series); for (int i = 0; i < s.fields.length; i++) { - if (s.fields[i].isStruct) { + if (s.fields[i].isStruct || s.fields[i].isEnum) { info.mdmDependencies.add(s.fields[i].seriesName); } } diff --git a/src/avtas/lmcp/lmcpgen/gui_setup.xml b/src/avtas/lmcp/lmcpgen/gui_setup.xml index 408c7a7..529e962 100644 --- a/src/avtas/lmcp/lmcpgen/gui_setup.xml +++ b/src/avtas/lmcp/lmcpgen/gui_setup.xml @@ -6,6 +6,7 @@ + diff --git a/src/templates/julia.tl b/src/templates/julia.tl new file mode 100644 index 0000000..2d75a0d --- /dev/null +++ b/src/templates/julia.tl @@ -0,0 +1,22 @@ + +# Julia template listing + + + +ONCE julia/lmcp.jl LMCP/src/LMCP.jl +ONCE julia/Project.toml LMCP/Project.toml +ONCE julia/AbstractLmcpMessage.jl LMCP/src/AbstractLmcpMessage.jl +ONCE julia/LMCPFactory.jl LMCP/src/LMCPFactory.jl +ONCE julia/precompilePack.jl LMCP/src/precompilePack.jl +ONCE julia/precompileXML.jl LMCP/src/precompileXML.jl +ONCE julia/LmcpServer.jl LMCP/test/LmcpServer.jl +ONCE julia/LmcpClient.jl LMCP/test/LmcpClient.jl +ONCE julia/JavaConnection.jl LMCP/test/JavaConnection.jl +ONCE julia/Readme.html LMCP/JuliaReadme.html +ONCE julia/installToRegistry.sh installToRegistry.sh +ONCE julia/runtests.jl LMCP/test/runtests.jl +ONCE julia/test_project.toml LMCP/test/Project.toml + +PER_STRUCT julia/SeriesStruct.jl LMCP/src/--/--.jl +PER_ENUM julia/enum.jl LMCP/src/--/--Module.jl +PER_MDM julia/module.jl LMCP/src/--/--.jl diff --git a/src/templates/julia/AbstractLmcpMessage.jl b/src/templates/julia/AbstractLmcpMessage.jl new file mode 100644 index 0000000..ea221df --- /dev/null +++ b/src/templates/julia/AbstractLmcpMessage.jl @@ -0,0 +1,261 @@ +## =============================================================================== +## Authors: AFRL/RQQA +## Organization: Air Force Research Laboratory, Aerospace Systems Directorate, Power and Control Division +## +## Copyright (c) 2017 Government of the United State of America, as represented by +## the Secretary of the Air Force. No copyright is claimed in the United States under +## Title 17, U.S. Code. All Other Rights Reserved. +## =============================================================================== + +## This file was auto-created by LmcpGen. Modifications will be overwritten. + +abstract type AbstractLmcpMessage end + +function ==(a::T, b::T) where {T <: AbstractLmcpMessage} + names = fieldnames(T) + for name ∈ names + getfield(a, name) == getfield(b, name) ? nothing : return false + end + return true +end + + +full_lmcp_type_name(::Type{T}) where T <: AbstractLmcpMessage = "Lmcp" +full_lmcp_type_name(::T) where T <: AbstractLmcpMessage = full_lmcp_type_name(T) + +lmcp_type(::Type{T}) where T <: AbstractLmcpMessage = "abstract" +lmcp_type(::T) where T <: AbstractLmcpMessage = lmcp_type(T) + +lmcp_type_id(::Type{T}) where T <: AbstractLmcpMessage = UInt32(0) +lmcp_type_id(::T) where T <: AbstractLmcpMessage = lmcp_type_id(T) + +series_name(::Type{T}) where T <: AbstractLmcpMessage = "ABSTRACT" +series_name(::T) where T <: AbstractLmcpMessage = series_name(T) + +series_name_id(::Type{T}) where T <: AbstractLmcpMessage = Int64(0) +series_name_id(::T) where T <: AbstractLmcpMessage = series_name_id(T) + +series_version(::Type{T}) where T <: AbstractLmcpMessage = UInt16(0) +series_version(::T) where T <: AbstractLmcpMessage = series_version(T) + + + +function _write(io::IO, o) + write(io, hton(o)) +end + +function _write(io::IO, o::Enum) + _write(io, Int32(o)) +end + +function _write(io::IO, ::Nothing) + _write(io, false) +end + +function _write(io::IO, o::String) + _write(io, UInt16(length(o))) + #length + write(io, o) #contents +end + +function _write(io::IO, o::Vector, ltype::Type{T}=UInt16) where T<:Union{UInt16, UInt32} + bytes = _write(io, T(length(o))) + for i in o + bytes += _write(io, i) + end + return bytes +end + +_write(io::IO, o::AbstractLmcpMessage) = write(io, o) #Forward to high level write +function write(io::IO, o::T) where T <: AbstractLmcpMessage + + #write header + bytes = _write(io, true) + bytes += _write(io, series_name_id(T)) + bytes += _write(io, lmcp_type_id(T)) + bytes += _write(io, series_version(T)) + + # loop through all fields and write + for field in fieldnames(T) + if (T, field) in LARGE_ARRAY_REGISTRY + bytes += _write(io, getfield(o, field), UInt32) + continue + end + bytes += _write(io, getfield(o, field)) + end + + return bytes +end + + +function _read(io::IO, o) + ntoh(read(io, o)) +end + +function _read(io::IO, ::Type{String}) + l = _read(io, UInt16) + String(read(io, l)) +end + +function _read(io::IO, ::Type{Vector{T}}, ltype::Type{T2}=UInt16) where {T, T2<:Union{UInt16, UInt32}} + l = _read(io, T2) + vec = T[] + sizehint!(vec, l) + for i ∈ 1:l + push!(vec, _read(io, T)) + end + return vec +end + +function _read(io::IO, ::Type{T}) where T <: Enum + T(_read(io, Int32)) # read as Int32 then conveert to Enum type T +end + +read(io::IO, t::Type{AbstractLmcpMessage}) = _read(io, t) #Forward to high level read +function _read(io::IO, ::Type{Union{T, Nothing}}) where T <: AbstractLmcpMessage + + # check if exists + exists = _read(io, Bool) + if !exists return nothing end + + series_name = _read(io, Int64) + object_type = _read(io, UInt32) + series_version = _read(io, UInt16) + + T2 = OBJECT_ID_REGISTRY[(series_name, object_type)] + + _read(io, T2; skip_header = true) # skip header for message since already read +end + + +function _read(io::IO, ::Type{T}; skip_header = false) where T <: AbstractLmcpMessage + type = T + if !skip_header + exists = _read(io, Bool) + series_name = _read(io, Int64) + object_type = _read(io, UInt32) + series_version = _read(io, UInt16) + type = OBJECT_ID_REGISTRY[(series_name, object_type)] + end + + values = map(fieldnames(type), fieldtypes(type)) do n, t + if (type, n) in LARGE_ARRAY_REGISTRY + v = _read(io, t, UInt32) + return v + end + v = _read(io, t) + v + end + + type(values...) +end + + +function _toLmcpType(t::Type) + if t == Bool return "bool" end + if t == String return "string" end + if t == UInt8 return "byte" end + if t == Char return "char"end + if t == Float64 return "real64" end + if t == Float32 return "float32" end + if t == Int64 return "int64" end + if t == Int32 return "int32" end + if t == Int16 return "int16" end + if t == UInt32 return "uint32" end + if t == UInt16 return "uint16" end + if t <: AbstractLmcpMessage return lmcp_type(t) end + return "LMCPObject" +end + +function _to_xml(xroot::LightXML.AbstractXMLNode, o) + LightXML.add_text(xroot, string(o)) + return xroot +end + +function _to_xml(xroot::LightXML.AbstractXMLNode, o::Type{T}) where T <: Enum + LightXML.add_text(xroot, string(Symbol(o))) +end + +function _to_xml(xroot::LightXML.AbstractXMLNode, vector::Vector) + for v in vector + tmpChild = LightXML.new_child(xroot, _toLmcpType(typeof(v))) + if typeof(v) <: AbstractLmcpMessage + _to_xml(tmpChild, v) + else + LightXML.add_text(tmpChild, string(v)) + end + end + return xroot +end + +function _to_xml(xroot::LightXML.AbstractXMLNode, o::Nothing) + return xroot +end + + +function _to_xml(xroot::LightXML.AbstractXMLNode, o::T) where T <: AbstractLmcpMessage + LightXML.set_attribute(xroot, "Series", series_name(T)) + type = typeof(o) + for (n, t) in zip(fieldnames(type), fieldtypes(type)) + value = getproperty(o, n) + if isnothing(value) continue end + child = LightXML.new_child(xroot, string(n)) + if t <: AbstractLmcpMessage || Nothing <: t #check if inner LMCP object + child = LightXML.new_child(child, lmcp_type(value)) + end + _to_xml(child, getproperty(o, n)) + end + return xroot +end + + +function _from_xml(xroot::LightXML.AbstractXMLNode, o::Type{T}) where T <: Number + text = LightXML.content(xroot) + return parse(T, text) +end + +function _from_xml(xroot::LightXML.AbstractXMLNode, ::Type{String}) + text = LightXML.content(xroot) + return text +end + +function _from_xml(xroot::LightXML.AbstractXMLNode, ::Type{Vector{T}}) where T + vec = T[] + memberName = LightXML.child_elements(xroot) + for subChild in memberName + push!(vec, _from_xml(subChild, T)) + end + return vec +end + +function _from_xml(xroot::LightXML.AbstractXMLNode, ::Type{T}) where T <: Enum + text = LightXML.content(xroot) + return T(text) +end + +function _from_xml(xroot::LightXML.AbstractXMLNode, o::Type{T}) where T <: AbstractLmcpMessage + lmcpNode = LightXML.has_attribute(xroot, "Series") ? xroot : collect(LightXML.child_elements(xroot))[1] + name = LightXML.name(lmcpNode) + series = LightXML.attribute(lmcpNode, "Series", required=true) + obj = TEXT_CREATION_REGISTRY[(series, name)]() + return _from_xml(lmcpNode, obj) +end + +function _from_xml(xroot::LightXML.AbstractXMLNode, o::Type{Union{T, Nothing}}) where T <: AbstractLmcpMessage + lmcpNode = LightXML.has_attribute(xroot, "Series") ? xroot : collect(LightXML.child_elements(xroot))[1] + name = LightXML.name(lmcpNode) + series = LightXML.attribute(lmcpNode, "Series", required=true) + + obj = TEXT_CREATION_REGISTRY[(series, name)]() + return _from_xml(lmcpNode, obj) +end + +function _from_xml(xroot::LightXML.AbstractXMLNode, o::AbstractLmcpMessage) + type = typeof(o) + for (n, t) in zip(fieldnames(type), fieldtypes(type)) + children = xroot[string(n)] + if length(children) == 0 continue end + value = _from_xml(children[1], t) + setproperty!(o, n, value) + end + return o +end diff --git a/src/templates/julia/JavaConnection.jl b/src/templates/julia/JavaConnection.jl new file mode 100644 index 0000000..829bfdf --- /dev/null +++ b/src/templates/julia/JavaConnection.jl @@ -0,0 +1,74 @@ +## =============================================================================== +## Authors: AFRL/RQQA +## Organization: Air Force Research Laboratory, Aerospace Systems Directorate, Power and Control Division +## +## Copyright (c) 2017 Government of the United State of America, as represented by +## the Secretary of the Air Force. No copyright is claimed in the United States under +## Title 17, U.S. Code. All Other Rights Reserved. +## =============================================================================== + +## This file was auto-created by LmcpGen. Modifications will be overwritten. + +#example using the JAVA style sentinal strings. + +#import ZMQ +import Sockets +#import Pkg +#Pkg.activate(".") +import LMCP + +port = 5555 + +function serialize(o::lmcp.LmcpBaseInterface) + + attributes = lmcp.FULL_LMCP_TYPE_NAME(o) * "\$lmcp|" * lmcp.FULL_LMCP_TYPE_NAME(o) * "||0|0\$" + smsg = LMCP.pack_message(o) + + sentinel = "+=+=+=+=" + sentinel *= string(length(attributes) + length(smsg)) + sentinel *= "#@#@#@#@" + addressedPayload = Vector{UInt8}(attributes) + append!(addressedPayload, smsg) + + # sentinelized checksum + val::UInt32 = 0 + for byte in addressedPayload + val += byte + end + + footer = "!%!%!%!%" * string(val) * "?^?^?^?^" + + totalmsg = Vector{UInt8}(transcode(UInt8, sentinel)) + append!(totalmsg, addressedPayload) + append!(totalmsg, transcode(UInt8, footer)) + + return totalmsg +end + +function deserialize(bytes) + stringOutput = transcode(String, bytes) + tokens = split(stringOutput, "\$") + address = tokens[1] + attributes = tokens[2] + msg = join(tokens[3:length(tokens)], "\$") #SOMETIMES '$' is valid in the data. Just reconstruct everything back. + lmcpMessage = LMCP.unpack_message(msg) + return lmcpMessage +end + +function send(socket, obj) + bytes = serialize(obj) + write(socket, bytes) +end + +function run() + sock = Sockets.connect(port) + + while isopen(sock) + bytes = readavailable(sock) + lmcpMessage = deserialize(bytes) + println(LMCP.to_xml(lmcpMessage)) + end +end + +run() +#client diff --git a/src/templates/julia/LMCPFactory.jl b/src/templates/julia/LMCPFactory.jl new file mode 100644 index 0000000..f92c758 --- /dev/null +++ b/src/templates/julia/LMCPFactory.jl @@ -0,0 +1,127 @@ +## =============================================================================== +## Authors: AFRL/RQQA +## Organization: Air Force Research Laboratory, Aerospace Systems Directorate, Power and Control Division +## +## Copyright (c) 2017 Government of the United State of America, as represented by +## the Secretary of the Air Force. No copyright is claimed in the United States under +## Title 17, U.S. Code. All Other Rights Reserved. +## =============================================================================== + +## This file was auto-created by LmcpGen. Modifications will be overwritten. + + +const LMCP_CONTROL_SEQUENCE = 0x4c4d4350 + +const OBJECT_ID_REGISTRY = Dict{Tuple{Int64, UInt32}, DataType}() +const TEXT_CREATION_REGISTRY = Dict{Tuple{String, String}, DataType}() + +const LARGE_ARRAY_REGISTRY = Set{Tuple{DataType, Symbol}}() + +function calculate_checksum(bytes::Vector{UInt8}) + checksum::UInt64 = 0 + for byte in bytes + checksum += byte + end + return UInt32(checksum) +end + + +function pack_message(o::AbstractLmcpMessage) + + buffer = IOBuffer() + write(buffer, hton(LMCP_CONTROL_SEQUENCE)) + + innerBuffer = IOBuffer() + write(innerBuffer, o) + write(buffer, hton(UInt32(innerBuffer.size))) + write(buffer, innerBuffer.data[1:innerBuffer.size]) + + #checksum + checksum = calculate_checksum(buffer.data[1:buffer.size]) + write(buffer, hton(checksum)) + + + return buffer.data[1:buffer.size] +end + +function validate(bytes::Vector{UInt8}) + #retrieves the checksum value in BIG_ENDIAN order + cs::UInt64 = 0 + len = length(bytes) + cs |= (bytes[len-3]) + cs <<= 8 + cs |= (bytes[len-2]) + cs <<= 8 + cs |= (bytes[len-1]) + cs <<= 8 + cs |= (bytes[len-0]) + + return (cs == 0) || (calculate_checksum(bytes[1:length(bytes) - 4]) == cs); +end + +function unpack_message(buffer::IOBuffer) + controlSequence = ntoh(read(buffer, Int32)) + length = ntoh(read(buffer, UInt32)) + + message = read(buffer, length) + checksum = ntoh(read(buffer, UInt32)) + + messageBuffer = IOBuffer(message) + + return read(messageBuffer, AbstractLmcpMessage) +end + +function unpack_message(bytes::Vector{UInt8}) + buffer = IOBuffer(bytes) + return unpack_message(buffer) +end + +function unpack_message(str::String) + buffer = IOBuffer(str) + return unpack_message(buffer) +end + +function unpack_messages(bytes::Vector{UInt8}) + buffer = IOBuffer(bytes) + messages = Vector{LmcpBaseInterface}() + while bytesavailable(buffer) > 0 + push!(messages, unpack_message(buffer)) + end + return messages +end + +function unpack_messages(msgs::String) + bytes = Vector{UInt8}(transcode(UInt8, msgs)) + return unpack_messages(bytes) +end + +function to_xml(o::AbstractLmcpMessage) + xdoc = LightXML.XMLDocument() + xroot = LightXML.create_root(xdoc, lmcp_type(o)) + xroot = _to_xml(xroot, o) + ret = string(xdoc) + LightXML.free(xdoc) + return ret +end + +function from_xml(xroot::LightXML.XMLElement) + name = LightXML.name(xroot) + series = LightXML.attribute(xroot, "Series", required=true) + + obj = TEXT_CREATION_REGISTRY[(series, name)]() + _from_xml(xroot, obj) + +end + +function from_xml(root::String) + if isempty(root) + return nothing + end + xdoc = LightXML.parse_string(root) + xroot = LightXML.root(xdoc) + + ret = from_xml(xroot) + + LightXML.free(xdoc) + return ret +end diff --git a/src/templates/julia/LmcpClient.jl b/src/templates/julia/LmcpClient.jl new file mode 100644 index 0000000..0dbc8fe --- /dev/null +++ b/src/templates/julia/LmcpClient.jl @@ -0,0 +1,22 @@ +## =============================================================================== +## Authors: AFRL/RQQA +## Organization: Air Force Research Laboratory, Aerospace Systems Directorate, Power and Control Division +## +## Copyright (c) 2017 Government of the United State of America, as represented by +## the Secretary of the Air Force. No copyright is claimed in the United States under +## Title 17, U.S. Code. All Other Rights Reserved. +## =============================================================================== + +## This file was auto-created by LmcpGen. Modifications will be overwritten. + +import Sockets +import Pkg +import LMCP + +port = 11041 + +clientSocket = Sockets.connect(port) + +-- + +close(clientSocket) \ No newline at end of file diff --git a/src/templates/julia/LmcpServer.jl b/src/templates/julia/LmcpServer.jl new file mode 100644 index 0000000..5700f5b --- /dev/null +++ b/src/templates/julia/LmcpServer.jl @@ -0,0 +1,31 @@ +## =============================================================================== +## Authors: AFRL/RQQA +## Organization: Air Force Research Laboratory, Aerospace Systems Directorate, Power and Control Division +## +## Copyright (c) 2017 Government of the United State of America, as represented by +## the Secretary of the Air Force. No copyright is claimed in the United States under +## Title 17, U.S. Code. All Other Rights Reserved. +## =============================================================================== + +## This file was auto-created by LmcpGen. Modifications will be overwritten. + +import Sockets +import LMCP + +host = "127.0.0.1" +port = 11041 + +serverSocket = Sockets.listen(port) + +while true + sock = Sockets.accept(serverSocket) + println("Connected client") + while isopen(sock) + bytes = read(sock) + objects = LMCP.unpack_messages(bytes) + for object in objects + println() + println(lmcp.to_xml(object)) + end + end +end diff --git a/src/templates/julia/Project.toml b/src/templates/julia/Project.toml new file mode 100644 index 0000000..fd72ecb --- /dev/null +++ b/src/templates/julia/Project.toml @@ -0,0 +1,12 @@ +name = "LMCP" +uuid = "5f2853af-5fcd-4df5-818b-ccf2a074bbd0" +authors = ["Adam R Gerlach ", + "Colin Taylor "] +version = "0.1.0" + +[deps] +LightXML = "9c8b4983-aa76-5018-a973-4c85ecc9e179" +PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" + +[compat] +julia = "1.9" diff --git a/src/templates/julia/Readme.html b/src/templates/julia/Readme.html new file mode 100644 index 0000000..539f5ef --- /dev/null +++ b/src/templates/julia/Readme.html @@ -0,0 +1,58 @@ + + + + LMCP / Julia Code Package Readme + + +

Using the LMCP Julia Code Package

+

Created on --

+ +

Introduction

+ +

+ This document describes how to use Julia files generated for the + Lightweight Message Construction Protocol (LMCP) system. The Julia + source code is produced using the LmcpGen utility, part of the + LMCP package available from the AVTAS laboratory at AFRL. +

+ +

The LMCP Julia source package includes classes to handle + the creation, recognition, serialization, and de-serialization of + LMCP objects. The source package contains two top-level directories. + The LMCP directory contains base classes from which all LMCP + classes are derived. These files enable interface-style access to + LMCP objects regardless of the MDM namespace. When implementing + tools to handle LMCP classes, it is recommended that one uses these + base-classes in methods. The Test directory contains basis + tests. +

+ +

Creating LMCP Objects

+ +

Every LMCP struct listed in the MDM file has a + corresponding class found in the namespace directory. + These classes are based on AbstractLmcpMessage found in the AbstractLmcpMessage.jl file. + Enums are placed in submodules to avoid common namespace collisions. + LMCP allows for subclassing of objects, however Julia does not allow subclassing concrete types. + Each concrete type has an abstract type it subclasses. and thosse abstract types properly follow the + subclassing defined in the MDM. We then add all fields of superclasses to stuctures as needed. +

+ +

Serializing

+ +

LMCP-Julia allows serialization of objects. + Objects are serialized using the pack() method. calling + pack_message() will apply the necessary header and checksum for + transmission. unpack_message() can then be used to receive messages. The contruction of LMCP messages are + described next. +

+ +

Creating LMCP Messages

+ +

Each MDM becomes a submodule under LMCP. Desires strucutre definitions are then located under + their respective submodule. Default constructores are provided for convenience for all messages. When + setting fields that are LMCP objects themselves, deepcopy should be used to avoid copy by reference. +

+ + + diff --git a/src/templates/julia/SeriesStruct.jl b/src/templates/julia/SeriesStruct.jl new file mode 100644 index 0000000..47a19d7 --- /dev/null +++ b/src/templates/julia/SeriesStruct.jl @@ -0,0 +1,47 @@ +## =============================================================================== +## Authors: AFRL/RQQA +## Organization: Air Force Research Laboratory, Aerospace Systems Directorate, Power and Control Division +## +## Copyright (c) 2017 Government of the United State of America, as represented by +## the Secretary of the Air Force. No copyright is claimed in the United States under +## Title 17, U.S. Code. All Other Rights Reserved. +## =============================================================================== + +## This file was auto-created by LmcpGen. Modifications will be overwritten. + +#LMCP_TYPE = "--" +#SERIES_NAME = "--" +#FULL_LMCP_TYPE_NAME = "--" + +#LMCP_TYPE_ID = -- +#SERIES_NAME_ID = -- +#SERIES_VERSION = -- + +-- + + +abstract type Abstract-- <: --Abstract-- end + +""" +-- +""" +mutable struct -- <: Abstract-- + -- +end + + +#empty default constructor for ease of use if needed +-- + + +full_lmcp_type_name(::Type{--}) = "--" +lmcp_type(::Type{--}) = "--" +lmcp_type_id(::Type{--}) = UInt32(--) +series_name(::Type{--}) = "--" +series_name_id(::Type{--}) = Int64(--) +series_version(::Type{--}) = UInt16(--) + +LMCP.OBJECT_ID_REGISTRY[(--, --)] = -- +LMCP.TEXT_CREATION_REGISTRY[("--", "--")] = -- + +-- \ No newline at end of file diff --git a/src/templates/julia/enum.jl b/src/templates/julia/enum.jl new file mode 100644 index 0000000..17293c8 --- /dev/null +++ b/src/templates/julia/enum.jl @@ -0,0 +1,29 @@ +## =============================================================================== +## Authors: AFRL/RQQA +## Organization: Air Force Research Laboratory, Aerospace Systems Directorate, Power and Control Division +## +## Copyright (c) 2017 Government of the United State of America, as represented by +## the Secretary of the Air Force. No copyright is claimed in the United States under +## Title 17, U.S. Code. All Other Rights Reserved. +## =============================================================================== + +## This file was auto-created by LmcpGen. Modifications will be overwritten. + +module --Module + +""" +-- +""" +@enum -- begin + -- +end + +function --(str::String) + """ + Returns a numerical value from a string + """ + -- +end + + +end \ No newline at end of file diff --git a/src/templates/julia/installToRegistry.sh b/src/templates/julia/installToRegistry.sh new file mode 100755 index 0000000..6948c48 --- /dev/null +++ b/src/templates/julia/installToRegistry.sh @@ -0,0 +1,6 @@ +# Example commands to install locally. Must be done if not activated. +cd LMCP +git init +git add . +git commit -m "init" +julia -e 'using Pkg; Pkg.add(path=pwd())' \ No newline at end of file diff --git a/src/templates/julia/lmcp.jl b/src/templates/julia/lmcp.jl new file mode 100644 index 0000000..159e3cc --- /dev/null +++ b/src/templates/julia/lmcp.jl @@ -0,0 +1,29 @@ +## =============================================================================== +## Authors: AFRL/RQQA +## Organization: Air Force Research Laboratory, Aerospace Systems Directorate, Power and Control Division +## +## Copyright (c) 2017 Government of the United State of America, as represented by +## the Secretary of the Air Force. No copyright is claimed in the United States under +## Title 17, U.S. Code. All Other Rights Reserved. +## =============================================================================== + +## This file was auto-created by LmcpGen. Modifications will be overwritten. + +module LMCP + +import LightXML +import Base: ==, write, read + +using PrecompileTools + +include("AbstractLmcpMessage.jl") +include("LMCPFactory.jl") + +#MDM submodules +-- + +@compile_workload begin + include("precompilePack.jl") + include("precompileXML.jl") +end +end \ No newline at end of file diff --git a/src/templates/julia/module.jl b/src/templates/julia/module.jl new file mode 100644 index 0000000..20e9528 --- /dev/null +++ b/src/templates/julia/module.jl @@ -0,0 +1,24 @@ +## =============================================================================== +## Authors: AFRL/RQQA +## Organization: Air Force Research Laboratory, Aerospace Systems Directorate, Power and Control Division +## +## Copyright (c) 2017 Government of the United State of America, as represented by +## the Secretary of the Air Force. No copyright is claimed in the United States under +## Title 17, U.S. Code. All Other Rights Reserved. +## =============================================================================== + +## This file was auto-created by LmcpGen. Modifications will be overwritten. + +""" +-- +""" +module -- + +using ..LMCP + +#explicitly import methods needed for dynamic dispatch +import ..full_lmcp_type_name, ..lmcp_type, ..lmcp_type_id, ..series_name, ..series_name_id, ..series_version + +-- + +end \ No newline at end of file diff --git a/src/templates/julia/precompilePack.jl b/src/templates/julia/precompilePack.jl new file mode 100644 index 0000000..2d56d1b --- /dev/null +++ b/src/templates/julia/precompilePack.jl @@ -0,0 +1,16 @@ +## =============================================================================== +## Authors: AFRL/RQQA +## Organization: Air Force Research Laboratory, Aerospace Systems Directorate, Power and Control Division +## +## Copyright (c) 2017 Government of the United State of America, as represented by +## the Secretary of the Air Force. No copyright is claimed in the United States under +## Title 17, U.S. Code. All Other Rights Reserved. +## =============================================================================== + +## This file was auto-created by LmcpGen. Modifications will be overwritten. + +#import Pkg +#Pkg.activate(".") +import LMCP + +-- \ No newline at end of file diff --git a/src/templates/julia/precompileXML.jl b/src/templates/julia/precompileXML.jl new file mode 100644 index 0000000..5aa90b3 --- /dev/null +++ b/src/templates/julia/precompileXML.jl @@ -0,0 +1,16 @@ +## =============================================================================== +## Authors: AFRL/RQQA +## Organization: Air Force Research Laboratory, Aerospace Systems Directorate, Power and Control Division +## +## Copyright (c) 2017 Government of the United State of America, as represented by +## the Secretary of the Air Force. No copyright is claimed in the United States under +## Title 17, U.S. Code. All Other Rights Reserved. +## =============================================================================== + +## This file was auto-created by LmcpGen. Modifications will be overwritten. + +#import Pkg +#Pkg.activate(".") +import LMCP + +-- \ No newline at end of file diff --git a/src/templates/julia/runtests.jl b/src/templates/julia/runtests.jl new file mode 100644 index 0000000..0386e5e --- /dev/null +++ b/src/templates/julia/runtests.jl @@ -0,0 +1,93 @@ +## =============================================================================== +## Authors: AFRL/RQQA +## Organization: Air Force Research Laboratory, Aerospace Systems Directorate, Power and Control Division +## +## Copyright (c) 2017 Government of the United State of America, as represented by +## the Secretary of the Air Force. No copyright is claimed in the United States under +## Title 17, U.S. Code. All Other Rights Reserved. +## =============================================================================== + +## This file was auto-created by LmcpGen. Modifications will be overwritten. + +import LMCP +import Random +using Test, InteractiveUtils + +function _subtypes(type::Type) + out = Any[] + return _subtypes!(out, type) +end + +function _subtypes!(out, type::Type) + if !isabstracttype(type) + push!(out, type) + else + foreach(T->_subtypes!(out, T), subtypes(type)) + end + return out +end + + +function _rand(type, _) return Random.rand(type) end +function _rand(::Type{String}, _) l = Random.rand(UInt8); return Random.randstring(Int(l)) end +function _rand(enum::Type{T}, _) where T<:Enum return rand(instances(enum)) end +function _rand(::Type{Vector{T}}, depthLimit) where T return [_rand(T, depthLimit) for x in 1:3] end +function _rand(::Type{T}, depthLimit) where T <: LMCP.AbstractLmcpMessage ret = rand(_subtypes(T))(); randomize!(ret, depthLimit); return ret end +function _rand(::Type{Union{T, Nothing}}, depthLimit) where T <: LMCP.AbstractLmcpMessage + retNothingCheck = Random.rand([0,1]) == 1 + if retNothingCheck return nothing end + ret = rand(_subtypes(T))() + randomize!(ret, depthLimit) + return ret +end + +function randomize!(o::LMCP.AbstractLmcpMessage, depthLimit=3) + + if depthLimit == 0 return end + + type = typeof(o) + for (n, t) in zip(fieldnames(type), fieldtypes(type)) + #print("$n $t\n") + value = _rand(t, depthLimit - 1) + setproperty!(o, n, value) + end +end + + +function test_pack_unpack(o::LMCP.AbstractLmcpMessage) + buffer = LMCP.pack_message(o) + tmp2 = LMCP.unpack_message(buffer) + return o == tmp2 +end + +function test_xml(o::LMCP.AbstractLmcpMessage) + xml = LMCP.to_xml(o) + tmp2 = LMCP.from_xml(xml) + return o == tmp2 +end + +const msg_types = _subtypes(LMCP.AbstractLmcpMessage) + +@testset "Default constructors" begin + for T ∈ msg_types + @test T() isa T + end +end + +@testset "pack_unpack" begin + Random.seed!(1234) + for T ∈ msg_types + msg = T() + randomize!(msg) + @test test_pack_unpack(msg) + end +end + +@testset "to_from_xml" begin + Random.seed!(1234) + for T ∈ msg_types + msg = T() + randomize!(msg) + @test test_xml(msg) + end +end diff --git a/src/templates/julia/test_project.toml b/src/templates/julia/test_project.toml new file mode 100644 index 0000000..23f3afc --- /dev/null +++ b/src/templates/julia/test_project.toml @@ -0,0 +1,4 @@ +[deps] +InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" From cbf834e54ff0d884c21827c98fd72590b1d75e8f Mon Sep 17 00:00:00 2001 From: Colin Taylor Date: Thu, 25 Jan 2024 11:09:58 -0500 Subject: [PATCH 2/7] Adding Julia kwarg constructor for ease of use. --- src/avtas/lmcp/lmcpgen/JuliaMethods.java | 119 +++++++++++++++++------ src/templates/julia/SeriesStruct.jl | 4 +- 2 files changed, 90 insertions(+), 33 deletions(-) diff --git a/src/avtas/lmcp/lmcpgen/JuliaMethods.java b/src/avtas/lmcp/lmcpgen/JuliaMethods.java index 95332ae..b25b9c6 100644 --- a/src/avtas/lmcp/lmcpgen/JuliaMethods.java +++ b/src/avtas/lmcp/lmcpgen/JuliaMethods.java @@ -291,16 +291,25 @@ public static String define_vars(MDMInfo[] infos, MDMInfo info, final File outfi String qualifier = f.isEnum ? f.seriesName + "." : ""; if (f.isArray) { //String qualifier = isTypePrimitive(f.type) ? "" : type + "Module."; + String qualifiedName = type; + if (f.isEnum) { str += ws + name + "::Vector{" + qualifier + type + "Module." + type + "}"; + qualifiedName = qualifier + type + "Module." + type; } else if (f.isStruct){ type = f.seriesName + ".Abstract" + type; str += ws + name + "::Vector{" + type + "}"; + qualifiedName = f.seriesName + "." + type; + } else { str += ws + name + "::Vector{" + type + "}"; } + if (f.length < 0) { + } else { + String initializer = isTypePrimitive(f.type) ? "0" : ""; + } } else if (f.isEnum) { str += ws + name + "::" + qualifier + type + "Module." + type; @@ -312,10 +321,16 @@ else if (f.isStruct) { } str += ws + name + "::" + type; } - else - { + else if (f.type.equalsIgnoreCase("string")) { str += ws + name + "::" + type; } + else { + str += ws + name + "::" + type; + String defaultVal = f.defaultVal; + if (defaultVal.isBlank()) { + defaultVal = getDefaultValue(f.type); + } + } str += "\n"; } return str; @@ -482,53 +497,95 @@ public static String define_defaults(MDMInfo[] infos, MDMInfo info, final File o List fields = getParentFields(infos, st.extends_name, st.extends_series); fields.addAll(Arrays.asList(st.fields)); - if (fields.isEmpty()) { - return str; - } - for (FieldInfo f : fields) { + String name = f.name; String type = getJuliaType(f.type); String qualifier = f.isEnum ? f.seriesName + "." : ""; - if (!f.isArray) { - if (f.type.equalsIgnoreCase("string")) { - str += f.defaultVal.isBlank() ? "\"\"," : "\"" + f.defaultVal + "\", "; - } - else if (f.isStruct) { - str += f.defaultVal.equalsIgnoreCase("null") || f.isOptional ? "nothing, " : f.seriesName + "." + f.type + "(),"; - } - else if (f.isEnum) { - str += qualifier + f.type + "Module." + f.defaultVal + ", "; - } - else { - String defaultVal = f.defaultVal; - if (defaultVal.isBlank()) { - defaultVal = getDefaultValue(f.type); - } - str += defaultVal + ", "; - } - - // variable length array - } else { + if (f.isArray) { + //String qualifier = isTypePrimitive(f.type) ? "" : type + "Module."; String qualifiedName = type; + if (f.isEnum) { + str += ws + name + "::Vector{" + qualifier + type + "Module." + type + "}"; qualifiedName = qualifier + type + "Module." + type; } - else if (f.isStruct) { + else if (f.isStruct){ + type = f.seriesName + ".Abstract" + type; + str += ws + name + "::Vector{" + type + "}"; qualifiedName = f.seriesName + "." + type; + + } + else { + str += ws + name + "::Vector{" + type + "}"; } if (f.length < 0) { - str += "Vector{" + qualifiedName + "}(), "; + str += " = Vector{" + qualifiedName + "}()"; } else { String initializer = isTypePrimitive(f.type) ? "0" : ""; - str += String.format("[%s(%s) for x in 1:%s], ", qualifiedName, initializer, f.length); - //str += "Vector{" + qualifiedName + "}(undef, "+ f.length + "), "; + str += String.format(" = [%s(%s) for x in 1:%s]", qualifiedName, initializer, f.length); } } + else if (f.isEnum) { + str += ws + name + "::" + qualifier + type + "Module." + type; + str += " = " + qualifier + f.type + "Module." + f.defaultVal; + } + else if (f.isStruct) { + type = f.seriesName + ".Abstract" + type; + if (f.isOptional || f.defaultVal.equalsIgnoreCase("null")) { + type = "Union{" + type + ", Nothing}"; + } + str += ws + name + "::" + type; + str += " = " + (f.defaultVal.equalsIgnoreCase("null") || f.isOptional ? "nothing " : f.seriesName + "." + f.type + "()"); + } + else if (f.type.equalsIgnoreCase("string")) { + str += ws + name + "::" + type; + str += " = " + (f.defaultVal.isBlank() ? "\"\"" : "\"" + f.defaultVal + "\""); + } + else { + str += ws + name + "::" + type; + String defaultVal = f.defaultVal; + if (defaultVal.isBlank()) { + defaultVal = getDefaultValue(f.type); + } + str += " = " + type + "(" + defaultVal + ")"; + } + str += ",\n"; } - str = String.format("%s() = %s(%s)", st.name, st.name, str); return str; } + public static String list_vars(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + String str = ws; + + List fields = getParentFields(infos, st.extends_name, st.extends_series); + fields.addAll(Arrays.asList(st.fields)); + + for (FieldInfo f : fields) { + str += f.name + ", "; + } + return str; + } + + public static String build_constructor(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { + String str = ws; + + List fields = getParentFields(infos, st.extends_name, st.extends_series); + fields.addAll(Arrays.asList(st.fields)); + + //This section is done in code for the below check. Otherwise we get + //a redefinition and stack overflow + if (fields.isEmpty()) { + return ""; + } + + str += typeName(infos, info, outfile, st, en, ws) + "(;\n"; + str += define_defaults(infos, info, outfile, st, en, ws); + str += ") = " + series_name(infos, info, outfile, st, en, ws) + "."; + str += typeName(infos, info, outfile, st, en, ws); + str += "(" + list_vars(infos, info, outfile, st, en, ws) + ")"; + + return str; + } private static boolean isTypePrimitive(String type) { return type.toLowerCase().matches("(bool)|(string)|(byte)|(char)|(real64)|(real32)|(int64)|(int32)|(int16)|(uint32)|(uint16)"); } diff --git a/src/templates/julia/SeriesStruct.jl b/src/templates/julia/SeriesStruct.jl index 47a19d7..9f9d733 100644 --- a/src/templates/julia/SeriesStruct.jl +++ b/src/templates/julia/SeriesStruct.jl @@ -30,8 +30,8 @@ mutable struct -- <: Abstract-- end -#empty default constructor for ease of use if needed --- +#kwarg constructor for ease of use if needed +-- full_lmcp_type_name(::Type{--}) = "--" From 30c99ca20760de4d1e33ed3b61bf681298861231 Mon Sep 17 00:00:00 2001 From: Colin Taylor Date: Thu, 25 Jan 2024 11:09:58 -0500 Subject: [PATCH 3/7] Julia Project output default to version 1.0.0. --- src/templates/julia/Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templates/julia/Project.toml b/src/templates/julia/Project.toml index fd72ecb..16835ca 100644 --- a/src/templates/julia/Project.toml +++ b/src/templates/julia/Project.toml @@ -2,7 +2,7 @@ name = "LMCP" uuid = "5f2853af-5fcd-4df5-818b-ccf2a074bbd0" authors = ["Adam R Gerlach ", "Colin Taylor "] -version = "0.1.0" +version = "1.0.0" [deps] LightXML = "9c8b4983-aa76-5018-a973-4c85ecc9e179" From 832631831f6518624d621d165a0da28751924ca2 Mon Sep 17 00:00:00 2001 From: Colin Taylor Date: Thu, 25 Jan 2024 11:09:58 -0500 Subject: [PATCH 4/7] whitespace cleanup. --- src/avtas/lmcp/lmcpgen/JuliaMethods.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/avtas/lmcp/lmcpgen/JuliaMethods.java b/src/avtas/lmcp/lmcpgen/JuliaMethods.java index b25b9c6..8eba410 100644 --- a/src/avtas/lmcp/lmcpgen/JuliaMethods.java +++ b/src/avtas/lmcp/lmcpgen/JuliaMethods.java @@ -492,7 +492,7 @@ public static String struct_imports(MDMInfo[] infos, MDMInfo info, final File ou } public static String define_defaults(MDMInfo[] infos, MDMInfo info, final File outfile, StructInfo st, EnumInfo en, String ws) throws Exception { - String str = ws; + String str = ""; List fields = getParentFields(infos, st.extends_name, st.extends_series); fields.addAll(Arrays.asList(st.fields)); @@ -577,12 +577,14 @@ public static String build_constructor(MDMInfo[] infos, MDMInfo info, final File if (fields.isEmpty()) { return ""; } + ws = " "; + String emptyws = ""; - str += typeName(infos, info, outfile, st, en, ws) + "(;\n"; + str += typeName(infos, info, outfile, st, en, emptyws) + "(;\n"; str += define_defaults(infos, info, outfile, st, en, ws); - str += ") = " + series_name(infos, info, outfile, st, en, ws) + "."; - str += typeName(infos, info, outfile, st, en, ws); - str += "(" + list_vars(infos, info, outfile, st, en, ws) + ")"; + str += ") = " + series_name(infos, info, outfile, st, en, emptyws) + "."; + str += typeName(infos, info, outfile, st, en, emptyws); + str += "(" + list_vars(infos, info, outfile, st, en, emptyws) + ")"; return str; } From 879f9f79b64abbaa4fcc139a0a3295c04176d69b Mon Sep 17 00:00:00 2001 From: Colin Taylor Date: Thu, 25 Jan 2024 11:09:58 -0500 Subject: [PATCH 5/7] Adding convenience parameters to add attributes to xml output. --- src/templates/julia/LMCPFactory.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/templates/julia/LMCPFactory.jl b/src/templates/julia/LMCPFactory.jl index f92c758..87083d2 100644 --- a/src/templates/julia/LMCPFactory.jl +++ b/src/templates/julia/LMCPFactory.jl @@ -95,9 +95,10 @@ function unpack_messages(msgs::String) return unpack_messages(bytes) end -function to_xml(o::AbstractLmcpMessage) +function to_xml(o::AbstractLmcpMessage; extraAttributes...) xdoc = LightXML.XMLDocument() xroot = LightXML.create_root(xdoc, lmcp_type(o)) + LightXML.set_attributes(xroot, extraAttributes) xroot = _to_xml(xroot, o) ret = string(xdoc) LightXML.free(xdoc) From 17111a49256049594bcdb092fea0f8ee10d07b5a Mon Sep 17 00:00:00 2001 From: Colin Taylor Date: Thu, 25 Jan 2024 11:09:58 -0500 Subject: [PATCH 6/7] Updating UUID to match existing LMCP.jl. --- src/templates/julia/Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templates/julia/Project.toml b/src/templates/julia/Project.toml index 16835ca..97a11f5 100644 --- a/src/templates/julia/Project.toml +++ b/src/templates/julia/Project.toml @@ -1,5 +1,5 @@ name = "LMCP" -uuid = "5f2853af-5fcd-4df5-818b-ccf2a074bbd0" +uuid = "8137e754-b8b5-4699-a81f-8f4ab81b79e2" authors = ["Adam R Gerlach ", "Colin Taylor "] version = "1.0.0" From af9be1738028af9e3cd4acbdfce7329193de812c Mon Sep 17 00:00:00 2001 From: Adam R Gerlach Date: Thu, 25 Jan 2024 11:18:57 -0500 Subject: [PATCH 7/7] update readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3be4ff8..fff16c9 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ specification of LMCP. Currently, *LmcpGen* creates libraries for the following - C++ - C# - Python +- Julia Additionally, *LmcpGen* can create HTML documentation in the form of an easy-to-navigate webpage for viewing the messages described in source MDMs. @@ -72,6 +73,7 @@ command line options are as follows: - `-cpp` Adds proper template and method name for C++ output. Note: creates c++11 compatible code. - `-cs` Adds proper template and method name for C# output. - `-py` Adds proper template and method name for Python output. + - `-jl` Adds proper template and method name for Julia output. - `-xsd` Adds proper template and method name for XML schema output. - `-doc` Adds proper template and method name for documentation output. - `-dir ` path to the directory where files are to be written.