Skip to content

Commit

Permalink
chore: refactored Java to new core model (#769)
Browse files Browse the repository at this point in the history
  • Loading branch information
jonaslagoni authored Jun 21, 2022
1 parent 3d0a85d commit e5121d6
Show file tree
Hide file tree
Showing 13 changed files with 245 additions and 423 deletions.
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

0 comments on commit e5121d6

Please sign in to comment.