Repository: asyncapi/modelina (ts)

Issue:
Generated model requires unknown imports 
#### Describe the bug
At the moment the generators have no way of telling the user which imports they require.

For example, for the Java generator, it generates enums with the expectation of `com.fasterxml.jackson` library to be present, however, the user has no way of knowing it is.

#### Expected behavior
I expected to be able to generate the model as is, without having to require me to dig into the generator to figure out which imports/required libraries are needed.


Pull request description:
feat: make it possible to add dependencies to rendering of models
**Description**
This PR adds a `dependencies` preset which can be used to add dependencies to the model rendering.


**Related issue(s)**
fixes #238 

Patch:
diff --git a/docs/presets.md b/docs/presets.md
index ea765df9..b8fec387 100644
--- a/docs/presets.md
+++ b/docs/presets.md
@@ -96,6 +96,19 @@ const generator = new TypeScriptGenerator({ presets: [
 ] });
 ```
 
+## Adding new dependencies
+
+Each preset hook has the possibility of adding its own dependencies that needs to be rendered for the given model. It can be done through the `addDependency` function from `renderer` property.
+
+```ts
+...
+self({ renderer, content }) {
+  renderer.addDependency('import java.util.*;');
+  return content;
+}
+...
+```
+
 ## Overriding the default preset
 
 Each implemented generator for appropriate language must have defined the default preset. However, we can override it by passing it as the `defaultPreset` parameter in the generator options. Check the example for TypeScript generator:
diff --git a/src/generators/AbstractGenerator.ts b/src/generators/AbstractGenerator.ts
index 1c899833..39bc81cb 100644
--- a/src/generators/AbstractGenerator.ts
+++ b/src/generators/AbstractGenerator.ts
@@ -2,6 +2,7 @@ import { CommonInputModel, CommonModel, OutputModel, Preset, Presets } from '../
 import { InputProcessor } from '../processors';
 import { IndentationTypes } from '../helpers';
 import { isPresetWithOptions } from '../utils';
+import { RenderOutput } from 'models/RenderOutput';
 
 export interface CommonGeneratorOptions<P extends Preset = Preset> {
   indentation?: {
@@ -33,7 +34,7 @@ export abstract class AbstractGenerator<Options extends CommonGeneratorOptions =
     this.options = this.mergeOptions(defaultOptions, passedOptions);
   }
 
-  public abstract render(model: CommonModel, inputModel: CommonInputModel): Promise<string>;
+  public abstract render(model: CommonModel, inputModel: CommonInputModel): Promise<RenderOutput>;
 
   public async process(input: Record<string, unknown>): Promise<CommonInputModel> {
     return await InputProcessor.processor.process(input);
@@ -50,8 +51,8 @@ export abstract class AbstractGenerator<Options extends CommonGeneratorOptions =
   protected generateModels(inputModel: CommonInputModel): Promise<OutputModel[]> {
     const models = inputModel.models;
     const renders = Object.entries(models).map(async ([modelName, model]) => {
-      const result = await this.render(model, inputModel);
-      return OutputModel.toOutputModel({ result, model, modelName, inputModel });
+      const renderedOutput = await this.render(model, inputModel);
+      return OutputModel.toOutputModel({ result: renderedOutput.result, model, modelName, inputModel, dependencies: renderedOutput.dependencies});
     });
     return Promise.all(renders);
   }
diff --git a/src/generators/AbstractRenderer.ts b/src/generators/AbstractRenderer.ts
index db74ef0d..edb68ad8 100644
--- a/src/generators/AbstractRenderer.ts
+++ b/src/generators/AbstractRenderer.ts
@@ -11,8 +11,20 @@ export abstract class AbstractRenderer<O extends CommonGeneratorOptions = Common
     protected readonly presets: Array<[Preset, unknown]>,
     protected readonly model: CommonModel, 
     protected readonly inputModel: CommonInputModel,
+    public dependencies: string[] = []
   ) {}
 
+  /**
+   * Adds a dependency while ensuring that only one dependency is preset at a time.
+   * 
+   * @param dependency complete dependency string so it can be rendered as is.
+   */
+  addDependency(dependency: string): void {
+    if (!this.dependencies.includes(dependency)) {
+      this.dependencies.push(dependency);
+    }
+  }
+
   renderLine(line: string): string {
     return `${line}\n`;
   }
@@ -35,16 +47,15 @@ export abstract class AbstractRenderer<O extends CommonGeneratorOptions = Common
   runSelfPreset(): Promise<string> {
     return this.runPreset('self');
   }
-
+  
   runAdditionalContentPreset(): Promise<string> {
     return this.runPreset('additionalContent');
   }
-
-  async runPreset(
+  async runPreset<RT = string>(
     functionName: string,
     params: Record<string, unknown> = {},
-  ): Promise<string> {
-    let content = '';
+  ): Promise<RT> {
+    let content;
     for (const [preset, options] of this.presets) {
       if (typeof preset[String(functionName)] === 'function') {
         content = await preset[String(functionName)]({ 
diff --git a/src/generators/java/JavaGenerator.ts b/src/generators/java/JavaGenerator.ts
index 06c09720..1cdaa030 100644
--- a/src/generators/java/JavaGenerator.ts
+++ b/src/generators/java/JavaGenerator.ts
@@ -3,11 +3,9 @@ import {
   CommonGeneratorOptions,
   defaultGeneratorOptions,
 } from '../AbstractGenerator';
-import { CommonModel, CommonInputModel } from '../../models';
+import { CommonModel, CommonInputModel, RenderOutput } from '../../models';
 import { ModelKind, TypeHelpers } from '../../helpers';
-
 import { JavaPreset, JAVA_DEFAULT_PRESET } from './JavaPreset';
-
 import { ClassRenderer } from './renderers/ClassRenderer';
 import { EnumRenderer } from './renderers/EnumRenderer';
 
@@ -25,7 +23,7 @@ export class JavaGenerator extends AbstractGenerator<JavaOptions> {
     super('Java', JavaGenerator.defaultOptions, options);
   }
 
-  render(model: CommonModel, inputModel: CommonInputModel): Promise<string> {
+  render(model: CommonModel, inputModel: CommonInputModel): Promise<RenderOutput> {
     const kind = TypeHelpers.extractKind(model);
     if (kind === ModelKind.ENUM) {
       return this.renderEnum(model, inputModel);
@@ -33,15 +31,17 @@ export class JavaGenerator extends AbstractGenerator<JavaOptions> {
     return this.renderClass(model, inputModel);
   }
 
-  renderClass(model: CommonModel, inputModel: CommonInputModel): Promise<string> {
+  async renderClass(model: CommonModel, inputModel: CommonInputModel): Promise<RenderOutput> {
     const presets = this.getPresets('class');
     const renderer = new ClassRenderer(this.options, presets, model, inputModel);
-    return renderer.runSelfPreset();
+    const result = await renderer.runSelfPreset();
+    return RenderOutput.toRenderOutput({result, dependencies: renderer.dependencies});
   }
 
-  renderEnum(model: CommonModel, inputModel: CommonInputModel): Promise<string> {
+  async renderEnum(model: CommonModel, inputModel: CommonInputModel): Promise<RenderOutput> {
     const presets = this.getPresets('enum'); 
     const renderer = new EnumRenderer(this.options, presets, model, inputModel);
-    return renderer.runSelfPreset();
+    const result = await renderer.runSelfPreset();
+    return RenderOutput.toRenderOutput({result, dependencies: renderer.dependencies});
   }
 }
diff --git a/src/generators/java/presets/CommonPreset.ts b/src/generators/java/presets/CommonPreset.ts
index 1ecaccc5..02a6732a 100644
--- a/src/generators/java/presets/CommonPreset.ts
+++ b/src/generators/java/presets/CommonPreset.ts
@@ -88,7 +88,6 @@ private String toIndentedString(Object o) {
  * 
  * @implements {JavaPreset}
  */
-/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
 export const JAVA_COMMON_PRESET: JavaPreset = {
   class: {
     additionalContent({ renderer, model, content, options }) {
diff --git a/src/generators/java/presets/ConstraintsPreset.ts b/src/generators/java/presets/ConstraintsPreset.ts
index 5c8c2f1d..b32655fc 100644
--- a/src/generators/java/presets/ConstraintsPreset.ts
+++ b/src/generators/java/presets/ConstraintsPreset.ts
@@ -7,9 +7,12 @@ import { CommonModel } from '../../../models';
  * 
  * @implements {JavaPreset}
  */
-/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
 export const JAVA_CONSTRAINTS_PRESET: JavaPreset = {
   class: {
+    self({renderer, content}) {
+      renderer.addDependency('import javax.validation.constraints.*;');
+      return content;
+    },
     getter({ renderer, model, propertyName, property, content }) {
       if (!(property instanceof CommonModel)) {
         return content;
diff --git a/src/generators/java/presets/DescriptioPreset.ts b/src/generators/java/presets/DescriptioPreset.ts
index 0d76cb75..47104346 100644
--- a/src/generators/java/presets/DescriptioPreset.ts
+++ b/src/generators/java/presets/DescriptioPreset.ts
@@ -34,7 +34,6 @@ function renderDescription({ renderer, content, item }: {
  * 
  * @implements {JavaPreset}
  */
-/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
 export const JAVA_DESCRIPTION_PRESET: JavaPreset = {
   class: {
     self({ renderer, model, content }) {
diff --git a/src/generators/java/presets/JacksonPreset.ts b/src/generators/java/presets/JacksonPreset.ts
index 2cc453f5..dddbc316 100644
--- a/src/generators/java/presets/JacksonPreset.ts
+++ b/src/generators/java/presets/JacksonPreset.ts
@@ -5,9 +5,12 @@ import { JavaPreset } from '../JavaPreset';
  * 
  * @implements {JavaPreset}
  */
-/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
 export const JAVA_JACKSON_PRESET: JavaPreset = {
   class: {
+    self({renderer, content}) {
+      renderer.addDependency('import com.fasterxml.jackson.annotations.*;');
+      return content;
+    },
     getter({ renderer, propertyName, content }) {
       const annotation = renderer.renderAnnotation('JsonProperty', `"${propertyName}"`);
       return renderer.renderBlock([annotation, content]);
diff --git a/src/generators/java/renderers/ClassRenderer.ts b/src/generators/java/renderers/ClassRenderer.ts
index 7a3ad444..d1cb388b 100644
--- a/src/generators/java/renderers/ClassRenderer.ts
+++ b/src/generators/java/renderers/ClassRenderer.ts
@@ -16,7 +16,7 @@ export class ClassRenderer extends JavaRenderer {
       await this.renderAccessors(),
       await this.runAdditionalContentPreset(),
     ];
-
+    
     const formattedName = this.model.$id && FormatHelpers.toPascalCase(this.model.$id);
     return `public class ${formattedName} {
 ${this.indent(this.renderBlock(content, 2))}
diff --git a/src/generators/java/renderers/EnumRenderer.ts b/src/generators/java/renderers/EnumRenderer.ts
index 52e3caba..6321006d 100644
--- a/src/generators/java/renderers/EnumRenderer.ts
+++ b/src/generators/java/renderers/EnumRenderer.ts
@@ -1,6 +1,5 @@
 import { JavaRenderer } from '../JavaRenderer';
-
-import { EnumPreset } from '../../../models';
+import { EnumPreset} from '../../../models';
 import { FormatHelpers } from '../../../helpers';
 
 /**
@@ -12,7 +11,7 @@ export class EnumRenderer extends JavaRenderer {
   async defaultSelf(): Promise<string> {
     const content = [
       await this.renderItems(),
-      await this.runAdditionalContentPreset(),
+      await this.runAdditionalContentPreset()
     ];
 
     const formattedName = this.model.$id && FormatHelpers.toPascalCase(this.model.$id);
@@ -61,6 +60,7 @@ ${this.indent(this.renderBlock(content, 2))}
 
 export const JAVA_DEFAULT_ENUM_PRESET: EnumPreset<EnumRenderer> = {
   self({ renderer }) {
+    renderer.addDependency('import com.fasterxml.jackson.annotations.*;');
     return renderer.defaultSelf();
   },
   item({ renderer, item }) {
diff --git a/src/generators/javascript/JavaScriptGenerator.ts b/src/generators/javascript/JavaScriptGenerator.ts
index 00f9c2ff..4306a060 100644
--- a/src/generators/javascript/JavaScriptGenerator.ts
+++ b/src/generators/javascript/JavaScriptGenerator.ts
@@ -3,7 +3,7 @@ import {
   CommonGeneratorOptions,
   defaultGeneratorOptions,
 } from '../AbstractGenerator';
-import { CommonModel, CommonInputModel } from '../../models';
+import { CommonModel, CommonInputModel, RenderOutput } from '../../models';
 import { TypeHelpers, ModelKind } from '../../helpers';
 
 import { JavaScriptPreset, JS_DEFAULT_PRESET } from './JavaScriptPreset';
@@ -27,17 +27,18 @@ export class JavaScriptGenerator extends AbstractGenerator<JavaScriptOptions> {
     super('JavaScript', JavaScriptGenerator.defaultOptions, options);
   }
 
-  render(model: CommonModel, inputModel: CommonInputModel): Promise<string> {
+  render(model: CommonModel, inputModel: CommonInputModel): Promise<RenderOutput> {
     const kind = TypeHelpers.extractKind(model);
     if (kind === ModelKind.OBJECT) {
       return this.renderClass(model, inputModel);
     }
-    return Promise.resolve('');
+    return Promise.resolve(RenderOutput.toRenderOutput({result: '', dependencies: []}));
   }
 
-  renderClass(model: CommonModel, inputModel: CommonInputModel): Promise<string> {
+  async renderClass(model: CommonModel, inputModel: CommonInputModel): Promise<RenderOutput> {
     const presets = this.getPresets('class'); 
     const renderer = new ClassRenderer(this.options, presets, model, inputModel);
-    return renderer.runSelfPreset();
+    const result = await renderer.runSelfPreset();
+    return RenderOutput.toRenderOutput({result, dependencies: renderer.dependencies});
   }
 }
diff --git a/src/generators/javascript/renderers/ClassRenderer.ts b/src/generators/javascript/renderers/ClassRenderer.ts
index 8e67ad34..db9ce029 100644
--- a/src/generators/javascript/renderers/ClassRenderer.ts
+++ b/src/generators/javascript/renderers/ClassRenderer.ts
@@ -14,9 +14,9 @@ export class ClassRenderer extends JavaScriptRenderer {
       await this.renderProperties(),
       await this.runCtorPreset(),
       await this.renderAccessors(),
-      await this.runAdditionalContentPreset(),
+      await this.runAdditionalContentPreset()
     ];
-
+    
     const formattedName = this.model.$id && FormatHelpers.toPascalCase(this.model.$id);
     return `class ${formattedName} {
 ${this.indent(this.renderBlock(content, 2))}
diff --git a/src/generators/typescript/TypeScriptGenerator.ts b/src/generators/typescript/TypeScriptGenerator.ts
index 7b683251..8a3cfbe2 100644
--- a/src/generators/typescript/TypeScriptGenerator.ts
+++ b/src/generators/typescript/TypeScriptGenerator.ts
@@ -3,11 +3,9 @@ import {
   CommonGeneratorOptions,
   defaultGeneratorOptions,
 } from '../AbstractGenerator';
-import { CommonModel, CommonInputModel } from '../../models';
+import { CommonModel, CommonInputModel, RenderOutput } from '../../models';
 import { TypeHelpers, ModelKind } from '../../helpers';
-
 import { TypeScriptPreset, TS_DEFAULT_PRESET } from './TypeScriptPreset';
-
 import { ClassRenderer } from './renderers/ClassRenderer';
 import { InterfaceRenderer } from './renderers/InterfaceRenderer';
 import { EnumRenderer } from './renderers/EnumRenderer';
@@ -35,7 +33,7 @@ export class TypeScriptGenerator extends AbstractGenerator<TypeScriptOptions> {
     super('TypeScript', TypeScriptGenerator.defaultOptions, options);
   }
 
-  render(model: CommonModel, inputModel: CommonInputModel): Promise<string> {
+  render(model: CommonModel, inputModel: CommonInputModel): Promise<RenderOutput> {
     const kind = TypeHelpers.extractKind(model);
     switch (kind) {
     case ModelKind.OBJECT: {
@@ -48,31 +46,35 @@ export class TypeScriptGenerator extends AbstractGenerator<TypeScriptOptions> {
     }
   }
 
-  renderClass(model: CommonModel, inputModel: CommonInputModel): Promise<string> {
+  async renderClass(model: CommonModel, inputModel: CommonInputModel): Promise<RenderOutput> {
     const presets = this.getPresets('class'); 
     const renderer = new ClassRenderer(this.options, presets, model, inputModel);
-    return renderer.runSelfPreset();
+    const result = await renderer.runSelfPreset();
+    return RenderOutput.toRenderOutput({result, dependencies: renderer.dependencies});
   }
 
-  renderInterface(model: CommonModel, inputModel: CommonInputModel): Promise<string> {
+  async renderInterface(model: CommonModel, inputModel: CommonInputModel): Promise<RenderOutput> {
     const presets = this.getPresets('interface'); 
     const renderer = new InterfaceRenderer(this.options, presets, model, inputModel);
-    return renderer.runSelfPreset();
+    const result = await renderer.runSelfPreset();
+    return RenderOutput.toRenderOutput({result, dependencies: renderer.dependencies});
   }
 
-  renderEnum(model: CommonModel, inputModel: CommonInputModel): Promise<string> {
+  async renderEnum(model: CommonModel, inputModel: CommonInputModel): Promise<RenderOutput> {
     const presets = this.getPresets('enum'); 
     const renderer = new EnumRenderer(this.options, presets, model, inputModel);
-    return renderer.runSelfPreset();
+    const result = await renderer.runSelfPreset();
+    return RenderOutput.toRenderOutput({result, dependencies: renderer.dependencies});
   }
 
-  renderType(model: CommonModel, inputModel: CommonInputModel): Promise<string> {
+  async renderType(model: CommonModel, inputModel: CommonInputModel): Promise<RenderOutput> {
     const presets = this.getPresets('type'); 
     const renderer = new TypeRenderer(this.options, presets, model, inputModel);
-    return renderer.runSelfPreset();
+    const result = await renderer.runSelfPreset();
+    return RenderOutput.toRenderOutput({result, dependencies: renderer.dependencies});
   }
 
-  private renderModelType(model: CommonModel, inputModel: CommonInputModel): Promise<string> {
+  private renderModelType(model: CommonModel, inputModel: CommonInputModel): Promise<RenderOutput> {
     const modelType = this.options.modelType;
     if (modelType === 'interface') {
       return this.renderInterface(model, inputModel);
diff --git a/src/generators/typescript/renderers/ClassRenderer.ts b/src/generators/typescript/renderers/ClassRenderer.ts
index ceb81cd4..a5916de8 100644
--- a/src/generators/typescript/renderers/ClassRenderer.ts
+++ b/src/generators/typescript/renderers/ClassRenderer.ts
@@ -14,7 +14,7 @@ export class ClassRenderer extends TypeScriptRenderer {
       await this.renderProperties(),
       await this.runCtorPreset(),
       await this.renderAccessors(),
-      await this.runAdditionalContentPreset(),
+      await this.runAdditionalContentPreset()
     ];
 
     const formattedName = this.model.$id && FormatHelpers.toPascalCase(this.model.$id);
diff --git a/src/generators/typescript/renderers/EnumRenderer.ts b/src/generators/typescript/renderers/EnumRenderer.ts
index c70c0666..3fa6adb8 100644
--- a/src/generators/typescript/renderers/EnumRenderer.ts
+++ b/src/generators/typescript/renderers/EnumRenderer.ts
@@ -12,7 +12,7 @@ export class EnumRenderer extends TypeScriptRenderer {
   async defaultSelf(): Promise<string> {
     const content = [
       await this.renderItems(),
-      await this.runAdditionalContentPreset(),
+      await this.runAdditionalContentPreset()
     ];
 
     const formattedName = this.model.$id && FormatHelpers.toPascalCase(this.model.$id);
diff --git a/src/generators/typescript/renderers/InterfaceRenderer.ts b/src/generators/typescript/renderers/InterfaceRenderer.ts
index f40113b6..5616b282 100644
--- a/src/generators/typescript/renderers/InterfaceRenderer.ts
+++ b/src/generators/typescript/renderers/InterfaceRenderer.ts
@@ -12,7 +12,7 @@ export class InterfaceRenderer extends TypeScriptRenderer {
   async defaultSelf(): Promise<string> {
     const content = [
       await this.renderProperties(),
-      await this.runAdditionalContentPreset(),
+      await this.runAdditionalContentPreset()
     ];
 
     const formattedName = this.model.$id && FormatHelpers.toPascalCase(this.model.$id);
diff --git a/src/models/OutputModel.ts b/src/models/OutputModel.ts
index c21ccea1..748e9197 100644
--- a/src/models/OutputModel.ts
+++ b/src/models/OutputModel.ts
@@ -6,6 +6,7 @@ export interface ToOutputModelArg {
   model: CommonModel;
   modelName: string;
   inputModel: CommonInputModel;
+  dependencies: string[];
 }
 
 /**
@@ -17,14 +18,15 @@ export class OutputModel {
     public readonly model: CommonModel,
     public readonly modelName: string,
     public readonly inputModel: CommonInputModel,
+    public readonly dependencies: string[]
   ) {}
 
   static toOutputModel(args: ToOutputModelArg): OutputModel;
   static toOutputModel(args: Array<ToOutputModelArg>): Array<OutputModel>;
   static toOutputModel(args: ToOutputModelArg | Array<ToOutputModelArg>): OutputModel | Array<OutputModel> {
     if (Array.isArray(args)) {
-      return args.map(arg => new this(arg.result, arg.model, arg.modelName, arg.inputModel));
+      return args.map(arg => new this(arg.result, arg.model, arg.modelName, arg.inputModel, arg.dependencies));
     }
-    return new this(args.result, args.model, args.modelName, args.inputModel);
+    return new this(args.result, args.model, args.modelName, args.inputModel, args.dependencies);
   }
 }
diff --git a/src/models/RenderOutput.ts b/src/models/RenderOutput.ts
new file mode 100644
index 00000000..a94690da
--- /dev/null
+++ b/src/models/RenderOutput.ts
@@ -0,0 +1,18 @@
+export interface ToRenderOutputArg {
+  result: string;
+  dependencies?: string[];
+}
+
+/**
+ * Common representation for the rendered output.
+ */
+export class RenderOutput {
+  constructor(
+    public readonly result: string,
+    public readonly dependencies: string[] = []
+  ) {}
+
+  static toRenderOutput(args: ToRenderOutputArg): RenderOutput {
+    return new this(args.result, args.dependencies);
+  }
+}
diff --git a/src/models/index.ts b/src/models/index.ts
index 4f60d88a..7cad3a77 100644
--- a/src/models/index.ts
+++ b/src/models/index.ts
@@ -1,5 +1,6 @@
 export * from './CommonInputModel';
 export * from './CommonModel';
+export * from './RenderOutput';
 export * from './CommonSchema';
 export * from './OutputModel';
 export * from './Preset';


Test patch:
diff --git a/test/blackbox/Dummy.spec.ts b/test/blackbox/Dummy.spec.ts
index 55f1b0f6..fe527d78 100644
--- a/test/blackbox/Dummy.spec.ts
+++ b/test/blackbox/Dummy.spec.ts
@@ -1,7 +1,7 @@
 import * as path from 'path';
 import { TypeScriptGenerator, JavaGenerator, JavaScriptGenerator } from '../../src';
 import { execCommand, generateModels, renderModels } from './utils/Utils';
-import { renderModelsToSeparateFiles } from './utils/Utils';
+import { renderJavaModelsToSeparateFiles } from './utils/Utils';
 const fileToGenerate = path.resolve(__dirname, './docs/dummy.json');
 describe('Dummy JSON Schema file', () => {
   jest.setTimeout(100000);
@@ -11,7 +11,7 @@ describe('Dummy JSON Schema file', () => {
       const generatedModels = await generateModels(fileToGenerate, generator);
       expect(generatedModels).not.toHaveLength(0);
       const renderOutputPath = path.resolve(__dirname, './output/java');
-      await renderModelsToSeparateFiles(generatedModels, renderOutputPath);
+      await renderJavaModelsToSeparateFiles(generatedModels, renderOutputPath);
       const compileCommand = `javac ${path.resolve(renderOutputPath, '*.java')}`;
       await execCommand(compileCommand);
     });
diff --git a/test/blackbox/utils/Utils.ts b/test/blackbox/utils/Utils.ts
index 64081275..06f1e1b9 100644
--- a/test/blackbox/utils/Utils.ts
+++ b/test/blackbox/utils/Utils.ts
@@ -40,12 +40,16 @@ export async function execCommand(command: string) : Promise<void> {
  * @param generatedModels to write to file
  * @param outputPath absolute path to output directory
  */
-export async function renderModelsToSeparateFiles(generatedModels: OutputModel[], outputPath: string): Promise<void> {
+export async function renderJavaModelsToSeparateFiles(generatedModels: OutputModel[], outputPath: string): Promise<void> {
   await fs.rm(outputPath, { recursive: true, force: true });
   await fs.mkdir(outputPath, { recursive: true });
   for (const outputModel of generatedModels) {
     const outputFilePath = path.resolve(outputPath, `${FormatHelpers.toPascalCase(outputModel.model.$id || 'undefined')}.java`);
-    await fs.writeFile(outputFilePath, outputModel.result);
+    const outputContent = `
+${outputModel.dependencies.join('\n')}
+${outputModel.result}
+`;
+    await fs.writeFile(outputFilePath, outputContent);
   }
 }
 
diff --git a/test/generators/AbstractGenerator.spec.ts b/test/generators/AbstractGenerator.spec.ts
index 93ac4951..0977afc0 100644
--- a/test/generators/AbstractGenerator.spec.ts
+++ b/test/generators/AbstractGenerator.spec.ts
@@ -1,5 +1,5 @@
 import { AbstractGenerator } from '../../src/generators'; 
-import { CommonInputModel, CommonModel } from '../../src/models';
+import { CommonInputModel, CommonModel, RenderOutput } from '../../src/models';
 
 describe('AbstractGenerator', () => {
   class TestGenerator extends AbstractGenerator {
@@ -8,7 +8,7 @@ describe('AbstractGenerator', () => {
     }
 
     render(model: CommonModel, inputModel: CommonInputModel): any {
-      return model.$id || 'rendered content';
+      return RenderOutput.toRenderOutput({result: model.$id || 'rendered content', dependencies: []});
     }
   }
 
@@ -53,7 +53,8 @@ describe('AbstractGenerator', () => {
     const keys = Object.keys(commonInputModel.models);
     const renderedContent = await generator.render(commonInputModel.models[keys[0]], commonInputModel);
 
-    expect(renderedContent).toEqual('SomeModel');
+    expect(renderedContent.result).toEqual('SomeModel');
+    expect(renderedContent.dependencies).toEqual([]);
   });
 
   describe('getPresets()', () => {
@@ -62,9 +63,8 @@ describe('AbstractGenerator', () => {
         constructor() {
           super('TestGenerator', {presets: [{preset: {test: 'test2'}, options: {}}]});
         }
-        render(model: CommonModel, inputModel: CommonInputModel): any {
-          return model.$id || 'rendered content';
-        }
+        render(model: CommonModel, inputModel: CommonInputModel): any { return; }
+
         testGetPresets(string: string) {
           return this.getPresets(string);
         }
diff --git a/test/generators/AbstractRenderer.spec.ts b/test/generators/AbstractRenderer.spec.ts
index a8cf323c..5b17825f 100644
--- a/test/generators/AbstractRenderer.spec.ts
+++ b/test/generators/AbstractRenderer.spec.ts
@@ -30,6 +30,17 @@ describe('AbstractRenderer', () => {
     expect(content).toEqual('Test1\nTest2');
   });
 
+  describe('addDependency()', () => {
+    test('should add dependency', () => {
+      renderer.addDependency('test');
+      expect(renderer.dependencies).toEqual(['test']);
+    });
+    test('should not add duplicate dependency', () => {
+      renderer.addDependency('test');
+      renderer.addDependency('test');
+      expect(renderer.dependencies).toEqual(['test']);
+    });
+  });
   describe('indent()', () => {
     test('should render text with indentation', () => {
       const content = renderer.indent('Test');
diff --git a/test/generators/java/JavaGenerator.spec.ts b/test/generators/java/JavaGenerator.spec.ts
index fc75c0f3..39d06556 100644
--- a/test/generators/java/JavaGenerator.spec.ts
+++ b/test/generators/java/JavaGenerator.spec.ts
@@ -56,10 +56,12 @@ describe('JavaGenerator', () => {
     const model = inputModel.models['Address'];
 
     let classModel = await generator.renderClass(model, inputModel);
-    expect(classModel).toEqual(expected);
+    expect(classModel.result).toEqual(expected);
+    expect(classModel.dependencies).toEqual([]);
 
     classModel = await generator.render(model, inputModel);
-    expect(classModel).toEqual(expected);
+    expect(classModel.result).toEqual(expected);
+    expect(classModel.dependencies).toEqual([]);
   });
 
   test('should work custom preset for `class` type', async () => {
@@ -103,10 +105,12 @@ describe('JavaGenerator', () => {
     const model = inputModel.models['CustomClass'];
 
     let classModel = await generator.renderClass(model, inputModel);
-    expect(classModel).toEqual(expected);
+    expect(classModel.result).toEqual(expected);
+    expect(classModel.dependencies).toEqual([]);
 
     classModel = await generator.render(model, inputModel);
-    expect(classModel).toEqual(expected);
+    expect(classModel.result).toEqual(expected);
+    expect(classModel.dependencies).toEqual([]);
   });
 
   test('should render `enum` type (string type)', async () => {
@@ -149,10 +153,13 @@ describe('JavaGenerator', () => {
     const model = inputModel.models['States'];
 
     let enumModel = await generator.renderEnum(model, inputModel);
-    expect(enumModel).toEqual(expected);
+    const expectedDependencies = ['import com.fasterxml.jackson.annotations.*;'];
+    expect(enumModel.result).toEqual(expected);
+    expect(enumModel.dependencies).toEqual(expectedDependencies);
 
     enumModel = await generator.render(model, inputModel);
-    expect(enumModel).toEqual(expected);
+    expect(enumModel.result).toEqual(expected);
+    expect(enumModel.dependencies).toEqual(expectedDependencies);
   });
 
   test('should render `enum` type (integer type)', async () => {
@@ -195,10 +202,13 @@ describe('JavaGenerator', () => {
     const model = inputModel.models['Numbers'];
 
     let enumModel = await generator.renderEnum(model, inputModel);
-    expect(enumModel).toEqual(expected);
+    const expectedDependencies = ['import com.fasterxml.jackson.annotations.*;'];
+    expect(enumModel.result).toEqual(expected);
+    expect(enumModel.dependencies).toEqual(expectedDependencies);
 
     enumModel = await generator.render(model, inputModel);
-    expect(enumModel).toEqual(expected);
+    expect(enumModel.result).toEqual(expected);
+    expect(enumModel.dependencies).toEqual(expectedDependencies);
   });
 
   test('should render `enum` type (union type)', async () => {
@@ -241,10 +251,13 @@ describe('JavaGenerator', () => {
     const model = inputModel.models['Union'];
 
     let enumModel = await generator.renderEnum(model, inputModel);
-    expect(enumModel).toEqual(expected);
+    const expectedDependencies = ['import com.fasterxml.jackson.annotations.*;'];
+    expect(enumModel.result).toEqual(expected);
+    expect(enumModel.dependencies).toEqual(expectedDependencies);
 
     enumModel = await generator.render(model, inputModel);
-    expect(enumModel).toEqual(expected);
+    expect(enumModel.result).toEqual(expected);
+    expect(enumModel.dependencies).toEqual(expectedDependencies);
   });
 
   test('should work custom preset for `enum` type', async () => {
@@ -299,9 +312,12 @@ public enum CustomEnum {
     const model = inputModel.models['CustomEnum'];
     
     let enumModel = await generator.render(model, inputModel);
-    expect(enumModel).toEqual(expected);
+    const expectedDependencies = ['import com.fasterxml.jackson.annotations.*;'];
+    expect(enumModel.result).toEqual(expected);
+    expect(enumModel.dependencies).toEqual(expectedDependencies);
     
     enumModel = await generator.renderEnum(model, inputModel);
-    expect(enumModel).toEqual(expected);
+    expect(enumModel.result).toEqual(expected);
+    expect(enumModel.dependencies).toEqual(expectedDependencies);
   });
 });
diff --git a/test/generators/java/presets/CommonPreset.spec.ts b/test/generators/java/presets/CommonPreset.spec.ts
index 093b319e..532a9fc4 100644
--- a/test/generators/java/presets/CommonPreset.spec.ts
+++ b/test/generators/java/presets/CommonPreset.spec.ts
@@ -64,7 +64,8 @@ describe('JAVA_COMMON_PRESET', () => {
     const model = inputModel.models['Clazz'];
 
     const classModel = await generator.renderClass(model, inputModel);
-    expect(classModel).toEqual(expected);
+    expect(classModel.result).toEqual(expected);
+    expect(classModel.dependencies).toEqual([]);
   });
 
   test('should skip rendering of disabled functions', async () => {
@@ -103,6 +104,7 @@ describe('JAVA_COMMON_PRESET', () => {
     const model = inputModel.models['Clazz'];
 
     const classModel = await generator.renderClass(model, inputModel);
-    expect(classModel).toEqual(expected);
+    expect(classModel.result).toEqual(expected);
+    expect(classModel.dependencies).toEqual([]);
   });
 });
diff --git a/test/generators/java/presets/ConstraintsPreset.spec.ts b/test/generators/java/presets/ConstraintsPreset.spec.ts
index 39d78611..4e442ebb 100644
--- a/test/generators/java/presets/ConstraintsPreset.spec.ts
+++ b/test/generators/java/presets/ConstraintsPreset.spec.ts
@@ -1,6 +1,6 @@
 import { JavaGenerator, JAVA_CONSTRAINTS_PRESET } from '../../../../src/generators'; 
 
-describe('JAVA_DESCRIPTION_PRESET', () => {
+describe('JAVA_CONSTRAINTS_PRESET', () => {
   let generator: JavaGenerator;
   beforeEach(() => {
     generator = new JavaGenerator({ presets: [JAVA_CONSTRAINTS_PRESET] });
@@ -48,6 +48,7 @@ describe('JAVA_DESCRIPTION_PRESET', () => {
     const model = inputModel.models['Clazz'];
 
     const classModel = await generator.renderClass(model, inputModel);
-    expect(classModel).toEqual(expected);
+    expect(classModel.result).toEqual(expected);
+    expect(classModel.dependencies).toEqual(['import javax.validation.constraints.*;']);
   });
 });
diff --git a/test/generators/java/presets/DescriptionPreset.spec.ts b/test/generators/java/presets/DescriptionPreset.spec.ts
index e5466a1f..fd828964 100644
--- a/test/generators/java/presets/DescriptionPreset.spec.ts
+++ b/test/generators/java/presets/DescriptionPreset.spec.ts
@@ -35,7 +35,8 @@ public class Clazz {
     const model = inputModel.models['Clazz'];
 
     const classModel = await generator.renderClass(model, inputModel);
-    expect(classModel).toEqual(expected);
+    expect(classModel.result).toEqual(expected);
+    expect(classModel.dependencies).toEqual([]);
   });
 
   test('should render description and examples for enum', async () => {
@@ -87,6 +88,7 @@ public enum Enum {
     const model = inputModel.models['Enum'];
 
     const enumModel = await generator.renderEnum(model, inputModel);
-    expect(enumModel).toEqual(expected);
+    expect(enumModel.result).toEqual(expected);
+    expect(enumModel.dependencies).toEqual(['import com.fasterxml.jackson.annotations.*;']);
   });
 });
diff --git a/test/generators/java/presets/JacksonPreset.spec.ts b/test/generators/java/presets/JacksonPreset.spec.ts
index da27dfe2..c4d47184 100644
--- a/test/generators/java/presets/JacksonPreset.spec.ts
+++ b/test/generators/java/presets/JacksonPreset.spec.ts
@@ -32,6 +32,7 @@ describe('JAVA_DESCRIPTION_PRESET', () => {
     const model = inputModel.models['Clazz'];
 
     const classModel = await generator.renderClass(model, inputModel);
-    expect(classModel).toEqual(expected);
+    expect(classModel.result).toEqual(expected);
+    expect(classModel.dependencies).toEqual(['import com.fasterxml.jackson.annotations.*;']);
   });
 });
diff --git a/test/generators/javascript/JavaScriptGenerator.spec.ts b/test/generators/javascript/JavaScriptGenerator.spec.ts
index 7d75e865..c453dcdb 100644
--- a/test/generators/javascript/JavaScriptGenerator.spec.ts
+++ b/test/generators/javascript/JavaScriptGenerator.spec.ts
@@ -66,10 +66,12 @@ describe('JavaScriptGenerator', () => {
     const model = inputModel.models['Address'];
 
     let classModel = await generator.renderClass(model, inputModel);
-    expect(classModel).toEqual(expected);
+    expect(classModel.result).toEqual(expected);
+    expect(classModel.dependencies).toEqual([]);
 
     classModel = await generator.render(model, inputModel);
-    expect(classModel).toEqual(expected);
+    expect(classModel.result).toEqual(expected);
+    expect(classModel.dependencies).toEqual([]);
   });
 
   test('should not render another type than `object`', async () => {
@@ -83,7 +85,8 @@ describe('JavaScriptGenerator', () => {
     const model = inputModel.models['AnyType'];
 
     const anyModel = await generator.render(model, inputModel);
-    expect(anyModel).toEqual(expected);
+    expect(anyModel.result).toEqual(expected);
+    expect(anyModel.dependencies).toEqual([]);
   });
 
   test('should work custom preset for `class` type', async () => {
@@ -124,7 +127,7 @@ describe('JavaScriptGenerator', () => {
           },
           setter() {
             return 'set property(property) { this.#property = property; }';
-          },
+          }
         }
       }
     ] });
@@ -133,6 +136,7 @@ describe('JavaScriptGenerator', () => {
     const model = inputModel.models['CustomClass'];
     
     const classModel = await generator.render(model, inputModel);
-    expect(classModel).toEqual(expected);
+    expect(classModel.result).toEqual(expected);
+    expect(classModel.dependencies).toEqual([]);
   });
 });
diff --git a/test/generators/typescript/TypeScriptGenerator.spec.ts b/test/generators/typescript/TypeScriptGenerator.spec.ts
index 59c79172..6dc28051 100644
--- a/test/generators/typescript/TypeScriptGenerator.spec.ts
+++ b/test/generators/typescript/TypeScriptGenerator.spec.ts
@@ -30,10 +30,12 @@ describe('TypeScriptGenerator', () => {
     const model = inputModel.models['_address'];
 
     let classModel = await generator.renderClass(model, inputModel);
-    expect(classModel).toEqual(expected);
+    expect(classModel.result).toEqual(expected);
+    expect(classModel.dependencies).toEqual([]);
 
     classModel = await generator.render(model, inputModel);
-    expect(classModel).toEqual(expected);
+    expect(classModel.result).toEqual(expected);
+    expect(classModel.dependencies).toEqual([]);
   });
   test('should render `class` type', async () => {
     const doc = {
@@ -110,10 +112,12 @@ describe('TypeScriptGenerator', () => {
     const model = inputModel.models['_address'];
 
     let classModel = await generator.renderClass(model, inputModel);
-    expect(classModel).toEqual(expected);
+    expect(classModel.result).toEqual(expected);
+    expect(classModel.dependencies).toEqual([]);
 
     classModel = await generator.render(model, inputModel);
-    expect(classModel).toEqual(expected);
+    expect(classModel.result).toEqual(expected);
+    expect(classModel.dependencies).toEqual([]);
   });
 
   test('should work custom preset for `class` type', async () => {
@@ -153,7 +157,8 @@ ${content}`;
     const model = inputModel.models['CustomClass'];
     
     const classModel = await generator.render(model, inputModel);
-    expect(classModel).toEqual(expected);
+    expect(classModel.result).toEqual(expected);
+    expect(classModel.dependencies).toEqual([]);
   });
 
   test('should render `interface` type', async () => {
@@ -188,7 +193,8 @@ ${content}`;
     const model = inputModel.models['Address'];
 
     const interfaceModel = await interfaceGenerator.render(model, inputModel);
-    expect(interfaceModel).toEqual(expected);
+    expect(interfaceModel.result).toEqual(expected);
+    expect(interfaceModel.dependencies).toEqual([]);
   });
 
   test('should work custom preset for `interface` type', async () => {
@@ -217,7 +223,8 @@ ${content}`;
     const model = inputModel.models['CustomInterface'];
 
     const interfaceModel = await generator.renderInterface(model, inputModel);
-    expect(interfaceModel).toEqual(expected);
+    expect(interfaceModel.result).toEqual(expected);
+    expect(interfaceModel.dependencies).toEqual([]);
   });
 
   test('should render `enum` type', async () => {
@@ -236,10 +243,12 @@ ${content}`;
     const model = inputModel.models['States'];
 
     let enumModel = await generator.render(model, inputModel);
-    expect(enumModel).toEqual(expected);
+    expect(enumModel.result).toEqual(expected);
+    expect(enumModel.dependencies).toEqual([]);
     
     enumModel = await generator.renderEnum(model, inputModel);
-    expect(enumModel).toEqual(expected);
+    expect(enumModel.result).toEqual(expected);
+    expect(enumModel.dependencies).toEqual([]);
   });
 
   test('should work custom preset for `enum` type', async () => {
@@ -268,10 +277,12 @@ ${content}`;
     const model = inputModel.models['CustomEnum'];
     
     let enumModel = await generator.render(model, inputModel);
-    expect(enumModel).toEqual(expected);
+    expect(enumModel.result).toEqual(expected);
+    expect(enumModel.dependencies).toEqual([]);
     
     enumModel = await generator.renderEnum(model, inputModel);
-    expect(enumModel).toEqual(expected);
+    expect(enumModel.result).toEqual(expected);
+    expect(enumModel.dependencies).toEqual([]);
   });
 
   test('should render `type` type - primitive', async () => {
@@ -285,10 +296,12 @@ ${content}`;
     const model = inputModel.models['TypePrimitive'];
 
     let primitiveModel = await generator.renderType(model, inputModel);
-    expect(primitiveModel).toEqual(expected);
+    expect(primitiveModel.result).toEqual(expected);
+    expect(primitiveModel.dependencies).toEqual([]);
 
     primitiveModel = await generator.render(model, inputModel);
-    expect(primitiveModel).toEqual(expected);
+    expect(primitiveModel.result).toEqual(expected);
+    expect(primitiveModel.dependencies).toEqual([]);
   });
 
   test('should render `type` type - enum', async () => {
@@ -302,7 +315,8 @@ ${content}`;
     const model = inputModel.models['TypeEnum'];
 
     const enumModel = await generator.renderType(model, inputModel);
-    expect(enumModel).toEqual(expected);
+    expect(enumModel.result).toEqual(expected);
+    expect(enumModel.dependencies).toEqual([]);
   });
 
   test('should render `type` type - union', async () => {
@@ -316,7 +330,8 @@ ${content}`;
     const model = inputModel.models['TypeUnion'];
 
     const unionModel = await generator.renderType(model, inputModel);
-    expect(unionModel).toEqual(expected);
+    expect(unionModel.result).toEqual(expected);
+    expect(unionModel.dependencies).toEqual([]);
   });
 
   test('should render `type` type - array of primitive type', async () => {
@@ -334,7 +349,8 @@ ${content}`;
     const model = inputModel.models['TypeArray'];
 
     const arrayModel = await generator.renderType(model, inputModel);
-    expect(arrayModel).toEqual(expected);
+    expect(arrayModel.result).toEqual(expected);
+    expect(arrayModel.dependencies).toEqual([]);
   });
 
   test('should render `type` type - array of union type', async () => {
@@ -352,6 +368,7 @@ ${content}`;
     const model = inputModel.models['TypeArray'];
 
     const arrayModel = await generator.renderType(model, inputModel);
-    expect(arrayModel).toEqual(expected);
+    expect(arrayModel.result).toEqual(expected);
+    expect(arrayModel.dependencies).toEqual([]);
   });
 });
diff --git a/test/models/OutputModel.spec.ts b/test/models/OutputModel.spec.ts
index d3008bf8..d126291a 100644
--- a/test/models/OutputModel.spec.ts
+++ b/test/models/OutputModel.spec.ts
@@ -10,6 +10,7 @@ describe('OutputModel', () => {
       model: commonModel,
       modelName: 'someModel',
       inputModel: new CommonInputModel(),
+      dependencies: ['test']
     };
     const output = OutputModel.toOutputModel(ioutput);
 
@@ -17,6 +18,7 @@ describe('OutputModel', () => {
     expect(output.model).toEqual(ioutput.model);
     expect(output.modelName).toEqual(ioutput.modelName);
     expect(output.inputModel).toEqual(ioutput.inputModel);
+    expect(output.dependencies).toEqual(ioutput.dependencies);
   });
 
   test('should return an array of OutputModel', () => {
@@ -28,6 +30,7 @@ describe('OutputModel', () => {
       model: commonModel,
       modelName: 'someModel',
       inputModel: new CommonInputModel(),
+      dependencies: ['test']
     };
     const output = OutputModel.toOutputModel([ioutput, ioutput]);
 
@@ -35,9 +38,11 @@ describe('OutputModel', () => {
     expect(output[0].model).toEqual(ioutput.model);
     expect(output[0].modelName).toEqual(ioutput.modelName);
     expect(output[0].inputModel).toEqual(ioutput.inputModel);
+    expect(output[0].dependencies).toEqual(ioutput.dependencies);
     expect(output[1].result).toEqual(ioutput.result);
     expect(output[1].model).toEqual(ioutput.model);
     expect(output[1].modelName).toEqual(ioutput.modelName);
     expect(output[1].inputModel).toEqual(ioutput.inputModel);
+    expect(output[1].dependencies).toEqual(ioutput.dependencies);
   });
 });
diff --git a/test/models/RenderOutput.spec.ts b/test/models/RenderOutput.spec.ts
new file mode 100644
index 00000000..2f9450c3
--- /dev/null
+++ b/test/models/RenderOutput.spec.ts
@@ -0,0 +1,21 @@
+import { ToRenderOutputArg, RenderOutput } from '../../src/models'; 
+
+describe('RenderOutput', () => {
+  test('should return an RenderOutput', () => {
+    const ioutput: ToRenderOutputArg = {
+      result: 'result',
+      dependencies: ['test']
+    };
+    const output = RenderOutput.toRenderOutput(ioutput);
+    expect(output.result).toEqual(ioutput.result);
+    expect(output.dependencies).toEqual(ioutput.dependencies);
+  });
+  test('should default to values', () => {
+    const ioutput: ToRenderOutputArg = {
+      result: 'result'
+    };
+    const output = RenderOutput.toRenderOutput(ioutput);
+    expect(output.result).toEqual(ioutput.result);
+    expect(output.dependencies).toEqual([]);
+  });
+});