Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: refactored Java to new core model #769

Merged
merged 4 commits into from
Jun 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/generators/java/JavaConstrainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<JavaRenderer> = {
export const JavaDefaultTypeMapping: TypeMapping<JavaOptions> = {
Object ({constrainedModel}): string {
return constrainedModel.name;
},
Expand Down Expand Up @@ -63,16 +63,16 @@ export const JavaDefaultTypeMapping: TypeMapping<JavaRenderer> = {
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}[]`;
Expand Down
4 changes: 2 additions & 2 deletions src/generators/java/JavaFileGenerator.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<string, unknown> | CommonInputModel, outputDirectory: string, options: JavaRenderCompleteModelOptions): Promise<OutputModel[]> {
public async generateToFiles(input: Record<string, unknown> | InputMetaModel, outputDirectory: string, options: JavaRenderCompleteModelOptions): Promise<OutputModel[]> {
let generatedModels = await this.generateCompleteModels(input, options);
generatedModels = generatedModels.filter((outputModel) => { return outputModel.modelName !== undefined; });
for (const outputModel of generatedModels) {
Expand Down
63 changes: 39 additions & 24 deletions src/generators/java/JavaGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<JavaPreset> {
collectionType: 'List' | 'Array';
namingConvention: CommonNamingConvention;
typeMapping: TypeMapping<JavaRenderer>;
typeMapping: TypeMapping<JavaOptions>;
constraints: Constraints;
}
export interface JavaRenderCompleteModelOptions {
Expand All @@ -36,9 +35,30 @@ export class JavaGenerator extends AbstractGenerator<JavaOptions, JavaRenderComp
constructor(
options: Partial<JavaOptions> = 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
}
);
}

/**
Expand All @@ -47,15 +67,13 @@ export class JavaGenerator extends AbstractGenerator<JavaOptions, JavaRenderComp
* @param model
* @param inputModel
*/
render(model: CommonModel, inputModel: CommonInputModel): Promise<RenderOutput> {
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<RenderOutput> {
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: [] }));
}

Expand All @@ -68,15 +86,14 @@ export class JavaGenerator extends AbstractGenerator<JavaOptions, JavaRenderComp
* @param inputModel
* @param options used to render the full output
*/
async renderCompleteModel(model: CommonModel, inputModel: CommonInputModel, options: JavaRenderCompleteModelOptions): Promise<RenderOutput> {
async renderCompleteModel(model: ConstrainedMetaModel, inputModel: InputMetaModel, options: JavaRenderCompleteModelOptions): Promise<RenderOutput> {
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')}
Expand All @@ -85,19 +102,17 @@ ${outputModel.result}`;
return RenderOutput.toRenderOutput({result: outputContent, renderedName: outputModel.renderedName, dependencies: outputModel.dependencies});
}

async renderClass(model: CommonModel, inputModel: CommonInputModel): Promise<RenderOutput> {
async renderClass(model: ConstrainedObjectModel, inputModel: InputMetaModel): Promise<RenderOutput> {
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<RenderOutput> {
async renderEnum(model: ConstrainedEnumModel, inputModel: InputMetaModel): Promise<RenderOutput> {
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});
}
}
10 changes: 7 additions & 3 deletions src/generators/java/JavaPreset.ts
Original file line number Diff line number Diff line change
@@ -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<O extends object = any> = Preset<{
class: ClassPreset<ClassRenderer, O>;
enum: EnumPreset<EnumRenderer, O>;
export type ClassPresetType<O> = ClassPreset<ClassRenderer, O>;
export type EnumPresetType<O> = EnumPreset<EnumRenderer, O>;

export type JavaPreset<O = JavaOptions> = Preset<{
class: ClassPresetType<O>;
enum: EnumPresetType<O>;
}>;

export const JAVA_DEFAULT_PRESET: JavaPreset = {
Expand Down
133 changes: 5 additions & 128 deletions src/generators/java/JavaRenderer.ts
Original file line number Diff line number Diff line change
@@ -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<JavaOptions, JavaGenerator> {
export abstract class JavaRenderer<RendererModelType extends ConstrainedMetaModel> extends AbstractRenderer<JavaOptions, JavaGenerator, RendererModelType> {
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');
Expand Down
Loading