Skip to content

Commit a5f73f8

Browse files
Merge pull request diffblue#356 from diffblue/feature/di-to-code
[SEC-121] Major update to DI tool
2 parents 26c7a5b + 5b82e9f commit a5f73f8

File tree

6 files changed

+508
-165
lines changed

6 files changed

+508
-165
lines changed

env-model-generator/src/JavaCode.ts

+42-6
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ export class BraceInitialiser extends CodeElement implements Expression {
8484
}
8585
}
8686

87+
export class Null extends CodeElement implements Expression {
88+
public isExpression: true = true;
89+
90+
public flatten(): string[] { return [ "null" ]; }
91+
}
92+
8793
export class StringConstant extends CodeElement implements Expression {
8894
public isExpression: true = true;
8995

@@ -233,10 +239,7 @@ export class BlankLine extends CodeElement implements Statement, Member {
233239
}
234240
}
235241

236-
export class Braces extends CodeElement implements Statement, Member {
237-
public isStatement: true = true;
238-
public isMember: true = true;
239-
242+
class Braces extends CodeElement {
240243
private intro: string;
241244
private body: Statement[];
242245

@@ -255,24 +258,57 @@ export class Braces extends CodeElement implements Statement, Member {
255258
}
256259
}
257260

261+
export class Block extends Braces implements Statement {
262+
public isStatement: true = true;
263+
}
264+
265+
export class Method extends Braces implements Member {
266+
public isMember: true = true;
267+
268+
private annotations: string[];
269+
270+
public constructor(intro: string, body: Statement[], exceptions?: string[], annotations?: string[]) {
271+
super(intro + (exceptions === undefined || exceptions.length === 0 ? "" : ` throws ${exceptions.join(", ")}`), body);
272+
this.annotations = annotations === undefined ? [] : annotations;
273+
}
274+
275+
public flatten(): string[] {
276+
return [
277+
...this.annotations.map((annotation) => "@" + annotation),
278+
...super.flatten(),
279+
];
280+
}
281+
}
282+
258283
export class Class extends CodeElement {
259284
private modifiers: string;
260285
private name: string;
286+
private baseClass: string | undefined;
287+
private interfaces: string[];
261288
private members: Member[];
289+
private annotations: string[];
262290

263-
public constructor(modifiers: string, name: string, members: Member[]) {
291+
public constructor(
292+
modifiers: string, name: string, baseClass: string | undefined, interfaces: string[], members: Member[], annotations?: string[])
293+
{
264294
super();
265295
this.modifiers = modifiers;
266296
this.name = name;
297+
this.baseClass = baseClass;
298+
this.interfaces = interfaces;
267299
this.members = members;
300+
this.annotations = annotations === undefined ? [] : annotations;
268301
}
269302

270303
public flatten(): string[] {
271304
const members = _.flatMap(this.members, (value, index, array) =>
272305
index !== array.length - 1 && !(value instanceof BlankLine)
273306
? [ value, new BlankLine() ] : value);
307+
const baseClassString = this.baseClass === undefined ? "" : ` extends ${this.baseClass}`;
308+
const interfacesString = this.interfaces.length === 0 ? "" : ` implements ${this.interfaces.join(", ")}`;
274309
return [
275-
`${this.modifiers} class ${this.name} {`,
310+
...this.annotations.map((annotation) => "@" + annotation),
311+
`${this.modifiers} class ${this.name}${baseClassString}${interfacesString} {`,
276312
..._.flatMap(members, (member) => member.flatten()).map((line) => indent + line),
277313
"}",
278314
];

env-model-generator/src/NameMap.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export default class NameMap<T extends NamedObject> implements Iterable<T> {
1515

1616
public add(elt: T) {
1717
if (this._map.has(elt.name))
18-
throw new Error("Name of object to add to NameMap already exists in map");
18+
throw new Error(`Tried to add object with same name '${elt.name}' as an object that already exists in this NameMap`);
1919
return this.set(elt);
2020
}
2121

env-model-generator/src/env-model-generator.ts

+75-49
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
import * as program from "commander";
66
import * as fs from "fs-extra";
77
import * as _ from "lodash";
8-
import * as path from "path";
9-
import { BlankLine, Braces, Class, FnCall, Return, Symbol } from "./JavaCode";
8+
import * as pathUtils from "path";
9+
import { BlankLine, Block, Class, FnCall, Method, Null, Return, Statement } from "./JavaCode";
1010
import * as model from "./model";
1111
import parseSpringXmlConfigFile from "./parse-spring-xml";
1212
import { createPath } from "./utils";
1313

14-
function collectOption(value: string, collection: string[]) {
15-
if (!collection)
14+
function collectOption(value: string, collection?: string[]) {
15+
if (collection === undefined)
1616
collection = [];
1717
collection.push(value);
1818
return collection;
@@ -23,18 +23,22 @@ program
2323
.arguments("<input-file>")
2424
.option("-i --input-file <path>", "Additional DI configuration file to read", collectOption)
2525
.option("-o --output-path <output-path>", "Path to write output under")
26-
.option("--verbose")
2726
.description("Create code from DI configuration")
28-
.action(function (inputFile: string, options) {
29-
transformConfigFile(inputFile, options);
30-
});
27+
.action(transformConfigFile);
3128
program.parse(process.argv);
3229

3330
// If program was called with no arguments then show help.
3431
if (program.args.length === 0) {
3532
program.help();
3633
}
3734

35+
enum ReturnValue {
36+
Success,
37+
ParseError,
38+
CreateJavaError,
39+
OutputFolderNotFound,
40+
OutputError,
41+
}
3842

3943
async function transformConfigFile(fileName: string, options: any) {
4044
let fileNames = [ fileName ];
@@ -43,71 +47,93 @@ async function transformConfigFile(fileName: string, options: any) {
4347
try {
4448
await parseSpringXmlConfigFile(fileNames);
4549
} catch (err) {
46-
console.error(err.message);
47-
process.exit(2);
50+
console.error(`Error parsing config file: ${err.message}`);
51+
process.exit(ReturnValue.ParseError);
4852
return;
4953
}
5054
const packageName = "org.springframework.context.support";
5155
let output = `package ${packageName};\n\n`;
52-
const applicationContext =
53-
new Class(
54-
"public", "ClassPathXmlApplicationContext",
55-
[
56-
new Braces(
57-
"public ClassPathXmlApplicationContext(String[] args)",
58-
[]
59-
),
60-
new Braces(
61-
"public Object getBean(String name)",
62-
[
63-
new Braces(
64-
"try",
65-
model.Bean.getNamed().map((bean) =>
66-
new Braces(
67-
`if (name.equals("${bean.name}"))`,
68-
[
69-
new Return(new FnCall(bean.getter, [])),
70-
])),
71-
),
72-
new Braces(
73-
"catch(Exception ex)",
74-
[]),
75-
new Return(new Symbol("null")),
76-
]
77-
),
78-
new BlankLine(),
79-
new BlankLine(),
80-
..._.flatMap([...model.Bean.getAll()], (bean) => bean.toJava()[0]),
81-
new BlankLine(),
82-
...model.PropertiesValue.utilityMethods(),
83-
...model.BeanValue.utilityMethods(),
84-
]);
85-
output += applicationContext.toString() + "\n";
56+
try {
57+
const applicationContext =
58+
new Class(
59+
"public", "ClassPathXmlApplicationContext", undefined, [ "org.springframework.context.ApplicationContext" ],
60+
[
61+
new Method(
62+
"public ClassPathXmlApplicationContext(String[] args)",
63+
[]),
64+
new Method(
65+
"public Object getBean(String name)",
66+
[
67+
new Block(
68+
"try",
69+
(<Statement[]>[]).concat(
70+
model.Bean.getNamed().map((bean) =>
71+
new Block(
72+
`if (name.equals("${bean.name}"))`,
73+
[
74+
new Return(new FnCall(bean.getter, [])),
75+
])),
76+
[...model.Bean.getAliases()].map(
77+
function ([ alias, beanId ]) {
78+
const bean = model.Bean.tryGet(beanId);
79+
if (bean === undefined)
80+
throw new Error("Found alias to bean that doesn't exist");
81+
return new Block(
82+
`if (name.equals("${alias}"))`,
83+
[
84+
new Return(new FnCall(bean.getter, [])),
85+
]);
86+
}),
87+
),
88+
),
89+
new Block(
90+
"catch(Exception ex)",
91+
[]),
92+
new Return(new Null()),
93+
],
94+
[],
95+
[ "OverlayMethodImplementation" ]
96+
),
97+
new BlankLine(),
98+
new BlankLine(),
99+
..._.flatMap([...model.Bean.getAll()], (bean) => bean.javaMembers),
100+
new BlankLine(),
101+
...model.PropertiesValue.utilityMethods(),
102+
...model.MapValue.utilityMethods(),
103+
...model.BeanValue.utilityMethods(),
104+
],
105+
[ "OverlayClassImplementation" ]);
106+
output += applicationContext.toString() + "\n";
107+
} catch (err) {
108+
console.error(`Error converting to Java: ${err.message}`);
109+
process.exit(ReturnValue.CreateJavaError);
110+
return;
111+
}
86112
// Write out the generated Java
87113
let outputPath = options.outputPath;
88114
if (outputPath) {
89115
if (!fs.existsSync(outputPath)) {
90116
console.error(`Given output folder '${outputPath}' does not exist`);
91-
process.exit(5);
117+
process.exit(ReturnValue.OutputFolderNotFound);
92118
return;
93119
}
94120
// Create folder to store it in based on the package name
95-
const appContextFolder = path.join(...packageName.split("."));
121+
const appContextFolder = pathUtils.join(...packageName.split("."));
96122
try {
97123
outputPath = createPath(appContextFolder, outputPath);
98124
} catch (err) {
99125
console.error(`Can't create output folder '${appContextFolder}': ${err.message}`);
100-
process.exit(3);
126+
process.exit(ReturnValue.OutputError);
101127
return;
102128
}
103129
// Write file
104-
const outputFileName = path.resolve(outputPath, "ClassPathXmlApplicationContext.java");
130+
const outputFileName = pathUtils.resolve(outputPath, "ClassPathXmlApplicationContext.java");
105131
try {
106132
await fs.writeFile(outputFileName, output, "utf8");
107133
console.log("Output written to %s", outputFileName);
108134
} catch (err) {
109135
console.error(`Can't write output file '${outputFileName}': ${err.message}`);
110-
process.exit(4);
136+
process.exit(ReturnValue.OutputError);
111137
return;
112138
}
113139
} else

0 commit comments

Comments
 (0)