# ===================================== # Copyright 2017-2023, Andrew Lindesay # Distributed under the terms of the MIT License. # ===================================== # common material related to generation of schema-generated artifacts. import datetime # The possible JSON types JSON_TYPE_STRING = "string" JSON_TYPE_OBJECT = "object" JSON_TYPE_ARRAY = "array" JSON_TYPE_BOOLEAN = "boolean" JSON_TYPE_INTEGER = "integer" JSON_TYPE_NUMBER = "number" # The possible C++ types CPP_TYPE_STRING = "BString" CPP_TYPE_ARRAY = "BObjectList" CPP_TYPE_BOOLEAN = "bool" CPP_TYPE_INTEGER = "int64" CPP_TYPE_NUMBER = "double" # The possible C++ default values CPP_DEFAULT_STRING = "NULL" CPP_DEFAULT_OBJECT = "NULL" CPP_DEFAULT_ARRAY = "NULL" CPP_DEFAULT_BOOLEAN = "false" CPP_DEFAULT_INTEGER = "0" CPP_DEFAULT_NUMBER = "0.0" def uniondicts(d1, d2): d = dict(d1) d.update(d2) return d def javatypetocppname(javaname): return javaname[javaname.rindex('.')+1:] def propnametocppname(propname): return propname[0:1].upper() + propname[1:] def propnametocppmembername(propname): return "f" + propnametocppname(propname) def propmetadatatocppdefaultvalue(propmetadata): type = propmetadata['type'] if type == JSON_TYPE_STRING: return CPP_DEFAULT_STRING if type == JSON_TYPE_BOOLEAN: return CPP_DEFAULT_BOOLEAN if type == JSON_TYPE_INTEGER: return CPP_DEFAULT_INTEGER if type == JSON_TYPE_NUMBER: return CPP_DEFAULT_NUMBER if type == JSON_TYPE_OBJECT: return CPP_DEFAULT_OBJECT if type == JSON_TYPE_ARRAY: return CPP_DEFAULT_ARRAY raise Exception('unknown json-schema type [' + type + ']') def propmetadatatocpptypename(propmetadata): type = propmetadata['type'] if type == JSON_TYPE_STRING: return CPP_TYPE_STRING if type == JSON_TYPE_BOOLEAN: return CPP_TYPE_BOOLEAN if type == JSON_TYPE_INTEGER: return CPP_TYPE_INTEGER if type == JSON_TYPE_NUMBER: return CPP_TYPE_NUMBER if type == JSON_TYPE_OBJECT: javatype = propmetadata['javaType'] if not javatype or 0 == len(javatype): raise Exception('missing "javaType" field') return javatypetocppname(javatype) if type == JSON_TYPE_ARRAY: itemsmetadata = propmetadata['items'] itemstype = itemsmetadata['type'] if not itemstype or 0 == len(itemstype): raise Exception('missing "type" field') if itemstype == JSON_TYPE_OBJECT: itemsjavatype = itemsmetadata['javaType'] if not itemsjavatype or 0 == len(itemsjavatype): raise Exception('missing "javaType" field') return "%s<%s>" % (CPP_TYPE_ARRAY, javatypetocppname(itemsjavatype)) if itemstype == JSON_TYPE_STRING: return "%s<%s>" % (CPP_TYPE_ARRAY, CPP_TYPE_STRING) raise Exception('unsupported type [%s]' % itemstype) raise Exception('unknown json-schema type [' + type + ']') def propmetadatatypeisscalar(propmetadata): type = propmetadata['type'] return type == JSON_TYPE_BOOLEAN or type == JSON_TYPE_INTEGER or type == JSON_TYPE_NUMBER def writetopcomment(f, inputfilename, variant): f.write(( '/*\n' ' * Generated %s Object\n' ' * source json-schema : %s\n' ' * generated at : %s\n' ' */\n' ) % (variant, inputfilename, datetime.datetime.now().isoformat())) def collect_all_objects(schema: dict[str, any]) -> list[dict[str, any]]: assembly = dict[str, dict[str, any]]() def accumulate_all_objects(obj: dict[str, any]) -> None: assembly[obj["cppname"]] = obj for prop_name, prop in obj['properties'].items(): if JSON_TYPE_ARRAY == prop["type"]: array_items = prop['items'] if JSON_TYPE_OBJECT == array_items["type"] and not array_items["cppname"] in assembly: accumulate_all_objects(array_items) if JSON_TYPE_OBJECT == prop["type"] and not prop["cppname"] in assembly: accumulate_all_objects(prop) accumulate_all_objects(schema) result = list(assembly.values()) result.sort(key= lambda v: v["cppname"]) return result def augment_schema(schema: dict[str, any]) -> None: """This function will take the data from the JSON schema and will overlay any specific information required for rendering the templates. """ def derive_cpp_classname(obj: dict[str, any]) -> str: obj_type = obj['type'] if obj_type != JSON_TYPE_OBJECT: raise Exception('expecting object, but was [' + obj_type + ']') java_type = obj['javaType'] return javatypetocppname(java_type) def augment_property(prop: dict[str, any]) -> None: prop_type = prop['type'] prop["cpptype"] = propmetadatatocpptypename(prop) is_scalar_type = propmetadatatypeisscalar(prop) is_array_type = (prop_type == JSON_TYPE_ARRAY) prop["iscppscalartype"] = is_scalar_type prop["isarray"] = is_array_type prop["isstring"] = JSON_TYPE_STRING == prop_type prop["isboolean"] = JSON_TYPE_BOOLEAN == prop_type prop["isnumber"] = JSON_TYPE_NUMBER == prop_type prop["isinteger"] = JSON_TYPE_INTEGER == prop_type prop["isobject"] = JSON_TYPE_OBJECT == prop_type prop["iscppnonscalarnoncollectiontype"] = not is_scalar_type and not is_array_type prop["toplevelcppname"] = top_level_cpp_name prop["cppdefaultvalue"] = propmetadatatocppdefaultvalue(prop) if not prop_type or 0 == len(prop_type): raise Exception('missing "type" field') if JSON_TYPE_ARRAY == prop_type: array_items = prop['items'] array_items_type = array_items["type"] if array_items_type == JSON_TYPE_OBJECT: augment_object(array_items) else: augment_property(array_items) if JSON_TYPE_OBJECT == prop_type: augment_object(prop) def augment_object(obj: dict[str, any]) -> None: def collect_referenced_class_names() -> list[str]: result = set() properties = obj['properties'].items() if len(properties) > 15: raise RuntimeError("an object has more than 15 properties" " which is not allowed") for _, prop in obj['properties'].items(): if prop['type'] == JSON_TYPE_ARRAY: array_items = prop['items'] if array_items['type'] == JSON_TYPE_OBJECT: result.add(array_items['cppname']) if prop['type'] == JSON_TYPE_OBJECT: result.add(prop['cppname']) result_ordered = list(result) result_ordered.sort() return result_ordered def has_any_list_properties() -> bool: for _, prop in obj['properties'].items(): if prop['type'] == JSON_TYPE_ARRAY: return True return False obj_cpp_classname = derive_cpp_classname(obj) obj["cppname"] = obj_cpp_classname obj["cppnameupper"] = obj_cpp_classname.upper() obj["cpptype"] = obj_cpp_classname obj["hasanylistproperties"] = has_any_list_properties() # allows the object to know the top level object that contains it obj["toplevelcppname"] = top_level_cpp_name properties = obj['properties'].items() for prop_name, prop in properties: prop["cppname"] = propnametocppname(prop_name) prop["cppmembername"] = propnametocppmembername(prop_name) augment_property(prop) # mustache is not able to iterate over a dictionary; it can only # iterate over a list where each item in the list has some properties. property_array = sorted( [{ "name": k, "property": v, "cppobjectname": obj_cpp_classname # ^ this is the name of the containing object } for k,v in properties], key= lambda item: item["name"] ) for i in range(len(property_array)): property_array[i]["cppbitmaskexpression"] = "(1 << %d)" % i if 0 != len(property_array): property_array[0]["isfirst"] = True property_array[len(property_array) - 1]["islast"] = True obj["propertyarray"] = property_array obj["referencedclasscpptypes"] = collect_referenced_class_names() top_level_cpp_name = derive_cpp_classname(schema) augment_object(schema)