Skip to content

Commit

Permalink
feat(java): offer Builders for certain Java classes (#895)
Browse files Browse the repository at this point in the history
* feat(java): offer Builders for certain Java classes

Provide a nested `Builder` class with a static `create` factory for all
classes that adhere to the following constraints:
- The class is not `abstract`
- The class has a visible `initializer` (aka `constructor`)
- The constructor has exactly one `struct` (aka `datatype`) parameter
- The constructor is not variadic

Such builders will request the *positional* parameters to be passed
directly to the `Builder#create` method and can (at least currently) not
be subsequently modified. All parameters from the `struct` are then set
direcly on the `Builder` instance.

Fixes #488

* improvements based on PR feedback from @bmaizels

* add factory overrides, better documentation coverage
  • Loading branch information
RomainMuller authored and mergify[bot] committed Oct 29, 2019
1 parent 4ff3a6d commit f9c1335
Show file tree
Hide file tree
Showing 35 changed files with 1,674 additions and 106 deletions.
44 changes: 44 additions & 0 deletions packages/jsii-calc/lib/compliance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1900,3 +1900,47 @@ export class OverridableProtectedMember {
return this.overrideReadOnly + this.overrideReadWrite;
}
}

/**
* We can generate fancy builders in Java for classes which take a mix of positional & struct parameters
*/
export class SupportsNiceJavaBuilderWithRequiredProps {
public readonly propId?: string;
public readonly bar: number;

/**
* @param id some identifier of your choice
* @param props some properties
*/
public constructor(public readonly id: number, props: SupportsNiceJavaBuilderProps) {
this.propId = props.id;
this.bar = props.bar;
}
}
export class SupportsNiceJavaBuilder extends SupportsNiceJavaBuilderWithRequiredProps {
public readonly rest: string[];

/**
*
* @param id some identifier
* @param defaultBar the default value of `bar`
* @param props some props once can provide
* @param rest a variadic continuation
*/
public constructor(public readonly id: number, defaultBar = 1337, props?: SupportsNiceJavaBuilderProps, ...rest: string[]) {
super(id, props || { bar: defaultBar });
this.rest = rest;
}
}
export interface SupportsNiceJavaBuilderProps {
/**
* An `id` field here is terrible API design, because the constructor of `SupportsNiceJavaBuilder` already has a
* parameter named `id`. But here we are, doing it like we didn't care.
*/
readonly id?: string;

/**
* Some number, like 42.
*/
readonly bar: number;
}
235 changes: 234 additions & 1 deletion packages/jsii-calc/test/assembly.jsii
Original file line number Diff line number Diff line change
Expand Up @@ -8587,6 +8587,239 @@
}
]
},
"jsii-calc.SupportsNiceJavaBuilder": {
"assembly": "jsii-calc",
"base": "jsii-calc.SupportsNiceJavaBuilderWithRequiredProps",
"docs": {
"stability": "experimental"
},
"fqn": "jsii-calc.SupportsNiceJavaBuilder",
"initializer": {
"docs": {
"stability": "experimental"
},
"parameters": [
{
"docs": {
"summary": "some identifier."
},
"name": "id",
"type": {
"primitive": "number"
}
},
{
"docs": {
"summary": "the default value of `bar`."
},
"name": "defaultBar",
"optional": true,
"type": {
"primitive": "number"
}
},
{
"docs": {
"summary": "some props once can provide."
},
"name": "props",
"optional": true,
"type": {
"fqn": "jsii-calc.SupportsNiceJavaBuilderProps"
}
},
{
"docs": {
"summary": "a variadic continuation."
},
"name": "rest",
"type": {
"primitive": "string"
},
"variadic": true
}
],
"variadic": true
},
"kind": "class",
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 1920
},
"name": "SupportsNiceJavaBuilder",
"properties": [
{
"docs": {
"stability": "experimental",
"summary": "some identifier."
},
"immutable": true,
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 1930
},
"name": "id",
"overrides": "jsii-calc.SupportsNiceJavaBuilderWithRequiredProps",
"type": {
"primitive": "number"
}
},
{
"docs": {
"stability": "experimental"
},
"immutable": true,
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 1921
},
"name": "rest",
"type": {
"collection": {
"elementtype": {
"primitive": "string"
},
"kind": "array"
}
}
}
]
},
"jsii-calc.SupportsNiceJavaBuilderProps": {
"assembly": "jsii-calc",
"datatype": true,
"docs": {
"stability": "experimental"
},
"fqn": "jsii-calc.SupportsNiceJavaBuilderProps",
"kind": "interface",
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 1935
},
"name": "SupportsNiceJavaBuilderProps",
"properties": [
{
"abstract": true,
"docs": {
"stability": "experimental",
"summary": "Some number, like 42."
},
"immutable": true,
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 1945
},
"name": "bar",
"type": {
"primitive": "number"
}
},
{
"abstract": true,
"docs": {
"remarks": "But here we are, doing it like we didn't care.",
"stability": "experimental",
"summary": "An `id` field here is terrible API design, because the constructor of `SupportsNiceJavaBuilder` already has a parameter named `id`."
},
"immutable": true,
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 1940
},
"name": "id",
"optional": true,
"type": {
"primitive": "string"
}
}
]
},
"jsii-calc.SupportsNiceJavaBuilderWithRequiredProps": {
"assembly": "jsii-calc",
"docs": {
"stability": "experimental",
"summary": "We can generate fancy builders in Java for classes which take a mix of positional & struct parameters."
},
"fqn": "jsii-calc.SupportsNiceJavaBuilderWithRequiredProps",
"initializer": {
"docs": {
"stability": "experimental"
},
"parameters": [
{
"docs": {
"summary": "some identifier of your choice."
},
"name": "id",
"type": {
"primitive": "number"
}
},
{
"docs": {
"summary": "some properties."
},
"name": "props",
"type": {
"fqn": "jsii-calc.SupportsNiceJavaBuilderProps"
}
}
]
},
"kind": "class",
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 1907
},
"name": "SupportsNiceJavaBuilderWithRequiredProps",
"properties": [
{
"docs": {
"stability": "experimental"
},
"immutable": true,
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 1909
},
"name": "bar",
"type": {
"primitive": "number"
}
},
{
"docs": {
"stability": "experimental",
"summary": "some identifier of your choice."
},
"immutable": true,
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 1915
},
"name": "id",
"type": {
"primitive": "number"
}
},
{
"docs": {
"stability": "experimental"
},
"immutable": true,
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 1908
},
"name": "propId",
"optional": true,
"type": {
"primitive": "string"
}
}
]
},
"jsii-calc.SyncVirtualMethods": {
"assembly": "jsii-calc",
"docs": {
Expand Down Expand Up @@ -9699,5 +9932,5 @@
}
},
"version": "0.19.0",
"fingerprint": "KFKFyrJ+3+cfNKWPiNetngWkPFI32ydxfJyaR43/wDI="
"fingerprint": "98k0qPH9S801EvX681aTDIxgufKA90iJ4SlfZKrm90c="
}
7 changes: 3 additions & 4 deletions packages/jsii-pacmak/lib/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import * as fs from 'fs-extra';
import reflect = require('jsii-reflect');
import * as spec from 'jsii-spec';
import * as path from 'path';
import { assemblySpec, typeSpec } from './reflect-hacks';
import { VERSION_DESC } from './version';

/**
Expand Down Expand Up @@ -83,7 +82,7 @@ export abstract class Generator implements IGenerator {

public async load(_packageRoot: string, assembly: reflect.Assembly): Promise<void> {
this._reflectAssembly = assembly;
this._assembly = assemblySpec(assembly);
this._assembly = assembly.spec;

// Including the version of jsii-pacmak in the fingerprint, as a new version may imply different code generation.
this.fingerprint = crypto.createHash('sha256')
Expand Down Expand Up @@ -523,12 +522,12 @@ export abstract class Generator implements IGenerator {
throw new Error(`Unable to find module ${name} as a direct or indirect dependency of ${this.assembly.name}`);
}

protected findType(fqn: string) {
protected findType(fqn: string): spec.Type {
const ret = this.reflectAssembly.system.tryFindFqn(fqn);
if (!ret) {
throw new Error(`Cannot find type '${fqn}' either as internal or external type`);
}

return typeSpec(ret);
return ret.spec;
}
}
28 changes: 0 additions & 28 deletions packages/jsii-pacmak/lib/reflect-hacks.ts

This file was deleted.

Loading

0 comments on commit f9c1335

Please sign in to comment.