1 2# ===================================== 3# Copyright 2017-2023, Andrew Lindesay 4# Distributed under the terms of the MIT License. 5# ===================================== 6 7# common material related to generation of schema-generated artifacts. 8 9import datetime 10 11 12# The possible JSON types 13JSON_TYPE_STRING = "string" 14JSON_TYPE_OBJECT = "object" 15JSON_TYPE_ARRAY = "array" 16JSON_TYPE_BOOLEAN = "boolean" 17JSON_TYPE_INTEGER = "integer" 18JSON_TYPE_NUMBER = "number" 19 20 21# The possible C++ types 22CPP_TYPE_STRING = "BString" 23CPP_TYPE_ARRAY = "BObjectList" 24CPP_TYPE_BOOLEAN = "bool" 25CPP_TYPE_INTEGER = "int64" 26CPP_TYPE_NUMBER = "double" 27 28 29# The possible C++ default values 30CPP_DEFAULT_STRING = "NULL" 31CPP_DEFAULT_OBJECT = "NULL" 32CPP_DEFAULT_ARRAY = "NULL" 33CPP_DEFAULT_BOOLEAN = "false" 34CPP_DEFAULT_INTEGER = "0" 35CPP_DEFAULT_NUMBER = "0.0" 36 37 38def uniondicts(d1, d2): 39 d = dict(d1) 40 d.update(d2) 41 return d 42 43 44def javatypetocppname(javaname): 45 return javaname[javaname.rindex('.')+1:] 46 47 48def propnametocppname(propname): 49 return propname[0:1].upper() + propname[1:] 50 51 52def propnametocppmembername(propname): 53 return "f" + propnametocppname(propname) 54 55def propmetadatatocppdefaultvalue(propmetadata): 56 type = propmetadata['type'] 57 58 if type == JSON_TYPE_STRING: 59 return CPP_DEFAULT_STRING 60 if type == JSON_TYPE_BOOLEAN: 61 return CPP_DEFAULT_BOOLEAN 62 if type == JSON_TYPE_INTEGER: 63 return CPP_DEFAULT_INTEGER 64 if type == JSON_TYPE_NUMBER: 65 return CPP_DEFAULT_NUMBER 66 if type == JSON_TYPE_OBJECT: 67 return CPP_DEFAULT_OBJECT 68 if type == JSON_TYPE_ARRAY: 69 return CPP_DEFAULT_ARRAY 70 71 raise Exception('unknown json-schema type [' + type + ']') 72 73def propmetadatatocpptypename(propmetadata): 74 type = propmetadata['type'] 75 76 if type == JSON_TYPE_STRING: 77 return CPP_TYPE_STRING 78 if type == JSON_TYPE_BOOLEAN: 79 return CPP_TYPE_BOOLEAN 80 if type == JSON_TYPE_INTEGER: 81 return CPP_TYPE_INTEGER 82 if type == JSON_TYPE_NUMBER: 83 return CPP_TYPE_NUMBER 84 if type == JSON_TYPE_OBJECT: 85 javatype = propmetadata['javaType'] 86 87 if not javatype or 0 == len(javatype): 88 raise Exception('missing "javaType" field') 89 90 return javatypetocppname(javatype) 91 92 if type == JSON_TYPE_ARRAY: 93 itemsmetadata = propmetadata['items'] 94 itemstype = itemsmetadata['type'] 95 96 if not itemstype or 0 == len(itemstype): 97 raise Exception('missing "type" field') 98 99 if itemstype == JSON_TYPE_OBJECT: 100 itemsjavatype = itemsmetadata['javaType'] 101 if not itemsjavatype or 0 == len(itemsjavatype): 102 raise Exception('missing "javaType" field') 103 return "%s<%s>" % (CPP_TYPE_ARRAY, javatypetocppname(itemsjavatype)) 104 105 if itemstype == JSON_TYPE_STRING: 106 return "%s<%s>" % (CPP_TYPE_ARRAY, CPP_TYPE_STRING) 107 108 raise Exception('unsupported type [%s]' % itemstype) 109 110 raise Exception('unknown json-schema type [' + type + ']') 111 112 113def propmetadatatypeisscalar(propmetadata): 114 type = propmetadata['type'] 115 return type == JSON_TYPE_BOOLEAN or type == JSON_TYPE_INTEGER or type == JSON_TYPE_NUMBER 116 117 118def writetopcomment(f, inputfilename, variant): 119 f.write(( 120 '/*\n' 121 ' * Generated %s Object\n' 122 ' * source json-schema : %s\n' 123 ' * generated at : %s\n' 124 ' */\n' 125 ) % (variant, inputfilename, datetime.datetime.now().isoformat())) 126 127def collect_all_objects(schema: dict[str, any]) -> list[dict[str, any]]: 128 assembly = dict[str, dict[str, any]]() 129 130 def accumulate_all_objects(obj: dict[str, any]) -> None: 131 assembly[obj["cppname"]] = obj 132 133 for prop_name, prop in obj['properties'].items(): 134 if JSON_TYPE_ARRAY == prop["type"]: 135 array_items = prop['items'] 136 137 if JSON_TYPE_OBJECT == array_items["type"] and not array_items["cppname"] in assembly: 138 accumulate_all_objects(array_items) 139 140 if JSON_TYPE_OBJECT == prop["type"] and not prop["cppname"] in assembly: 141 accumulate_all_objects(prop) 142 143 accumulate_all_objects(schema) 144 result = list(assembly.values()) 145 result.sort(key= lambda v: v["cppname"]) 146 147 return result 148 149 150def augment_schema(schema: dict[str, any]) -> None: 151 """This function will take the data from the JSON schema and will overlay any 152 specific information required for rendering the templates. 153 """ 154 155 def derive_cpp_classname(obj: dict[str, any]) -> str: 156 obj_type = obj['type'] 157 158 if obj_type != JSON_TYPE_OBJECT: 159 raise Exception('expecting object, but was [' + obj_type + ']') 160 161 java_type = obj['javaType'] 162 163 return javatypetocppname(java_type) 164 165 def augment_property(prop: dict[str, any]) -> None: 166 prop_type = prop['type'] 167 168 prop["cpptype"] = propmetadatatocpptypename(prop) 169 170 is_scalar_type = propmetadatatypeisscalar(prop) 171 is_array_type = (prop_type == JSON_TYPE_ARRAY) 172 173 prop["iscppscalartype"] = is_scalar_type 174 prop["isarray"] = is_array_type 175 prop["isstring"] = JSON_TYPE_STRING == prop_type 176 prop["isboolean"] = JSON_TYPE_BOOLEAN == prop_type 177 prop["isnumber"] = JSON_TYPE_NUMBER == prop_type 178 prop["isinteger"] = JSON_TYPE_INTEGER == prop_type 179 prop["isobject"] = JSON_TYPE_OBJECT == prop_type 180 prop["iscppnonscalarnoncollectiontype"] = not is_scalar_type and not is_array_type 181 prop["toplevelcppname"] = top_level_cpp_name 182 prop["cppdefaultvalue"] = propmetadatatocppdefaultvalue(prop) 183 184 if not prop_type or 0 == len(prop_type): 185 raise Exception('missing "type" field') 186 187 if JSON_TYPE_ARRAY == prop_type: 188 array_items = prop['items'] 189 array_items_type = array_items["type"] 190 191 if array_items_type == JSON_TYPE_OBJECT: 192 augment_object(array_items) 193 else: 194 augment_property(array_items) 195 196 if JSON_TYPE_OBJECT == prop_type: 197 augment_object(prop) 198 199 def augment_object(obj: dict[str, any]) -> None: 200 201 def collect_referenced_class_names() -> list[str]: 202 result = set() 203 properties = obj['properties'].items() 204 205 if len(properties) > 15: 206 raise RuntimeError("an object has more than 15 properties" 207 " which is not allowed") 208 209 for _, prop in obj['properties'].items(): 210 if prop['type'] == JSON_TYPE_ARRAY: 211 array_items = prop['items'] 212 if array_items['type'] == JSON_TYPE_OBJECT: 213 result.add(array_items['cppname']) 214 if prop['type'] == JSON_TYPE_OBJECT: 215 result.add(prop['cppname']) 216 217 result_ordered = list(result) 218 result_ordered.sort() 219 return result_ordered 220 221 def has_any_list_properties() -> bool: 222 for _, prop in obj['properties'].items(): 223 if prop['type'] == JSON_TYPE_ARRAY: 224 return True 225 return False 226 227 obj_cpp_classname = derive_cpp_classname(obj) 228 obj["cppname"] = obj_cpp_classname 229 obj["cppnameupper"] = obj_cpp_classname.upper() 230 obj["cpptype"] = obj_cpp_classname 231 obj["hasanylistproperties"] = has_any_list_properties() 232 233 # allows the object to know the top level object that contains it 234 obj["toplevelcppname"] = top_level_cpp_name 235 236 properties = obj['properties'].items() 237 238 for prop_name, prop in properties: 239 prop["cppname"] = propnametocppname(prop_name) 240 prop["cppmembername"] = propnametocppmembername(prop_name) 241 augment_property(prop) 242 243 # mustache is not able to iterate over a dictionary; it can only 244 # iterate over a list where each item in the list has some properties. 245 246 property_array = sorted( 247 [{ 248 "name": k, 249 "property": v, 250 "cppobjectname": obj_cpp_classname 251 # ^ this is the name of the containing object 252 } for k,v in properties], 253 key= lambda item: item["name"] 254 ) 255 256 for i in range(len(property_array)): 257 property_array[i]["cppbitmaskexpression"] = "(1 << %d)" % i 258 259 if 0 != len(property_array): 260 property_array[0]["isfirst"] = True 261 property_array[len(property_array) - 1]["islast"] = True 262 263 obj["propertyarray"] = property_array 264 obj["referencedclasscpptypes"] = collect_referenced_class_names() 265 266 top_level_cpp_name = derive_cpp_classname(schema) 267 augment_object(schema)