diff --git a/src/generators/java/JavaConstrainer.ts b/src/generators/java/JavaConstrainer.ts index 975cdee02c..9f9a958390 100644 --- a/src/generators/java/JavaConstrainer.ts +++ b/src/generators/java/JavaConstrainer.ts @@ -2,9 +2,9 @@ import { TypeMapping } from '../../helpers'; import { defaultEnumKeyConstraints, defaultEnumValueConstraints } from './constrainer/EnumConstrainer'; import { defaultModelNameConstraints } from './constrainer/ModelNameConstrainer'; import { defaultPropertyKeyConstraints } from './constrainer/PropertyKeyConstrainer'; -import { JavaRenderer } from './JavaRenderer'; +import { JavaOptions } from './JavaGenerator'; -export const JavaDefaultTypeMapping: TypeMapping = { +export const JavaDefaultTypeMapping: TypeMapping = { Object ({constrainedModel}): string { return constrainedModel.name; }, @@ -63,16 +63,16 @@ export const JavaDefaultTypeMapping: TypeMapping = { Boolean (): string { return 'Boolean'; }, - Tuple ({renderer}): string { + Tuple ({options}): string { //Because Java have no notion of tuples (and no custom implementation), we have to render it as a list of any value. const tupleType = 'Object'; - if (renderer.options.collectionType && renderer.options.collectionType === 'List') { + if (options.collectionType && options.collectionType === 'List') { return `List<${tupleType}>`; } return `${tupleType}[]`; }, - Array ({constrainedModel, renderer}): string { - if (renderer.options.collectionType && renderer.options.collectionType === 'List') { + Array ({constrainedModel, options}): string { + if (options.collectionType && options.collectionType === 'List') { return `List<${constrainedModel.valueModel.type}>`; } return `${constrainedModel.valueModel.type}[]`; diff --git a/src/generators/java/JavaFileGenerator.ts b/src/generators/java/JavaFileGenerator.ts index e1bfb99ca1..872aab70d3 100644 --- a/src/generators/java/JavaFileGenerator.ts +++ b/src/generators/java/JavaFileGenerator.ts @@ -1,5 +1,5 @@ import { JavaGenerator, JavaRenderCompleteModelOptions } from './'; -import { CommonInputModel, OutputModel } from '../../models'; +import { InputMetaModel, OutputModel } from '../../models'; import * as path from 'path'; import { AbstractFileGenerator } from '../AbstractFileGenerator'; import { FileHelpers } from '../../helpers'; @@ -12,7 +12,7 @@ export class JavaFileGenerator extends JavaGenerator implements AbstractFileGene * @param outputDirectory where you want the models generated to * @param options */ - public async generateToFiles(input: Record | CommonInputModel, outputDirectory: string, options: JavaRenderCompleteModelOptions): Promise { + public async generateToFiles(input: Record | InputMetaModel, outputDirectory: string, options: JavaRenderCompleteModelOptions): Promise { let generatedModels = await this.generateCompleteModels(input, options); generatedModels = generatedModels.filter((outputModel) => { return outputModel.modelName !== undefined; }); for (const outputModel of generatedModels) { diff --git a/src/generators/java/JavaGenerator.ts b/src/generators/java/JavaGenerator.ts index 403857857b..7498119f7a 100644 --- a/src/generators/java/JavaGenerator.ts +++ b/src/generators/java/JavaGenerator.ts @@ -3,21 +3,20 @@ import { CommonGeneratorOptions, defaultGeneratorOptions } from '../AbstractGenerator'; -import { CommonModel, CommonInputModel, RenderOutput } from '../../models'; -import { CommonNamingConvention, CommonNamingConventionImplementation, ModelKind, TypeHelpers, TypeMapping } from '../../helpers'; +import { ConstrainedEnumModel, ConstrainedMetaModel, ConstrainedObjectModel, InputMetaModel, MetaModel, RenderOutput } from '../../models'; +import { CommonNamingConvention, CommonNamingConventionImplementation, split, TypeMapping } from '../../helpers'; import { JavaPreset, JAVA_DEFAULT_PRESET } from './JavaPreset'; import { ClassRenderer } from './renderers/ClassRenderer'; import { EnumRenderer } from './renderers/EnumRenderer'; import { isReservedJavaKeyword } from './Constants'; import { Logger } from '../../'; -import { JavaRenderer } from './JavaRenderer'; -import { Constraints } from '../../helpers/ConstrainHelpers'; +import { constrainMetaModel, Constraints } from '../../helpers/ConstrainHelpers'; import { JavaDefaultConstraints, JavaDefaultTypeMapping } from './JavaConstrainer'; export interface JavaOptions extends CommonGeneratorOptions { collectionType: 'List' | 'Array'; namingConvention: CommonNamingConvention; - typeMapping: TypeMapping; + typeMapping: TypeMapping; constraints: Constraints; } export interface JavaRenderCompleteModelOptions { @@ -36,9 +35,30 @@ export class JavaGenerator extends AbstractGenerator = JavaGenerator.defaultOptions, ) { - const mergedOptions = {...JavaGenerator.defaultOptions, ...options}; + const realizedOptions = {...JavaGenerator.defaultOptions, ...options}; - super('Java', JavaGenerator.defaultOptions, mergedOptions); + super('Java', realizedOptions); + } + + splitMetaModel(model: MetaModel): MetaModel[] { + //These are the models that we have separate renderers for + const metaModelsToSplit = { + splitEnum: true, + splitObject: true + }; + return split(model, metaModelsToSplit); + } + + constrainToMetaModel(model: MetaModel): ConstrainedMetaModel { + return constrainMetaModel( + this.options.typeMapping, + this.options.constraints, + { + metaModel: model, + options: this.options, + constrainedName: '' //This is just a placeholder, it will be constrained within the function + } + ); } /** @@ -47,15 +67,13 @@ export class JavaGenerator extends AbstractGenerator { - const kind = TypeHelpers.extractKind(model); - // We don't support union in Java generator, however, if union is an object, we render it as a class. - if (kind === ModelKind.OBJECT || (kind === ModelKind.UNION && model.type?.includes('object'))) { + render(model: ConstrainedMetaModel, inputModel: InputMetaModel): Promise { + if (model instanceof ConstrainedObjectModel) { return this.renderClass(model, inputModel); - } else if (kind === ModelKind.ENUM) { + } else if (model instanceof ConstrainedEnumModel) { return this.renderEnum(model, inputModel); - } - Logger.warn(`Java generator, cannot generate this type of model, ${model.$id}`); + } + Logger.warn(`Java generator, cannot generate this type of model, ${model.name}`); return Promise.resolve(RenderOutput.toRenderOutput({ result: '', renderedName: '', dependencies: [] })); } @@ -68,15 +86,14 @@ export class JavaGenerator extends AbstractGenerator { + async renderCompleteModel(model: ConstrainedMetaModel, inputModel: InputMetaModel, options: JavaRenderCompleteModelOptions): Promise { if (isReservedJavaKeyword(options.packageName)) { throw new Error(`You cannot use reserved Java keyword (${options.packageName}) as package name, please use another.`); } const outputModel = await this.render(model, inputModel); - const modelDependencies = model.getNearestDependencies().map((dependencyModelName) => { - const formattedDependencyModelName = this.options.namingConvention?.type ? this.options.namingConvention.type(dependencyModelName, {inputModel, model: inputModel.models[String(dependencyModelName)], reservedKeywordCallback: isReservedJavaKeyword}) : dependencyModelName; - return `import ${options.packageName}.${formattedDependencyModelName};`; + const modelDependencies = model.getNearestDependencies().map((dependencyModel) => { + return `import ${options.packageName}.${dependencyModel.name};`; }); const outputContent = `package ${options.packageName}; ${modelDependencies.join('\n')} @@ -85,19 +102,17 @@ ${outputModel.result}`; return RenderOutput.toRenderOutput({result: outputContent, renderedName: outputModel.renderedName, dependencies: outputModel.dependencies}); } - async renderClass(model: CommonModel, inputModel: CommonInputModel): Promise { + async renderClass(model: ConstrainedObjectModel, inputModel: InputMetaModel): Promise { const presets = this.getPresets('class'); const renderer = new ClassRenderer(this.options, this, presets, model, inputModel); const result = await renderer.runSelfPreset(); - const renderedName = renderer.nameType(model.$id, model); - return RenderOutput.toRenderOutput({result, renderedName, dependencies: renderer.dependencies}); + return RenderOutput.toRenderOutput({result, renderedName: model.name, dependencies: renderer.dependencies}); } - async renderEnum(model: CommonModel, inputModel: CommonInputModel): Promise { + async renderEnum(model: ConstrainedEnumModel, inputModel: InputMetaModel): Promise { const presets = this.getPresets('enum'); const renderer = new EnumRenderer(this.options, this, presets, model, inputModel); const result = await renderer.runSelfPreset(); - const renderedName = renderer.nameType(model.$id, model); - return RenderOutput.toRenderOutput({result, renderedName, dependencies: renderer.dependencies}); + return RenderOutput.toRenderOutput({result, renderedName: model.name, dependencies: renderer.dependencies}); } } diff --git a/src/generators/java/JavaPreset.ts b/src/generators/java/JavaPreset.ts index b2de02b29b..945a653ca2 100644 --- a/src/generators/java/JavaPreset.ts +++ b/src/generators/java/JavaPreset.ts @@ -1,11 +1,15 @@ /* eslint-disable @typescript-eslint/ban-types */ import { Preset, ClassPreset, EnumPreset } from '../../models'; +import { JavaOptions } from './JavaGenerator'; import { ClassRenderer, JAVA_DEFAULT_CLASS_PRESET } from './renderers/ClassRenderer'; import { EnumRenderer, JAVA_DEFAULT_ENUM_PRESET } from './renderers/EnumRenderer'; -export type JavaPreset = Preset<{ - class: ClassPreset; - enum: EnumPreset; +export type ClassPresetType = ClassPreset; +export type EnumPresetType = EnumPreset; + +export type JavaPreset = Preset<{ + class: ClassPresetType; + enum: EnumPresetType; }>; export const JAVA_DEFAULT_PRESET: JavaPreset = { diff --git a/src/generators/java/JavaRenderer.ts b/src/generators/java/JavaRenderer.ts index 22fdd800cb..69c4e7fdd0 100644 --- a/src/generators/java/JavaRenderer.ts +++ b/src/generators/java/JavaRenderer.ts @@ -1,147 +1,24 @@ import { AbstractRenderer } from '../AbstractRenderer'; import { JavaGenerator, JavaOptions } from './JavaGenerator'; -import { CommonModel, CommonInputModel, Preset } from '../../models'; -import { FormatHelpers, ModelKind, TypeHelpers } from '../../helpers'; -import { isReservedJavaKeyword } from './Constants'; +import { ConstrainedMetaModel, InputMetaModel, Preset } from '../../models'; +import { FormatHelpers } from '../../helpers'; /** * Common renderer for Java types * * @extends AbstractRenderer */ -export abstract class JavaRenderer extends AbstractRenderer { +export abstract class JavaRenderer extends AbstractRenderer { constructor( options: JavaOptions, generator: JavaGenerator, presets: Array<[Preset, unknown]>, - model: CommonModel, - inputModel: CommonInputModel, + model: RendererModelType, + inputModel: InputMetaModel, ) { super(options, generator, presets, model, inputModel); } - /** - * Renders the name of a type based on provided generator option naming convention type function. - * - * This is used to render names of models and then later used if it is referenced from other models. - * - * @param name - * @param model - */ - nameType(name: string | undefined, model?: CommonModel): string { - return this.options?.namingConvention?.type - ? this.options.namingConvention.type(name, { model: model || this.model, inputModel: this.inputModel, reservedKeywordCallback: isReservedJavaKeyword }) - : name || ''; - } - - /** - * Renders the name of a property based on provided generator option naming convention property function. - * - * @param propertyName - * @param property - */ - nameProperty(propertyName: string | undefined, property?: CommonModel): string { - return this.options?.namingConvention?.property - ? this.options.namingConvention.property(propertyName, { model: this.model, inputModel: this.inputModel, property, reservedKeywordCallback: isReservedJavaKeyword }) - : propertyName || ''; - } - - /** - * Renders model(s) to Java type(s). - * - * @param model - */ - renderType(model: CommonModel | CommonModel[]): string { - if (Array.isArray(model) || Array.isArray(model.type)) { - return 'Object'; // fallback - } - if (model.$ref !== undefined) { - return this.nameType(model.$ref, model); - } - const kind = TypeHelpers.extractKind(model); - if ( - kind === ModelKind.PRIMITIVE || - kind === ModelKind.ARRAY - ) { - const format = model.getFromOriginalInput('format'); - return this.toClassType(this.toJavaType(format || model.type, model)); - } - return this.nameType(model.$id, model); - } - - /** - * Returns the Java corresponding type from CommonModel type or JSON schema format - * @param type - * @param model - */ - toJavaType(type: string | undefined, model: CommonModel): string { - switch (type) { - case 'integer': - case 'int32': - return 'int'; - case 'long': - case 'int64': - return 'long'; - case 'boolean': - return 'boolean'; - case 'date': - return 'java.time.LocalDate'; - case 'time': - return 'java.time.OffsetTime'; - case 'dateTime': - case 'date-time': - return 'java.time.OffsetDateTime'; - case 'string': - case 'password': - case 'byte': - return 'String'; - case 'float': - return 'float'; - case 'double': - case 'number': - return 'double'; - case 'binary': - return 'byte[]'; - case 'array': { - let arrayItemModel = model.items; - //Since Java dont support tuples, lets make sure that we combine the tuple types to find the appropriate array type - if (Array.isArray(model.items)) { - arrayItemModel = model.items.reduce((prevModel, currentModel) => { - return CommonModel.mergeCommonModels(CommonModel.toCommonModel(prevModel), CommonModel.toCommonModel(currentModel), {}); - }); - //If tuples and additionalItems make sure to find the appropriate type by merging all the tuples and additionalItems model together to find the combined type. - if (model.additionalItems !== undefined) { - arrayItemModel = CommonModel.mergeCommonModels(arrayItemModel, model.additionalItems, {}); - } - } - const newType = arrayItemModel ? this.renderType(arrayItemModel) : 'Object'; - if (this.options.collectionType && this.options.collectionType === 'List') { - return `List<${newType}>`; - } - return `${newType}[]`; - } - default: - return 'Object'; - } - } - - toClassType(type: string): string { - switch (type) { - case 'int': - return 'Integer'; - case 'long': - return 'Long'; - case 'boolean': - return 'Boolean'; - case 'float': - return 'Float'; - case 'double': - return 'Double'; - default: - return type; - } - } - renderComments(lines: string | string[]): string { lines = FormatHelpers.breakLines(lines); const newLiteral = lines.map(line => ` * ${line}`).join('\n'); diff --git a/src/generators/java/presets/CommonPreset.ts b/src/generators/java/presets/CommonPreset.ts index 4b6691e6ae..0bc8c269b8 100644 --- a/src/generators/java/presets/CommonPreset.ts +++ b/src/generators/java/presets/CommonPreset.ts @@ -1,10 +1,7 @@ import { JavaRenderer } from '../JavaRenderer'; import { JavaPreset } from '../JavaPreset'; - -import { getUniquePropertyName, DefaultPropertyNames, FormatHelpers } from '../../../helpers'; -import { CommonModel } from '../../../models'; -import { Logger } from '../../../utils/LoggingInterface'; - +import { FormatHelpers } from '../../../helpers'; +import { ConstrainedArrayModel, ConstrainedObjectModel } from '../../../models'; export interface JavaCommonPresetOptions { equal: boolean; hashCode: boolean; @@ -16,18 +13,14 @@ export interface JavaCommonPresetOptions { * Render `equal` function based on model's properties */ function renderEqual({ renderer, model }: { - renderer: JavaRenderer, - model: CommonModel, + renderer: JavaRenderer, + model: ConstrainedObjectModel, }): string { - const formattedModelName = renderer.nameType(model.$id); const properties = model.properties || {}; const propertyKeys = [...Object.keys(properties)]; - if (model.additionalProperties !== undefined) { - propertyKeys.push(getUniquePropertyName(model, DefaultPropertyNames.additionalProperties)); - } - const equalProperties = propertyKeys.map(prop => { - const camelCasedProp = renderer.nameProperty(prop); - return `Objects.equals(this.${camelCasedProp}, self.${camelCasedProp})`; + + const equalProperties = propertyKeys.map(propertyName => { + return `Objects.equals(this.${propertyName}, self.${propertyName})`; }).join(' &&\n'); return `${renderer.renderAnnotation('Override')} @@ -38,7 +31,7 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - ${formattedModelName} self = (${formattedModelName}) o; + ${model.name} self = (${model.name}) o; return ${equalProperties.length > 0 ? renderer.indent(equalProperties, 6) : 'true'}; }`; @@ -48,16 +41,14 @@ ${equalProperties.length > 0 ? renderer.indent(equalProperties, 6) : 'true'}; * Render `hashCode` function based on model's properties */ function renderHashCode({ renderer, model }: { - renderer: JavaRenderer, - model: CommonModel, + renderer: JavaRenderer, + model: ConstrainedObjectModel, }): string { const properties = model.properties || {}; const propertyKeys = [...Object.keys(properties)]; - if (model.additionalProperties !== undefined) { - propertyKeys.push(getUniquePropertyName(model, DefaultPropertyNames.additionalProperties)); - } + //Object casting needed because otherwise properties with arrays fails to be compiled. - const hashProperties = propertyKeys.map(prop => `(Object)${renderer.nameProperty(prop)}`).join(', '); + const hashProperties = propertyKeys.map(propertyName => `(Object)${propertyName}`).join(', '); return `${renderer.renderAnnotation('Override')} public int hashCode() { @@ -69,23 +60,18 @@ public int hashCode() { * Render `toString` function based on model's properties */ function renderToString({ renderer, model }: { - renderer: JavaRenderer, - model: CommonModel, + renderer: JavaRenderer, + model: ConstrainedObjectModel, }): string { - const formattedModelName = renderer.nameType(model.$id); const properties = model.properties || {}; const propertyKeys = [...Object.keys(properties)]; - if (model.additionalProperties !== undefined) { - propertyKeys.push(getUniquePropertyName(model, DefaultPropertyNames.additionalProperties)); - } - const toStringProperties = propertyKeys.map(prop => { - const renderedPropertyName = renderer.nameProperty(prop); - return `" ${renderedPropertyName}: " + toIndentedString(${renderedPropertyName}) + "\\n" +`; + const toStringProperties = propertyKeys.map(propertyName => { + return `" ${propertyName}: " + toIndentedString(${propertyName}) + "\\n" +`; }); return `${renderer.renderAnnotation('Override')} public String toString() { - return "class ${formattedModelName} {\\n" + + return "class ${model.name} {\\n" + ${toStringProperties.length > 0 ? renderer.indent(renderer.renderBlock(toStringProperties), 4) : ''} "}"; } @@ -99,14 +85,15 @@ private String toIndentedString(Object o) { }`; } -function renderMarshalProperties(renderer: JavaRenderer, model: CommonModel) { +function renderMarshalProperties({ model }: { + model: ConstrainedObjectModel, +}): string { const properties = model.properties || {}; const propertyKeys = [...Object.keys(properties)]; - const marshalProperties = propertyKeys.map(prop => { - const formattedPropertyName = renderer.nameProperty(prop, model); - const modelInstanceVariable = `this.${formattedPropertyName}`; + const marshalProperties = propertyKeys.map(propertyName => { + const modelInstanceVariable = `this.${propertyName}`; return `if(${modelInstanceVariable} != null) { - propList.add("${formattedPropertyName}:"+${modelInstanceVariable}.toString()); + propList.add("${propertyName}:"+${modelInstanceVariable}.toString()); }`; }); return marshalProperties.join('\n'); @@ -115,40 +102,35 @@ function renderMarshalProperties(renderer: JavaRenderer, model: CommonModel) { * Render `marshal` function based on model's properties */ function renderMarshalling({ renderer, model }: { - renderer: JavaRenderer, - model: CommonModel, + renderer: JavaRenderer, + model: ConstrainedObjectModel, }): string { return `public String marshal() { List propList = new ArrayList(); - ${renderer.indent(renderMarshalProperties(renderer, model))} + ${renderer.indent(renderMarshalProperties({model}))} return propList.stream().collect(Collectors.joining(",")); }`; } -function renderUnmarshalProperties(renderer: JavaRenderer, model: CommonModel) { +function renderUnmarshalProperties({ model }: { + model: ConstrainedObjectModel, +}): string { const properties = model.properties || {}; - const propertyKeys = [...Object.keys(properties)]; - const unmarshalProperties = propertyKeys.map(prop => { - const formattedPropertyName = renderer.nameProperty(prop, model); - const setterFunction = `set${formattedPropertyName.charAt(0).toUpperCase()}${formattedPropertyName.slice(1)}`; - const propModel = properties[String(prop)]; - if (propModel.type === 'undefined') { - Logger.error(`Could not render unmarshal for property ${prop}`); - return; - } - if (propModel.type === 'array') { - return `if(jsonObject.has("${formattedPropertyName}")) { - JSONArray jsonArray = jsonObject.getJSONArray("${formattedPropertyName}"); - String[] ${formattedPropertyName} = new String[jsonArray.length()]; + const unmarshalProperties = Object.entries(properties).map(([propertyName, property]) => { + const setterFunction = `set${propertyName.charAt(0).toUpperCase()}${propertyName.slice(1)}`; + if (property instanceof ConstrainedArrayModel) { + return `if(jsonObject.has("${propertyName}")) { + JSONArray jsonArray = jsonObject.getJSONArray("${propertyName}"); + String[] ${propertyName} = new String[jsonArray.length()]; for(int i = 0; i < jsonArray.length(); i++) { - ${formattedPropertyName}[i] = jsonArray.getString(i); + ${propertyName}[i] = jsonArray.getString(i); } - result.${setterFunction}(${formattedPropertyName}); + result.${setterFunction}(${propertyName}); }`; } - const getType = `jsonObject.get${FormatHelpers.upperFirst(propModel.type?.toString() || '')}`; - return `if(jsonObject.has("${formattedPropertyName}")) { - result.${setterFunction}(${getType}("${formattedPropertyName}")); + const getType = `jsonObject.get${FormatHelpers.upperFirst(property.property.type)}`; + return `if(jsonObject.has("${propertyName}")) { + result.${setterFunction}(${getType}("${propertyName}")); }`; }); return unmarshalProperties.join('\n'); @@ -157,14 +139,13 @@ function renderUnmarshalProperties(renderer: JavaRenderer, model: CommonModel) { * Render `unmarshal` function based on model's properties */ function renderUnmarshalling({ renderer, model }: { - renderer: JavaRenderer, - model: CommonModel, + renderer: JavaRenderer, + model: ConstrainedObjectModel, }): string { - const formattedModelName = renderer.nameType(model.$id); - return `public static ${formattedModelName} unmarshal(String json) { - ${formattedModelName} result = new ${formattedModelName}(); + return `public static ${model.name} unmarshal(String json) { + ${model.name} result = new ${model.name}(); JSONObject jsonObject = new JSONObject(json); - ${renderer.indent(renderUnmarshalProperties(renderer, model))} + ${renderer.indent(renderUnmarshalProperties({model}))} return result; }`; } diff --git a/src/generators/java/presets/ConstraintsPreset.ts b/src/generators/java/presets/ConstraintsPreset.ts index ad1818995a..fc6304b1eb 100644 --- a/src/generators/java/presets/ConstraintsPreset.ts +++ b/src/generators/java/presets/ConstraintsPreset.ts @@ -1,57 +1,66 @@ +import { ConstrainedArrayModel, ConstrainedFloatModel, ConstrainedIntegerModel, ConstrainedStringModel } from 'models'; import { JavaPreset } from '../JavaPreset'; + /** * Preset which extends class's getters with annotations from `javax.validation.constraints` package * * @implements {JavaPreset} */ -export const JAVA_CONSTRAINTS_PRESET: JavaPreset = { +export const JAVA_CONSTRAINTS_PRESET: JavaPreset = { class: { self({renderer, content}) { renderer.addDependency('import javax.validation.constraints.*;'); return content; }, - getter({ renderer, model, propertyName, property, content }) { + getter({ renderer, property, content }) { const annotations: string[] = []; - const isRequired = model.isRequired(propertyName); - if (isRequired) { + if (property.required) { annotations.push(renderer.renderAnnotation('NotNull')); } + const originalInput = property.property.originalInput; // string - const pattern = property.getFromOriginalInput('pattern'); - if (pattern !== undefined) { - annotations.push(renderer.renderAnnotation('Pattern', { regexp: `"${pattern}"` })); - } - const minLength = property.getFromOriginalInput('minLength'); - const maxLength = property.getFromOriginalInput('maxLength'); - if (minLength !== undefined || maxLength !== undefined) { - annotations.push(renderer.renderAnnotation('Size', { min: minLength, max: maxLength })); + if (property.property instanceof ConstrainedStringModel) { + const pattern = originalInput['pattern']; + if (pattern !== undefined) { + annotations.push(renderer.renderAnnotation('Pattern', { regexp: `"${pattern}"` })); + } + const minLength = originalInput['minLength']; + const maxLength = originalInput['maxLength']; + if (minLength !== undefined || maxLength !== undefined) { + annotations.push(renderer.renderAnnotation('Size', { min: minLength, max: maxLength })); + } } // number/integer - const minimum = property.getFromOriginalInput('minimum'); - if (minimum !== undefined) { - annotations.push(renderer.renderAnnotation('Min', minimum)); - } - const exclusiveMinimum = property.getFromOriginalInput('exclusiveMinimum'); - if (exclusiveMinimum !== undefined) { - annotations.push(renderer.renderAnnotation('Min', exclusiveMinimum + 1)); - } - const maximum = property.getFromOriginalInput('maximum'); - if (maximum !== undefined) { - annotations.push(renderer.renderAnnotation('Max', maximum)); - } - const exclusiveMaximum = property.getFromOriginalInput('exclusiveMaximum'); - if (exclusiveMaximum !== undefined) { - annotations.push(renderer.renderAnnotation('Max', exclusiveMaximum - 1)); + if (property.property instanceof ConstrainedFloatModel || + property.property instanceof ConstrainedIntegerModel) { + const minimum = originalInput['minimum']; + if (minimum !== undefined) { + annotations.push(renderer.renderAnnotation('Min', minimum)); + } + const exclusiveMinimum = originalInput['exclusiveMinimum']; + if (exclusiveMinimum !== undefined) { + annotations.push(renderer.renderAnnotation('Min', exclusiveMinimum + 1)); + } + const maximum = originalInput['maximum']; + if (maximum !== undefined) { + annotations.push(renderer.renderAnnotation('Max', maximum)); + } + const exclusiveMaximum = originalInput['exclusiveMaximum']; + if (exclusiveMaximum !== undefined) { + annotations.push(renderer.renderAnnotation('Max', exclusiveMaximum - 1)); + } } // array - const minItems = property.getFromOriginalInput('minItems'); - const maxItems = property.getFromOriginalInput('maxItems'); - if (minItems !== undefined || maxItems !== undefined) { - annotations.push(renderer.renderAnnotation('Size', { min: minItems, max: maxItems })); + if (property.property instanceof ConstrainedArrayModel) { + const minItems = originalInput['minItems']; + const maxItems = originalInput['maxItems']; + if (minItems !== undefined || maxItems !== undefined) { + annotations.push(renderer.renderAnnotation('Size', { min: minItems, max: maxItems })); + } } return renderer.renderBlock([...annotations, content]); diff --git a/src/generators/java/presets/DescriptioPreset.ts b/src/generators/java/presets/DescriptioPreset.ts index acbb7df5b8..c54ffaa290 100644 --- a/src/generators/java/presets/DescriptioPreset.ts +++ b/src/generators/java/presets/DescriptioPreset.ts @@ -1,15 +1,15 @@ import { JavaRenderer } from '../JavaRenderer'; import { JavaPreset } from '../JavaPreset'; import { FormatHelpers } from '../../../helpers'; -import { CommonModel } from '../../../models'; +import { ConstrainedMetaModel } from '../../../models'; function renderDescription({ renderer, content, item }: { - renderer: JavaRenderer, + renderer: JavaRenderer, content: string, - item: CommonModel, + item: ConstrainedMetaModel, }): string { - let desc = item.getFromOriginalInput('description'); - const examples = item.getFromOriginalInput('examples'); + let desc = item.originalInput['description']; + const examples = item.originalInput['examples']; if (Array.isArray(examples)) { const renderedExamples = FormatHelpers.renderJSONExamples(examples); @@ -35,7 +35,7 @@ export const JAVA_DESCRIPTION_PRESET: JavaPreset = { return renderDescription({ renderer, content, item: model }); }, getter({ renderer, property, content }) { - return renderDescription({ renderer, content, item: property }); + return renderDescription({ renderer, content, item: property.property }); } }, enum: { diff --git a/src/generators/java/presets/JacksonPreset.ts b/src/generators/java/presets/JacksonPreset.ts index 2038ef6a10..895c77f9af 100644 --- a/src/generators/java/presets/JacksonPreset.ts +++ b/src/generators/java/presets/JacksonPreset.ts @@ -1,4 +1,4 @@ -import { PropertyType } from '../../../models'; +import { ConstrainedDictionaryModel } from '../../../models'; import { JavaPreset } from '../JavaPreset'; /** @@ -12,9 +12,12 @@ export const JAVA_JACKSON_PRESET: JavaPreset = { renderer.addDependency('import com.fasterxml.jackson.annotation.*;'); return content; }, - getter({ renderer, propertyName, content, type }) { - if (type === PropertyType.property) { - const annotation = renderer.renderAnnotation('JsonProperty', `"${propertyName}"`); + getter({ renderer, property, content }) { + //Properties that are dictionaries with unwrapped options, cannot get the annotation because it cannot be accurately unwrapped by the jackson library. + const isDictionary = property.property instanceof ConstrainedDictionaryModel; + const hasUnwrappedOptions = isDictionary && (property.property as ConstrainedDictionaryModel).serializationType === 'unwrap'; + if (!hasUnwrappedOptions) { + const annotation = renderer.renderAnnotation('JsonProperty', `"${property.propertyName}"`); return renderer.renderBlock([annotation, content]); } return renderer.renderBlock([content]); diff --git a/src/generators/java/renderers/ClassRenderer.ts b/src/generators/java/renderers/ClassRenderer.ts index f006a55ef3..2ceb7a5422 100644 --- a/src/generators/java/renderers/ClassRenderer.ts +++ b/src/generators/java/renderers/ClassRenderer.ts @@ -1,13 +1,15 @@ import { JavaRenderer } from '../JavaRenderer'; -import { CommonModel, ClassPreset, PropertyType } from '../../../models'; -import { DefaultPropertyNames, FormatHelpers, getUniquePropertyName } from '../../../helpers'; +import { ConstrainedDictionaryModel, ConstrainedObjectModel, ConstrainedObjectPropertyModel } from '../../../models'; +import { FormatHelpers } from '../../../helpers'; +import { JavaOptions } from '../JavaGenerator'; +import { ClassPresetType } from '../JavaPreset'; /** * Renderer for Java's `class` type * * @extends JavaRenderer */ -export class ClassRenderer extends JavaRenderer { +export class ClassRenderer extends JavaRenderer { async defaultSelf(): Promise { const content = [ await this.renderProperties(), @@ -19,12 +21,11 @@ export class ClassRenderer extends JavaRenderer { if (this.options?.collectionType === 'List') { this.addDependency('import java.util.List;'); } - if (this.model.additionalProperties !== undefined || this.model.patternProperties !== undefined) { + if (this.model.containsDictionaryProperty(ConstrainedDictionaryModel)) { this.addDependency('import java.util.Map;'); } - const formattedName = this.nameType(`${this.model.$id}`); - return `public class ${formattedName} { + return `public class ${this.model.name} { ${this.indent(this.renderBlock(content, 2))} }`; } @@ -40,30 +41,16 @@ ${this.indent(this.renderBlock(content, 2))} const properties = this.model.properties || {}; const content: string[] = []; - for (const [propertyName, property] of Object.entries(properties)) { - const rendererProperty = await this.runPropertyPreset(propertyName, property); + for (const property of Object.values(properties)) { + const rendererProperty = await this.runPropertyPreset(property); content.push(rendererProperty); } - - if (this.model.additionalProperties !== undefined) { - const propertyName = getUniquePropertyName(this.model, DefaultPropertyNames.additionalProperties); - const additionalProperty = await this.runPropertyPreset(propertyName, this.model.additionalProperties, PropertyType.additionalProperty); - content.push(additionalProperty); - } - - if (this.model.patternProperties !== undefined) { - for (const [pattern, patternModel] of Object.entries(this.model.patternProperties)) { - const propertyName = getUniquePropertyName(this.model, `${pattern}${DefaultPropertyNames.patternProperties}`); - const renderedPatternProperty = await this.runPropertyPreset(propertyName, patternModel, PropertyType.patternProperties); - content.push(renderedPatternProperty); - } - } return this.renderBlock(content); } - runPropertyPreset(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): Promise { - return this.runPreset('property', { propertyName, property, type}); + runPropertyPreset(property: ConstrainedObjectPropertyModel): Promise { + return this.runPreset('property', { property }); } /** @@ -73,68 +60,37 @@ ${this.indent(this.renderBlock(content, 2))} const properties = this.model.properties || {}; const content: string[] = []; - for (const [propertyName, property] of Object.entries(properties)) { - const getter = await this.runGetterPreset(propertyName, property); - const setter = await this.runSetterPreset(propertyName, property); - content.push(this.renderBlock([getter, setter])); - } - - if (this.model.additionalProperties !== undefined) { - const propertyName = getUniquePropertyName(this.model, DefaultPropertyNames.additionalProperties); - const getter = await this.runGetterPreset(propertyName, this.model.additionalProperties, PropertyType.additionalProperty); - const setter = await this.runSetterPreset(propertyName, this.model.additionalProperties, PropertyType.additionalProperty); + for (const property of Object.values(properties)) { + const getter = await this.runGetterPreset(property); + const setter = await this.runSetterPreset(property); content.push(this.renderBlock([getter, setter])); } - if (this.model.patternProperties !== undefined) { - for (const [pattern, patternModel] of Object.entries(this.model.patternProperties)) { - const propertyName = getUniquePropertyName(this.model, `${pattern}${DefaultPropertyNames.patternProperties}`); - const getter = await this.runGetterPreset(propertyName, patternModel, PropertyType.patternProperties); - const setter = await this.runSetterPreset(propertyName, patternModel, PropertyType.patternProperties); - content.push(this.renderBlock([getter, setter])); - } - } - return this.renderBlock(content, 2); } - runGetterPreset(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): Promise { - return this.runPreset('getter', { propertyName, property, type}); + runGetterPreset(property: ConstrainedObjectPropertyModel): Promise { + return this.runPreset('getter', { property }); } - runSetterPreset(propertyName: string, property: CommonModel, type: PropertyType = PropertyType.property): Promise { - return this.runPreset('setter', { propertyName, property, type}); + runSetterPreset(property: ConstrainedObjectPropertyModel): Promise { + return this.runPreset('setter', { property }); } } -export const JAVA_DEFAULT_CLASS_PRESET: ClassPreset = { +export const JAVA_DEFAULT_CLASS_PRESET: ClassPresetType = { self({ renderer }) { return renderer.defaultSelf(); }, - property({ renderer, propertyName, property, type }) { - propertyName = renderer.nameProperty(propertyName, property); - let propertyType = renderer.renderType(property); - if (type === PropertyType.additionalProperty || type === PropertyType.patternProperties) { - propertyType = `Map`; - } - return `private ${propertyType} ${propertyName};`; + property({ property }) { + return `private ${property.property.type} ${property.property.name};`; }, - getter({ renderer, propertyName, property, type }) { - const formattedPropertyName = renderer.nameProperty(propertyName, property); - const getterName = `get${FormatHelpers.toPascalCase(propertyName)}`; - let getterType = renderer.renderType(property); - if (type === PropertyType.additionalProperty || type === PropertyType.patternProperties) { - getterType = `Map`; - } - return `public ${getterType} ${getterName}() { return this.${formattedPropertyName}; }`; + getter({ property }) { + const getterName = `get${FormatHelpers.toPascalCase(property.propertyName)}`; + return `public ${property.property.type} ${getterName}() { return this.${property.property.name}; }`; }, - setter({ renderer, propertyName, property, type }) { - const formattedPropertyName = renderer.nameProperty(propertyName, property); - const setterName = FormatHelpers.toPascalCase(propertyName); - let setterType = renderer.renderType(property); - if (type === PropertyType.additionalProperty || type === PropertyType.patternProperties) { - setterType = `Map`; - } - return `public void set${setterName}(${setterType} ${formattedPropertyName}) { this.${formattedPropertyName} = ${formattedPropertyName}; }`; + setter({ property }) { + const setterName = FormatHelpers.toPascalCase(property.propertyName); + return `public void set${setterName}(${property.property.type} ${property.property.name}) { this.${property.property.name} = ${property.property.name}; }`; } }; diff --git a/src/generators/java/renderers/EnumRenderer.ts b/src/generators/java/renderers/EnumRenderer.ts index 99c92d84bf..1d1ddd0ef3 100644 --- a/src/generators/java/renderers/EnumRenderer.ts +++ b/src/generators/java/renderers/EnumRenderer.ts @@ -1,26 +1,26 @@ import { JavaRenderer } from '../JavaRenderer'; -import { EnumPreset} from '../../../models'; -import { FormatHelpers } from '../../../helpers'; +import { ConstrainedEnumModel, ConstrainedEnumValueModel} from '../../../models'; +import { EnumPresetType } from '../JavaPreset'; +import { JavaOptions } from '../JavaGenerator'; /** * Renderer for Java's `enum` type * * @extends JavaRenderer */ -export class EnumRenderer extends JavaRenderer { +export class EnumRenderer extends JavaRenderer { async defaultSelf(): Promise { const content = [ await this.renderItems(), await this.runAdditionalContentPreset() ]; - const formattedName = this.nameType(this.model.$id); - return `public enum ${formattedName} { + return `public enum ${this.model.name} { ${this.indent(this.renderBlock(content, 2))} }`; } async renderItems(): Promise { - const enums = this.model.enum || []; + const enums = this.model.values || []; const items: string[] = []; for (const value of enums) { @@ -32,71 +32,28 @@ ${this.indent(this.renderBlock(content, 2))} return `${content};`; } - normalizeKey(value: any): string { - let key; - switch (typeof value) { - case 'bigint': - case 'number': { - key = `number_${value}`; - break; - } - case 'boolean': { - key = `boolean_${value}`; - break; - } - case 'object': { - key = JSON.stringify(value); - break; - } - default: { - key = FormatHelpers.replaceSpecialCharacters(String(value), { exclude: [' '], separator: '_' }); - //Ensure no special char can be the beginning letter - if (!(/^[a-zA-Z]+$/).test(key.charAt(0))) { - key = `string_${key}`; - } - } - } - return FormatHelpers.toConstantCase(key); - } - - normalizeValue(value: any): string { - if (typeof value === 'string') { - return `"${value}"`; - } - if (typeof value === 'object') { - return `"${JSON.stringify(value).replace(/"/g, '\\"')}"`; - } - return String(value); - } - - runItemPreset(item: any): Promise { + runItemPreset(item: ConstrainedEnumValueModel): Promise { return this.runPreset('item', { item }); } } -export const JAVA_DEFAULT_ENUM_PRESET: EnumPreset = { +export const JAVA_DEFAULT_ENUM_PRESET: EnumPresetType = { self({ renderer }) { renderer.addDependency('import com.fasterxml.jackson.annotation.*;'); return renderer.defaultSelf(); }, - item({ renderer, item }) { - const key = renderer.normalizeKey(item); - const value = renderer.normalizeValue(item); - return `${key}(${value})`; + item({ item }) { + return `${item.key}(${item.value})`; }, - additionalContent({ renderer, model }) { - const enumName = renderer.nameType(model.$id); - const type = Array.isArray(model.type) ? 'Object' : model.type; - const classType = renderer.toClassType(renderer.toJavaType(type, model)); - - return `private ${classType} value; + additionalContent({ model }) { + return `private ${model.type} value; -${enumName}(${classType} value) { +${model.type}(${model.type} value) { this.value = value; } @JsonValue -public ${classType} getValue() { +public ${model.type} getValue() { return value; } @@ -106,8 +63,8 @@ public String toString() { } @JsonCreator -public static ${enumName} fromValue(${classType} value) { - for (${enumName} e : ${enumName}.values()) { +public static ${model.type} fromValue(${model.type} value) { + for (${model.type} e : ${model.type}.values()) { if (e.value.equals(value)) { return e; } diff --git a/src/generators/typescript/renderers/EnumRenderer.ts b/src/generators/typescript/renderers/EnumRenderer.ts index 4ecec568a5..69102ad660 100644 --- a/src/generators/typescript/renderers/EnumRenderer.ts +++ b/src/generators/typescript/renderers/EnumRenderer.ts @@ -1,5 +1,5 @@ import { TypeScriptRenderer } from '../TypeScriptRenderer'; -import { ConstrainedEnumModel } from '../../../models'; +import { ConstrainedEnumModel, ConstrainedEnumValueModel } from '../../../models'; import { EnumPresetType } from '../TypeScriptPreset'; import { TypeScriptOptions } from '../TypeScriptGenerator'; @@ -32,7 +32,7 @@ ${this.indent(this.renderBlock(content, 2))} return this.renderBlock(items); } - runItemPreset(item: any): Promise { + runItemPreset(item: ConstrainedEnumValueModel): Promise { return this.runPreset('item', { item }); } } diff --git a/src/models/ConstrainedMetaModel.ts b/src/models/ConstrainedMetaModel.ts index 52faf6c1ce..1f5bd8f553 100644 --- a/src/models/ConstrainedMetaModel.ts +++ b/src/models/ConstrainedMetaModel.ts @@ -7,6 +7,7 @@ export abstract class ConstrainedMetaModel extends MetaModel { public type: string) { super(name, originalInput); } + /** * Get the nearest constrained meta models for the constrained model. * @@ -77,29 +78,6 @@ export class ConstrainedObjectPropertyModel { public property: ConstrainedMetaModel) { } } -export class ConstrainedObjectModel extends ConstrainedMetaModel { - constructor( - name: string, - originalInput: any, - type: string, - public properties: { [key: string]: ConstrainedObjectPropertyModel; }) { - super(name, originalInput, type); - } - getNearestDependencies(): ConstrainedReferenceModel[] { - let dependencyModels = Object.values(this.properties).filter( - (modelProperty) => { - return modelProperty.property instanceof ConstrainedReferenceModel; - } - ).map((modelProperty) => { - return modelProperty.property as ConstrainedReferenceModel; - }); - //Ensure no self references - dependencyModels = dependencyModels.filter((referenceModel) => { - return referenceModel.name !== this.name; - }); - return dependencyModels; - } -} export class ConstrainedArrayModel extends ConstrainedMetaModel { getNearestDependencies(): ConstrainedMetaModel[] { if (this.valueModel instanceof ConstrainedReferenceModel && @@ -182,3 +160,45 @@ export class ConstrainedDictionaryModel extends ConstrainedMetaModel { super(name, originalInput, type); } } + +export class ConstrainedObjectModel extends ConstrainedMetaModel { + constructor( + name: string, + originalInput: any, + type: string, + public properties: { [key: string]: ConstrainedObjectPropertyModel; }) { + super(name, originalInput, type); + } + getNearestDependencies(): ConstrainedReferenceModel[] { + let dependencyModels = Object.values(this.properties).filter( + (modelProperty) => { + return modelProperty.property instanceof ConstrainedReferenceModel; + } + ).map((modelProperty) => { + return modelProperty.property as ConstrainedReferenceModel; + }); + //Ensure no self references + dependencyModels = dependencyModels.filter((referenceModel) => { + return referenceModel.name !== this.name; + }); + return dependencyModels; + } + + /** + * More specifics on the type setup here: https://github.com/Microsoft/TypeScript/wiki/FAQ#why-cant-i-write-typeof-t-new-t-or-instanceof-t-in-my-generic-function + * + * Generics are erased during compilation. + * This means that there is no value T at runtime inside doSomething. + * The normal pattern that people try to express here is to use the constructor function for a class either as a factory or as a runtime type check. + * In both cases, using a construct signature and providing it as a parameter will do the right thing. + * + * @param propertyType + * @returns + */ + containsDictionaryProperty(propertyType: { new(...args: any[]): T }) : boolean { + const foundPropertiesWithType = Object.values(this.properties).filter((property) => { + return property instanceof propertyType; + }); + return foundPropertiesWithType.length === 0; + } +}