cesium-native/tools/generate-classes/generate.js

546 lines
17 KiB
JavaScript

const fs = require("fs");
const getNameFromTitle = require("./getNameFromTitle");
const indent = require("./indent");
const lodash = require("lodash");
const NameFormatters = require("./NameFormatters");
const path = require("path");
const resolveProperty = require("./resolveProperty");
const unindent = require("./unindent");
function generate(options, schema, writers) {
const {
schemaCache,
outputDir,
readerOutputDir,
config,
namespace,
readerNamespace,
writerNamespace,
extensions,
} = options;
const baseName = getNameFromTitle(config, schema.title);
const prefix =
schema.prefix && schema.prefix !== baseName ? schema.prefix : "";
const name = prefix + baseName;
const thisConfig = config.classes[schema.title] || {};
console.log(`Generating ${name}`);
schemaCache.pushContext(schema);
let baseSchema;
if (schema.allOf && schema.allOf.length > 0 && schema.allOf[0].$ref) {
baseSchema = schemaCache.load(schema.allOf[0].$ref);
} else if (schema.$ref) {
baseSchema = schemaCache.load(schema.$ref);
}
let base = "CesiumUtility::ExtensibleObject";
if (baseSchema !== undefined) {
base = getNameFromTitle(config, baseSchema.title);
}
const required = schema.required || [];
const properties = Object.keys(schema.properties || {})
.map((key) =>
resolveProperty(
schemaCache,
config,
schema,
name,
key,
schema.properties[key],
required,
namespace,
readerNamespace,
writerNamespace
)
)
.filter((property) => property !== undefined);
const localTypes = lodash.uniq(
lodash.flatten(properties.map((property) => property.localTypes))
);
schemaCache.popContext();
let headers = lodash.uniq([
`"${namespace}/Library.h"`,
NameFormatters.getIncludeFromName(base, namespace),
...lodash.flatten(properties.map((property) => property.headers)),
]);
// Prevent header from including itself for recursive types like Tile
headers = headers.filter((header) => {
return header !== `"${namespace}/${name}.h"`;
});
headers.sort();
// prettier-ignore
const header = `
// This file was generated by generate-classes.
// DO NOT EDIT THIS FILE!
#pragma once
${headers.map((header) => `#include ${header}`).join("\n")}
namespace ${namespace} {
/**
* @brief ${schema.description ? schema.description : schema.title}
*/
struct ${namespace.toUpperCase()}_API ${name}${thisConfig.toBeInherited ? "Spec" : (thisConfig.isBaseClass ? "" : " final")} : public ${base} {
static inline constexpr const char* TypeName = "${name}";
${thisConfig.extensionName ? `static inline constexpr const char* ExtensionName = "${thisConfig.extensionName}";` : ""}
${indent(localTypes.join("\n\n"), 16)}
${indent(
properties
.map((property) => formatProperty(property))
.filter(propertyText => propertyText !== undefined)
.join("\n\n"),
16
)}
${thisConfig.toBeInherited ? privateSpecConstructor(name) : ""}
};
}
`;
const headerOutputDir = path.join(
outputDir,
"generated",
"include",
namespace
);
fs.mkdirSync(headerOutputDir, { recursive: true });
const headerOutputPath = path.join(
headerOutputDir,
`${name}${thisConfig.toBeInherited ? "Spec" : ""}.h`
);
fs.writeFileSync(headerOutputPath, unindent(header), "utf-8");
let readerHeaders = lodash.uniq([
NameFormatters.getReaderIncludeFromName(base, readerNamespace),
`<${namespace}/${name}.h>`,
...lodash.flatten(properties.map((property) => property.readerHeaders)),
]);
// Prevent header from including itself for recursive types like Tile
readerHeaders = readerHeaders.filter((readerHeader) => {
return readerHeader !== `"${name}JsonHandler.h"`;
});
readerHeaders.sort();
const readerLocalTypes = lodash.uniq(
lodash.flatten(properties.map((property) => property.readerLocalTypes))
);
const baseReader = NameFormatters.getReaderName(base, readerNamespace);
// prettier-ignore
const readerHeader = `
// This file was generated by generate-classes.
// DO NOT EDIT THIS FILE!
#pragma once
${readerHeaders.map((header) => `#include ${header}`).join("\n")}
namespace CesiumJsonReader {
class ExtensionReaderContext;
}
namespace ${readerNamespace} {
class ${name}JsonHandler : public ${baseReader}${thisConfig.extensionName ? `, public CesiumJsonReader::IExtensionJsonHandler` : ""} {
public:
using ValueType = ${namespace}::${name};
${thisConfig.extensionName ? `static inline constexpr const char* ExtensionName = "${thisConfig.extensionName}";` : ""}
${name}JsonHandler(const CesiumJsonReader::ExtensionReaderContext& context) noexcept;
void reset(IJsonHandler* pParentHandler, ${namespace}::${name}* pObject);
virtual IJsonHandler* readObjectKey(const std::string_view& str) override;
${thisConfig.extensionName ? `
virtual void reset(IJsonHandler* pParentHandler, CesiumUtility::ExtensibleObject& o, const std::string_view& extensionName) override;
virtual IJsonHandler* readNull() override {
return ${baseReader}::readNull();
};
virtual IJsonHandler* readBool(bool b) override {
return ${baseReader}::readBool(b);
}
virtual IJsonHandler* readInt32(int32_t i) override {
return ${baseReader}::readInt32(i);
}
virtual IJsonHandler* readUint32(uint32_t i) override {
return ${baseReader}::readUint32(i);
}
virtual IJsonHandler* readInt64(int64_t i) override {
return ${baseReader}::readInt64(i);
}
virtual IJsonHandler* readUint64(uint64_t i) override {
return ${baseReader}::readUint64(i);
}
virtual IJsonHandler* readDouble(double d) override {
return ${baseReader}::readDouble(d);
}
virtual IJsonHandler* readString(const std::string_view& str) override {
return ${baseReader}::readString(str);
}
virtual IJsonHandler* readObjectStart() override {
return ${baseReader}::readObjectStart();
}
virtual IJsonHandler* readObjectEnd() override {
return ${baseReader}::readObjectEnd();
}
virtual IJsonHandler* readArrayStart() override {
return ${baseReader}::readArrayStart();
}
virtual IJsonHandler* readArrayEnd() override {
return ${baseReader}::readArrayEnd();
}
virtual void reportWarning(const std::string& warning, std::vector<std::string>&& context = std::vector<std::string>()) override {
${baseReader}::reportWarning(warning, std::move(context));
}
` : ""}
protected:
IJsonHandler* readObjectKey${name}(const std::string& objectType, const std::string_view& str, ${namespace}::${name}& o);
private:
${indent(readerLocalTypes.join("\n\n"), 12)}
${namespace}::${name}* _pObject = nullptr;
${indent(
properties
.map((property) => formatReaderProperty(property))
.join("\n"),
12
)}
};
}
`;
const readerHeaderOutputDir = path.join(readerOutputDir, "generated", "src");
fs.mkdirSync(readerHeaderOutputDir, { recursive: true });
const readerHeaderOutputPath = path.join(
readerHeaderOutputDir,
name + "JsonHandler.h"
);
fs.writeFileSync(readerHeaderOutputPath, unindent(readerHeader), "utf-8");
const readerLocalTypesImpl = lodash.uniq(
lodash.flatten(properties.map((property) => property.readerLocalTypesImpl))
);
const readerHeadersImpl = lodash.uniq([
...lodash.flatten(properties.map((property) => property.readerHeadersImpl)),
]);
readerHeadersImpl.sort();
function generateReaderOptionsInitializerList(properties, varName) {
const initializerList = properties
.filter((p) => p.readerType.toLowerCase().indexOf("jsonhandler") != -1)
.map(
(p) =>
`_${p.cppSafeName}(${
p.schemas && p.schemas.length > 0 ? varName : ""
})`
)
.join(", ");
return initializerList == "" ? "" : ", " + initializerList;
}
// prettier-ignore
const readerImpl = `
// This file was generated by generate-classes.
// DO NOT EDIT THIS FILE!
#include "${name}JsonHandler.h"
#include <${namespace}/${name}.h>
${readerHeadersImpl.map((header) => `#include ${header}`).join("\n")}
#include <cassert>
#include <string>
namespace ${readerNamespace} {
${name}JsonHandler::${name}JsonHandler(const CesiumJsonReader::ExtensionReaderContext& context) noexcept : ${baseReader}(context)${generateReaderOptionsInitializerList(properties, 'context')} {}
void ${name}JsonHandler::reset(CesiumJsonReader::IJsonHandler* pParentHandler, ${namespace}::${name}* pObject) {
${baseReader}::reset(pParentHandler, pObject);
this->_pObject = pObject;
}
CesiumJsonReader::IJsonHandler* ${name}JsonHandler::readObjectKey(const std::string_view& str) {
assert(this->_pObject);
return this->readObjectKey${name}(${namespace}::${name}::TypeName, str, *this->_pObject);
}
${thisConfig.extensionName ? `
void ${name}JsonHandler::reset(CesiumJsonReader::IJsonHandler* pParentHandler, CesiumUtility::ExtensibleObject& o, const std::string_view& extensionName) {
std::any& value =
o.extensions.emplace(extensionName, ${namespace}::${name}())
.first->second;
this->reset(
pParentHandler,
&std::any_cast<${namespace}::${name}&>(value));
}
` : ""}
CesiumJsonReader::IJsonHandler* ${name}JsonHandler::readObjectKey${name}(const std::string& objectType, const std::string_view& str, ${namespace}::${name}& o) {
using namespace std::string_literals;
${properties.length > 0 ? `
${indent(
properties
.map((property) => formatReaderPropertyImpl(property))
.join("\n"),
10
)}` : `(void)o;`}
return this->readObjectKey${NameFormatters.removeNamespace(base)}(objectType, str, *this->_pObject);
}
${indent(readerLocalTypesImpl.join("\n\n"), 8)}
} // namespace ${readerNamespace}
`;
const writeForwardDeclaration = `struct ${name};`;
const writeInclude = `#include <${namespace}/${name}.h>`;
const writeDeclaration = `
struct ${name}JsonWriter {
using ValueType = ${namespace}::${name};
${
thisConfig.extensionName
? `static inline constexpr const char* ExtensionName = "${thisConfig.extensionName}";`
: ""
}
static void write(
const ${namespace}::${name}& obj,
CesiumJsonWriter::JsonWriter& jsonWriter,
const CesiumJsonWriter::ExtensionWriterContext& context);
};
`;
const writeJsonDeclaration = `
void writeJson(
const ${namespace}::${name}& obj,
CesiumJsonWriter::JsonWriter& jsonWriter,
const CesiumJsonWriter::ExtensionWriterContext& context);
`;
const writeDefinition = `
void ${name}JsonWriter::write(
const ${namespace}::${name}& obj,
CesiumJsonWriter::JsonWriter& jsonWriter,
const CesiumJsonWriter::ExtensionWriterContext& context) {
writeJson(obj, jsonWriter, context);
}
`;
let writeBaseJsonDefinition;
let writeJsonDefinition;
if (thisConfig.isBaseClass) {
writeBaseJsonDefinition = `
template <typename T>
void write${NameFormatters.getWriterName(name)}(
const T& obj,
CesiumJsonWriter::JsonWriter& jsonWriter,
const CesiumJsonWriter::ExtensionWriterContext& context) {
${indent(
properties
.map((property) => formatWriterPropertyImpl(property))
.join("\n\n"),
10
)}
write${NameFormatters.getWriterName(base)}(obj, jsonWriter, context);
}
`;
writeJsonDefinition = `
void writeJson(
const ${namespace}::${name}& obj,
CesiumJsonWriter::JsonWriter& jsonWriter,
const CesiumJsonWriter::ExtensionWriterContext& context) {
jsonWriter.StartObject();
write${NameFormatters.getWriterName(name)}(obj, jsonWriter, context);
jsonWriter.EndObject();
}
`;
} else {
writeJsonDefinition = `
void writeJson(
const ${namespace}::${name}& obj,
CesiumJsonWriter::JsonWriter& jsonWriter,
const CesiumJsonWriter::ExtensionWriterContext& context) {
jsonWriter.StartObject();
${indent(
properties
.map((property) => formatWriterPropertyImpl(property))
.join("\n\n"),
10
)}
write${NameFormatters.getWriterName(base)}(obj, jsonWriter, context);
jsonWriter.EndObject();
}
`;
}
const writeExtensionsRegistration = `
${
extensions[schema.title]
? extensions[schema.title]
.map((extension) => {
return `context.registerExtension<${namespace}::${name}, ${extension.className}JsonWriter>();`;
})
.join("\n")
: ""
}
`;
writers.push({
writeInclude,
writeForwardDeclaration,
writeDeclaration,
writeJsonDeclaration,
writeDefinition,
writeJsonDefinition,
writeBaseJsonDefinition,
writeExtensionsRegistration,
});
if (options.oneHandlerFile) {
const readerSourceOutputPath = path.join(
readerHeaderOutputDir,
"GeneratedJsonHandlers.cpp"
);
fs.appendFileSync(readerSourceOutputPath, unindent(readerImpl), "utf-8");
} else {
const readerSourceOutputPath = path.join(
readerHeaderOutputDir,
name + "JsonHandler.cpp"
);
fs.writeFileSync(readerSourceOutputPath, unindent(readerImpl), "utf-8");
}
const schemas = lodash.flatten(
properties.map((property) => property.schemas)
);
if (baseSchema && !base.includes("::")) {
schemas.push(baseSchema);
}
return lodash.uniq(schemas);
}
function formatProperty(property) {
if (!property.type) {
return undefined;
}
let result = "";
result += `/**\n * @brief ${property.briefDoc || property.cppSafeName}\n`;
if (property.fullDoc) {
result += ` *\n * ${property.fullDoc.split("\n").join("\n * ")}\n`;
}
result += ` */\n`;
result += `${property.type} ${property.cppSafeName}`;
if (property.defaultValue !== undefined) {
result += " = " + property.defaultValue;
} else if (property.needsInitialization) {
result += " = " + property.type + "()";
}
result += ";";
return result;
}
function formatReaderProperty(property) {
return `${property.readerType} _${property.cppSafeName};`;
}
function formatReaderPropertyImpl(property) {
return `if ("${property.name}"s == str) return property("${property.name}", this->_${property.cppSafeName}, o.${property.cppSafeName});`;
}
function formatWriterPropertyImpl(property) {
let result = "";
const type = property.type;
const defaultValue = property.defaultValueWriter || property.defaultValue;
const isId = property.requiredId !== undefined;
const isRequiredEnum = property.requiredEnum === true;
const isVector = type.startsWith("std::vector");
const isMap = type.startsWith("std::unordered_map");
const isOptional = type.startsWith("std::optional");
const hasDefaultValueGuard =
!isId && !isRequiredEnum && defaultValue !== undefined;
const hasDefaultVectorGuard = hasDefaultValueGuard && isVector;
const hasEmptyGuard = isVector || isMap;
const hasOptionalGuard = isOptional;
const hasNegativeIndexGuard = isId;
const hasGuard =
hasDefaultValueGuard ||
hasEmptyGuard ||
hasOptionalGuard ||
hasNegativeIndexGuard;
if (hasDefaultVectorGuard) {
result += `static const ${type} ${property.cppSafeName}Default = ${defaultValue};\n`;
result += `if (obj.${property.cppSafeName} != ${property.cppSafeName}Default) {\n`;
} else if (hasDefaultValueGuard) {
result += `if (obj.${property.cppSafeName} != ${defaultValue}) {\n`;
} else if (hasEmptyGuard) {
result += `if (!obj.${property.cppSafeName}.empty()) {\n`;
} else if (hasNegativeIndexGuard) {
result += `if (obj.${property.cppSafeName} > -1) {\n`;
} else if (hasOptionalGuard) {
result += `if (obj.${property.cppSafeName}.has_value()) {\n`;
}
result += `jsonWriter.Key("${property.name}");\n`;
result += `writeJson(obj.${property.cppSafeName}, jsonWriter, context);\n`;
if (hasGuard) {
result += "}\n";
}
return result;
}
function privateSpecConstructor(name) {
return `
private:
/**
* @brief This class is not meant to be instantiated directly. Use {@link ${name}} instead.
*/
${name}Spec() = default;
friend struct ${name};
`;
}
module.exports = generate;