skir-java-gen 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,730 @@
1
+ // TODO: add comments to generated code
2
+ // TODO: add comments to records/fields/methods
3
+ // TODO: s/field/variant for enums
4
+ import { convertCase, } from "skir-internal";
5
+ import { z } from "zod";
6
+ import { Namer, toEnumConstantName } from "./naming.js";
7
+ import { TypeSpeller } from "./type_speller.js";
8
+ const Config = z.object({
9
+ packagePrefix: z
10
+ .string()
11
+ .regex(/^([a-z_$][a-z0-9_$]*\.)*$/)
12
+ .optional(),
13
+ });
14
+ class JavaCodeGenerator {
15
+ constructor() {
16
+ this.id = "java";
17
+ this.configType = Config;
18
+ this.version = "1.0.0";
19
+ }
20
+ generateCode(input) {
21
+ const { recordMap, config } = input;
22
+ const javaSourceFiles = [];
23
+ for (const module of input.modules) {
24
+ for (const record of module.records) {
25
+ if (record.recordAncestors.length !== 1) {
26
+ // Only consider top-level records
27
+ continue;
28
+ }
29
+ javaSourceFiles.push(new JavaSourceFileGenerator(record.record, module.path, recordMap, config));
30
+ }
31
+ if (module.methods.length > 0) {
32
+ javaSourceFiles.push(new JavaSourceFileGenerator({
33
+ kind: "methods",
34
+ methods: module.methods,
35
+ }, module.path, recordMap, config));
36
+ }
37
+ if (module.constants.length > 0) {
38
+ javaSourceFiles.push(new JavaSourceFileGenerator({
39
+ kind: "constants",
40
+ constants: module.constants,
41
+ }, module.path, recordMap, config));
42
+ }
43
+ }
44
+ const outputFiles = javaSourceFiles.map((sourceFile) => ({
45
+ path: sourceFile.path,
46
+ code: sourceFile.generate(),
47
+ }));
48
+ return { files: outputFiles };
49
+ }
50
+ }
51
+ // Generates the code for one Java file.
52
+ class JavaSourceFileGenerator {
53
+ constructor(target, modulePath, recordMap, config) {
54
+ this.target = target;
55
+ this.modulePath = modulePath;
56
+ this.recordMap = recordMap;
57
+ this.code = "";
58
+ this.packagePrefix = config.packagePrefix ?? "";
59
+ this.namer = new Namer(this.packagePrefix);
60
+ this.typeSpeller = new TypeSpeller(recordMap, this.namer);
61
+ }
62
+ generate() {
63
+ // http://patorjk.com/software/taag/#f=Doom&t=Do%20not%20edit
64
+ this.push(`// ______ _ _ _ _
65
+ // | _ \\ | | | |(_)| |
66
+ // | | | | ___ _ __ ___ | |_ ___ __| | _ | |_
67
+ // | | | | / _ \\ | '_ \\ / _ \\ | __| / _ \\ / _\` || || __|
68
+ // | |/ / | (_) | | | | || (_) || |_ | __/| (_| || || |_
69
+ // |___/ \\___/ |_| |_| \\___/ \\__| \\___| \\__,_||_| \\__|
70
+ //
71
+
72
+ // To install the skir client library, add:
73
+ // implementation("build.skir:skir-kotlin-client:latest.release")
74
+ // to your build.gradle file
75
+
76
+ `, `package ${this.packagePrefix}skirout.`, this.modulePath.replace(/\.skir$/, "").replace("/", "."), ";\n\n");
77
+ const { target } = this;
78
+ switch (target.kind) {
79
+ case "record": {
80
+ this.writeClassForRecord(target, "top-level");
81
+ break;
82
+ }
83
+ case "methods": {
84
+ this.writeClassForMethods(target.methods);
85
+ break;
86
+ }
87
+ case "constants": {
88
+ this.writeClassForConstants(target.constants);
89
+ break;
90
+ }
91
+ default: {
92
+ const _ = target;
93
+ }
94
+ }
95
+ return this.joinLinesAndFixFormatting();
96
+ }
97
+ writeClassForRecord(record, nested) {
98
+ if (record.recordType === "struct") {
99
+ this.writeClassForStruct(record, nested);
100
+ }
101
+ else {
102
+ this.writeClassForEnum(record, nested);
103
+ }
104
+ }
105
+ writeClassForStruct(record, nested) {
106
+ const { namer, recordMap, typeSpeller } = this;
107
+ const recordLocation = recordMap.get(record.key);
108
+ const className = this.namer.getClassName(recordLocation).name;
109
+ const fields = [...record.fields];
110
+ fields.sort((a, b) => a.name.text.localeCompare(b.name.text));
111
+ this.push("public ", nested === "nested" ? "static " : "", `final class ${className} {\n`);
112
+ // Declare fields
113
+ for (const field of fields) {
114
+ const fieldName = namer.structFieldToJavaName(field);
115
+ const type = typeSpeller.getJavaType(field.type, "frozen");
116
+ this.push(`private final ${type} ${fieldName};\n`);
117
+ }
118
+ const unrecognizedFieldsType = `build.skir.internal.UnrecognizedFields<${className}>`;
119
+ this.push(`private final ${unrecognizedFieldsType} _u;\n\n`);
120
+ // Constructor
121
+ this.push(`private ${className}(\n`);
122
+ for (const field of fields) {
123
+ const fieldName = namer.structFieldToJavaName(field);
124
+ const type = typeSpeller.getJavaType(field.type, "frozen");
125
+ this.push(`${type} ${fieldName},\n`);
126
+ }
127
+ this.push(`${unrecognizedFieldsType} _u\n`, ") {\n");
128
+ for (const field of fields) {
129
+ const fieldName = namer.structFieldToJavaName(field);
130
+ this.push(`this.${fieldName} = ${fieldName};\n`);
131
+ }
132
+ this.push("this._u = _u;\n", "}\n\n");
133
+ // DEFAULT instance
134
+ this.push(`private ${className}() {\n`);
135
+ for (const field of fields) {
136
+ const fieldName = namer.structFieldToJavaName(field);
137
+ if (field.isRecursive === "hard") {
138
+ this.push(`this.${fieldName} = null;\n`);
139
+ }
140
+ else {
141
+ const defaultExpr = this.getDefaultExpression(field.type);
142
+ this.push(`this.${fieldName} = ${defaultExpr};\n`);
143
+ }
144
+ }
145
+ this.push("this._u = null;\n", "}\n\n", `public static final ${className} DEFAULT = new ${className}();\n\n`);
146
+ // Getters
147
+ for (const field of fields) {
148
+ const fieldName = namer.structFieldToJavaName(field);
149
+ const type = typeSpeller.getJavaType(field.type, "frozen");
150
+ this.push(`public ${type} ${fieldName}() {\n`);
151
+ if (field.isRecursive === "hard") {
152
+ const defaultExpr = this.getDefaultExpression(field.type);
153
+ this.push(`if (this.${fieldName} != null) {\n`, `return this.${fieldName};\n`, `} else {\n`, `return ${defaultExpr};\n`, "}\n");
154
+ }
155
+ else {
156
+ this.push(`return this.${fieldName};\n`);
157
+ }
158
+ this.push("}\n\n");
159
+ }
160
+ // toBuilder()
161
+ this.push(`public Builder toBuilder() {\n`);
162
+ this.push(`return new Builder(\n`);
163
+ for (const field of fields) {
164
+ const fieldName = namer.structFieldToJavaName(field);
165
+ this.push(`this.${fieldName},\n`);
166
+ }
167
+ this.push("this._u);\n", "}\n\n");
168
+ // equals()
169
+ this.push("@java.lang.Override\n", "public boolean equals(Object other) {\n", "if (this == other) return true;\n", `if (!(other instanceof ${className})) return false;\n`, `return java.util.Arrays.equals(_equalsProxy(), ((${className}) other)._equalsProxy());\n`, "}\n\n");
170
+ // hashCode()
171
+ this.push("@java.lang.Override\n", "public int hashCode() {\n", "return java.util.Arrays.hashCode(_equalsProxy());\n", "}\n\n");
172
+ // toString()
173
+ this.push("@java.lang.Override\n", "public java.lang.String toString() {\n", `return SERIALIZER.toJsonCode(this, build.skir.JsonFlavor.READABLE);\n`, "}\n\n");
174
+ // _equalsProxy()
175
+ this.push("private Object[] _equalsProxy() {\n", "return new Object[] {\n", fields
176
+ .map((field) => "this." + namer.structFieldToJavaName(field))
177
+ .join(",\n"), "\n};\n", "}\n\n");
178
+ // builder()
179
+ {
180
+ const firstField = fields[0];
181
+ const retType = firstField
182
+ ? "Builder_At" + convertCase(firstField.name.text, "UpperCamel")
183
+ : "Builder_Done";
184
+ this.push(`public static ${retType} builder() {\n`, "return new Builder();\n", "}\n\n");
185
+ }
186
+ // partialBuilder()
187
+ this.push("public static Builder partialBuilder() {\n", "return new Builder();\n", "}\n\n");
188
+ // Builder_At? interfaces
189
+ for (const [index, field] of fields.entries()) {
190
+ const fieldName = namer.structFieldToJavaName(field);
191
+ const nextField = index < fields.length - 1 ? fields[index + 1] : null;
192
+ const upperCamelName = convertCase(field.name.text, "UpperCamel");
193
+ const retType = nextField
194
+ ? "Builder_At" + convertCase(nextField.name.text, "UpperCamel")
195
+ : "Builder_Done";
196
+ const paramType = typeSpeller.getJavaType(field.type, "initializer");
197
+ this.push(`public interface Builder_At${upperCamelName} {\n`, `${retType} set${upperCamelName}(${paramType} ${fieldName});\n`, "}\n\n");
198
+ }
199
+ this.push(`public interface Builder_Done {\n`, `${className} build();\n`, "}\n\n");
200
+ // Builder class
201
+ this.push("public static final class Builder implements ");
202
+ for (const field of fields) {
203
+ const upperCamelName = convertCase(field.name.text, "UpperCamel");
204
+ this.push(`Builder_At${upperCamelName}, `);
205
+ }
206
+ this.push("Builder_Done {\n");
207
+ // Builder fields
208
+ for (const field of fields) {
209
+ const fieldName = namer.structFieldToJavaName(field);
210
+ const type = typeSpeller.getJavaType(field.type, "frozen");
211
+ this.push(`private ${type} ${fieldName};\n`);
212
+ }
213
+ this.push(`private ${unrecognizedFieldsType} _u;\n\n`);
214
+ // Builder constructors
215
+ this.push("private Builder(\n");
216
+ for (const field of fields) {
217
+ const fieldName = namer.structFieldToJavaName(field);
218
+ const type = typeSpeller.getJavaType(field.type, "frozen");
219
+ this.push(`${type} ${fieldName},\n`);
220
+ }
221
+ this.push(`${unrecognizedFieldsType} _u\n`, ") {\n");
222
+ for (const field of fields) {
223
+ const fieldName = namer.structFieldToJavaName(field);
224
+ this.push(`this.${fieldName} = ${fieldName};\n`);
225
+ }
226
+ this.push("this._u = _u;\n", "}\n\n");
227
+ this.push("private Builder() {\n");
228
+ for (const field of fields) {
229
+ const fieldName = namer.structFieldToJavaName(field);
230
+ const defaultExpr = this.getDefaultExpression(field.type);
231
+ this.push(`this.${fieldName} = ${defaultExpr};\n`);
232
+ }
233
+ this.push("this._u = null;\n", "}\n\n");
234
+ // Setters
235
+ for (const field of fields) {
236
+ const fieldName = namer.structFieldToJavaName(field);
237
+ const upperCamelName = convertCase(field.name.text, "UpperCamel");
238
+ const type = field.type;
239
+ const javaType = typeSpeller.getJavaType(type, "initializer");
240
+ this.push("@java.lang.Override\n", `public Builder set${upperCamelName}(${javaType} ${fieldName}) {\n`);
241
+ const toFrozenExpr = this.toFrozenExpression(fieldName, type, "can-be-null", "_e");
242
+ this.push(`this.${fieldName} = ${toFrozenExpr};\n`, "return this;\n", "}\n\n");
243
+ const isStruct = type.kind === "record" &&
244
+ recordMap.get(type.key).record.recordType === "struct";
245
+ if (isStruct) {
246
+ const updaterType = `java.util.function.Function<? super ${javaType}, ? extends ${javaType}>`;
247
+ this.push(`public Builder update${upperCamelName}(${updaterType} updater) {\n`, `return set${upperCamelName}(updater.apply(this.${fieldName}));\n`, "}\n\n");
248
+ }
249
+ }
250
+ this.push("@java.lang.Override\n", `public ${className} build() {\n`, `return new ${className}(\n`);
251
+ for (const field of fields) {
252
+ const fieldName = namer.structFieldToJavaName(field);
253
+ this.push(`this.${fieldName},\n`);
254
+ }
255
+ this.push("this._u);\n", "}\n\n");
256
+ this.push("}\n\n");
257
+ // _serializerImpl
258
+ {
259
+ const serializerType = `build.skir.internal.StructSerializer<${className}, ${className}.Builder>`;
260
+ this.push(`private static final ${serializerType} _serializerImpl = (\n`, "new build.skir.internal.StructSerializer<>(\n", `"${getRecordId(recordLocation)}",\n`, `${toJavaStringLiteral(docToCommentText(record.doc))},\n`, "DEFAULT,\n", `(${className} it) -> it != null ? it.toBuilder() : partialBuilder(),\n`, `(${className}.Builder it) -> it.build(),\n`, `(${className} it) -> it._u,\n`, `(${className}.Builder builder, ${unrecognizedFieldsType} u) -> {\n`, `builder._u = u;\n`, "return null;\n", "}\n", ")\n", ");\n\n");
261
+ }
262
+ // SERIALIZER
263
+ this.push(`public static final build.skir.Serializer<${className}> SERIALIZER = (\n`, "build.skir.internal.SerializersKt.makeSerializer(_serializerImpl)\n", ");\n\n");
264
+ // TYPE_DESCRIPTOR
265
+ {
266
+ const typeDescriptorType = `build.skir.reflection.StructDescriptor.Reflective<${className}, ${className}.Builder>`;
267
+ this.push(`public static final ${typeDescriptorType} TYPE_DESCRIPTOR = (\n`, "_serializerImpl\n", ");\n\n");
268
+ }
269
+ // Finalize serializer
270
+ this.push("static {\n");
271
+ for (const field of fields) {
272
+ const skirName = field.name.text;
273
+ const javadName = namer.structFieldToJavaName(field);
274
+ this.push("_serializerImpl.addField(\n", `"${skirName}",\n`, '"",\n', `${field.number},\n`, `${typeSpeller.getSerializerExpression(field.type)},\n`, `${toJavaStringLiteral(docToCommentText(field.doc))},\n`, `(it) -> it.${javadName}(),\n`, "(builder, v) -> {\n", `builder.${javadName} = v;\n`, "return null;\n", "}\n", ");\n");
275
+ }
276
+ for (const removedNumber of record.removedNumbers) {
277
+ this.push(`_serializerImpl.addRemovedNumber(${removedNumber});\n`);
278
+ }
279
+ this.push("_serializerImpl.finalizeStruct();\n", "}\n\n");
280
+ // Nested classes
281
+ this.writeClassesForNestedRecords(record);
282
+ this.push("}\n\n");
283
+ }
284
+ writeClassForEnum(record, nested) {
285
+ const { recordMap, typeSpeller } = this;
286
+ const recordLocation = recordMap.get(record.key);
287
+ const className = this.namer.getClassName(recordLocation).name;
288
+ const { fields } = record;
289
+ const constantFields = fields.filter((f) => !f.type);
290
+ const wrapperFields = fields.filter((f) => f.type);
291
+ this.push("public ", nested === "nested" ? "static " : "", `final class ${className} {\n`);
292
+ // Kind enum
293
+ this.push("public enum Kind {\n", "UNKNOWN,\n");
294
+ for (const field of constantFields) {
295
+ this.push(field.name.text, "_CONST,\n");
296
+ }
297
+ for (const field of wrapperFields) {
298
+ this.push(convertCase(field.name.text, "UPPER_UNDERSCORE"), "_WRAPPER,\n");
299
+ }
300
+ this.push("}\n\n");
301
+ // Constants
302
+ this.push(`public static final ${className} UNKNOWN = new ${className}(Kind.UNKNOWN, null);\n`);
303
+ for (const field of constantFields) {
304
+ const skirName = field.name.text;
305
+ const name = toEnumConstantName(field);
306
+ this.push(`public static final ${className} ${name} = new ${className}(Kind.${skirName}_CONST, null);\n`);
307
+ }
308
+ this.pushEol();
309
+ // WrapX methods
310
+ for (const field of wrapperFields) {
311
+ const upperCamelName = convertCase(field.name.text, "UpperCamel");
312
+ const upperUnderscoreName = convertCase(field.name.text, "UPPER_UNDERSCORE");
313
+ const type = field.type;
314
+ const initializerType = typeSpeller.getJavaType(type, "initializer");
315
+ const frozenType = typeSpeller.getJavaType(type, "frozen");
316
+ const toFrozenExpr = this.toFrozenExpression("value", type, "can-be-null", "_e");
317
+ this.push(`public static ${className} wrap${upperCamelName}(${initializerType} value) {\n`, `final ${frozenType} v = ${toFrozenExpr};\n`, `return new ${className}(Kind.${upperUnderscoreName}_WRAPPER, v);\n`, "}\n\n");
318
+ }
319
+ // Declare fields
320
+ this.push("private final Kind kind;\n", "private final java.lang.Object value;\n\n");
321
+ // Constructor
322
+ this.push(`private ${className}(Kind kind, java.lang.Object value) {\n`, "this.kind = kind;\n", "this.value = value;\n", "}\n\n");
323
+ // kind()
324
+ this.push("public Kind kind() {\n", "return kind;\n", "}\n\n");
325
+ // asX() methods
326
+ for (const field of wrapperFields) {
327
+ const type = typeSpeller.getJavaType(field.type, "frozen");
328
+ const upperCamelName = convertCase(field.name.text, "UpperCamel");
329
+ const upperUnderscoreName = convertCase(field.name.text, "UPPER_UNDERSCORE");
330
+ this.push(`public ${type} as${upperCamelName}() {\n`, `if (kind != Kind.${upperUnderscoreName}_WRAPPER) {\n`, `throw new java.lang.IllegalStateException("kind=" + kind.name());\n`, "}\n", `return (${type}) value;\n`, "}\n\n");
331
+ }
332
+ // Visitor
333
+ this.push("public interface Visitor<R> {\n", "R onUnknown();\n");
334
+ for (const field of constantFields) {
335
+ const upperCamelName = convertCase(field.name.text, "UpperCamel");
336
+ this.push(`R on${upperCamelName}();\n`);
337
+ }
338
+ for (const field of wrapperFields) {
339
+ const upperCamelName = convertCase(field.name.text, "UpperCamel");
340
+ const type = typeSpeller.getJavaType(field.type, "frozen");
341
+ this.push(`R on${upperCamelName}(${type} value);\n`);
342
+ }
343
+ this.push("}\n\n");
344
+ // accept()
345
+ this.push("public <R> R accept(Visitor<R> visitor) {\n", "return switch (kind) {\n");
346
+ for (const field of constantFields) {
347
+ const upperUnderscoreName = field.name.text;
348
+ const upperCamelName = convertCase(field.name.text, "UpperCamel");
349
+ this.push(`case ${upperUnderscoreName}_CONST -> visitor.on${upperCamelName}();\n`);
350
+ }
351
+ for (const field of wrapperFields) {
352
+ const upperUnderscoreName = convertCase(field.name.text, "UPPER_UNDERSCORE");
353
+ const upperCamelName = convertCase(field.name.text, "UpperCamel");
354
+ const type = typeSpeller.getJavaType(field.type, "frozen");
355
+ this.push(`case ${upperUnderscoreName}_WRAPPER -> visitor.on${upperCamelName}((${type}) value);\n`);
356
+ }
357
+ this.push("default -> visitor.onUnknown();\n", "};\n", "}\n\n");
358
+ // equals()
359
+ this.push("@java.lang.Override\n", "public boolean equals(Object other) {\n", `if (!(other instanceof ${className})) return false;\n`, `final ${className} otherEnum = (${className}) other;\n`, "if (kind == Kind.UNKNOWN) return otherEnum.kind == Kind.UNKNOWN;\n", "return kind == otherEnum.kind && java.util.Objects.equals(value, otherEnum.value);\n", "}\n\n");
360
+ // hashCode()
361
+ this.push("@java.lang.Override\n", "public int hashCode() {\n", "final Object v = kind == Kind.UNKNOWN ? null : value;\n", "return 31 * java.util.Objects.hashCode(v) + kind.ordinal();\n", "}\n\n");
362
+ // toString()
363
+ this.push("@java.lang.Override\n", "public java.lang.String toString() {\n", `return SERIALIZER.toJsonCode(this, build.skir.JsonFlavor.READABLE);\n`, "}\n\n");
364
+ // _serializerImpl
365
+ {
366
+ const serializerType = `build.skir.internal.EnumSerializer<${className}>`;
367
+ const unrecognizedVariantType = `build.skir.internal.UnrecognizedVariant<${className}>`;
368
+ this.push(`private static final ${serializerType} _serializerImpl = (\n`, "build.skir.internal.EnumSerializer.Companion.create(\n", `"${getRecordId(recordLocation)}",\n`, `${toJavaStringLiteral(docToCommentText(record.doc))},\n`, `(${className} it) -> it.kind().ordinal(),\n`, "Kind.values().length,\n", "UNKNOWN,\n", `(${unrecognizedVariantType} it) -> new ${className}(Kind.UNKNOWN, it),\n`, `(${className} it) -> (${unrecognizedVariantType}) it.value\n`, ")\n", ");\n\n");
369
+ }
370
+ // SERIALIZER
371
+ this.push(`public static final build.skir.Serializer<${className}> SERIALIZER = (\n`, "build.skir.internal.SerializersKt.makeSerializer(_serializerImpl)\n", ");\n\n");
372
+ // TYPE_DESCRIPTOR
373
+ {
374
+ const typeDescriptorType = `build.skir.reflection.EnumDescriptor.Reflective<${className}>`;
375
+ this.push(`public static final ${typeDescriptorType} TYPE_DESCRIPTOR = (\n`, "_serializerImpl\n", ");\n\n");
376
+ }
377
+ // Finalize serializer
378
+ this.push("static {\n");
379
+ for (const field of constantFields) {
380
+ const name = field.name.text;
381
+ this.push("_serializerImpl.addConstantVariant(\n", `${field.number},\n`, `"${name}",\n`, `Kind.${name}_CONST.ordinal(),\n`, `${toJavaStringLiteral(docToCommentText(field.doc))},\n`, `${toEnumConstantName(field)}\n`, ");\n");
382
+ }
383
+ for (const field of wrapperFields) {
384
+ const type = field.type;
385
+ const javaType = typeSpeller.getJavaType(type, "frozen", "must-be-object");
386
+ const serializerExpression = typeSpeller.getSerializerExpression(type);
387
+ const skirName = field.name.text;
388
+ const upperCamelName = convertCase(skirName, "UpperCamel");
389
+ const kindConstName = convertCase(skirName, "UPPER_UNDERSCORE") + "_WRAPPER";
390
+ this.push("_serializerImpl.addWrapperVariant(\n", `${field.number},\n`, `"${field.name.text}",\n`, `Kind.${kindConstName}.ordinal(),\n`, `${serializerExpression},\n`, `${toJavaStringLiteral(docToCommentText(field.doc))},\n`, `(${javaType} it) -> wrap${upperCamelName}(it),\n`, `(${className} it) -> it.as${upperCamelName}()\n`, ");\n");
391
+ }
392
+ for (const removedNumber of record.removedNumbers) {
393
+ this.push(`_serializerImpl.addRemovedNumber(${removedNumber});\n`);
394
+ }
395
+ this.push("_serializerImpl.finalizeEnum();\n", "}\n\n");
396
+ // Nested classes
397
+ this.writeClassesForNestedRecords(record);
398
+ this.push("}\n\n");
399
+ }
400
+ writeClassesForNestedRecords(record) {
401
+ for (const nestedRecord of record.nestedRecords) {
402
+ this.writeClassForRecord(nestedRecord, "nested");
403
+ }
404
+ }
405
+ get path() {
406
+ const { target, modulePath } = this;
407
+ let className;
408
+ switch (target.kind) {
409
+ case "record": {
410
+ const record = this.recordMap.get(target.key);
411
+ className = this.namer.getClassName(record).name;
412
+ break;
413
+ }
414
+ case "methods": {
415
+ className = "Methods";
416
+ break;
417
+ }
418
+ case "constants": {
419
+ className = "Constants";
420
+ break;
421
+ }
422
+ }
423
+ return modulePath.replace(/\.skir$/, "") + `/${className}.java`;
424
+ }
425
+ writeClassForMethods(methods) {
426
+ this.push("public final class Methods {\n\n", "private Methods() {}\n\n");
427
+ for (const method of methods) {
428
+ this.writeMethod(method);
429
+ }
430
+ this.push("}\n\n");
431
+ }
432
+ writeMethod(method) {
433
+ const { typeSpeller } = this;
434
+ const requestType = typeSpeller.getJavaType(method.requestType, "frozen");
435
+ const requestSerializer = typeSpeller.getSerializerExpression(method.requestType);
436
+ const responseType = typeSpeller.getJavaType(method.responseType, "frozen");
437
+ const responseSerializer = typeSpeller.getSerializerExpression(method.responseType);
438
+ const skirName = method.name.text;
439
+ const javaName = convertCase(skirName, "UPPER_UNDERSCORE");
440
+ const methodType = `build.skir.service.Method<${requestType}, ${responseType}>`;
441
+ this.push(`public static final ${methodType} ${javaName} = (\n`, "new build.skir.service.Method<>(\n", `"${skirName}",\n`, `${method.number}L,\n`, `${requestSerializer},\n`, `${responseSerializer},\n`, `${toJavaStringLiteral(docToCommentText(method.doc))}\n`, ")\n", ");\n\n");
442
+ }
443
+ writeClassForConstants(constants) {
444
+ this.push("public final class Constants {\n\n", "private Constants() {}\n\n");
445
+ for (const constant of constants) {
446
+ this.writeConstant(constant);
447
+ }
448
+ this.push("}\n\n");
449
+ }
450
+ writeConstant(constant) {
451
+ const { typeSpeller } = this;
452
+ const javaType = typeSpeller.getJavaType(constant.type, "frozen");
453
+ const name = constant.name.text;
454
+ const serializerExpression = typeSpeller.getSerializerExpression(constant.type);
455
+ const jsonStringLiteral = JSON.stringify(JSON.stringify(constant.valueAsDenseJson));
456
+ this.push(`public static final ${javaType} ${name} = (\n`, `${serializerExpression}.fromJsonCode(${jsonStringLiteral})\n`, ");\n\n");
457
+ }
458
+ getDefaultExpression(type) {
459
+ switch (type.kind) {
460
+ case "primitive": {
461
+ switch (type.primitive) {
462
+ case "bool":
463
+ return "false";
464
+ case "int32":
465
+ case "int64":
466
+ case "uint64":
467
+ return "0";
468
+ case "float32":
469
+ return "0.0f";
470
+ case "float64":
471
+ return "0.0";
472
+ case "timestamp":
473
+ return "java.time.Instant.EPOCH";
474
+ case "string":
475
+ return '""';
476
+ case "bytes":
477
+ return "okio.ByteString.EMPTY";
478
+ default: {
479
+ const _ = type.primitive;
480
+ throw Error();
481
+ }
482
+ }
483
+ }
484
+ case "array": {
485
+ if (type.key) {
486
+ return `build.skir.internal.FrozenListKt.emptyKeyedList()`;
487
+ }
488
+ else {
489
+ return `build.skir.internal.FrozenListKt.emptyFrozenList()`;
490
+ }
491
+ }
492
+ case "optional": {
493
+ return "java.util.Optional.empty()";
494
+ }
495
+ case "record": {
496
+ const record = this.typeSpeller.recordMap.get(type.key);
497
+ const kotlinType = this.typeSpeller.getJavaType(type, "frozen");
498
+ switch (record.record.recordType) {
499
+ case "struct": {
500
+ return `${kotlinType}.DEFAULT`;
501
+ }
502
+ case "enum": {
503
+ return `${kotlinType}.UNKNOWN`;
504
+ }
505
+ }
506
+ break;
507
+ }
508
+ }
509
+ }
510
+ toFrozenExpression(inputExpr, type, nullability, it) {
511
+ const { namer } = this;
512
+ switch (type.kind) {
513
+ case "primitive": {
514
+ switch (type.primitive) {
515
+ case "bool":
516
+ case "int32":
517
+ case "int64":
518
+ case "uint64":
519
+ case "float32":
520
+ case "float64":
521
+ return inputExpr;
522
+ case "timestamp":
523
+ case "string":
524
+ case "bytes":
525
+ return nullability === "can-be-null"
526
+ ? `java.util.Objects.requireNonNull(${inputExpr})`
527
+ : inputExpr;
528
+ default: {
529
+ const _ = type.primitive;
530
+ throw Error();
531
+ }
532
+ }
533
+ }
534
+ case "array": {
535
+ const itemToFrozenExpr = this.toFrozenExpression(it, type.item, "can-be-null", it + "_");
536
+ const frozenListKt = "build.skir.internal.FrozenListKt";
537
+ if (type.key) {
538
+ const path = type.key.path
539
+ .map((f) => namer.structFieldToJavaName(f.name.text) + "()")
540
+ .join(".");
541
+ if (itemToFrozenExpr === it) {
542
+ return `${frozenListKt}.toKeyedList(\n${inputExpr},\n"${path}",\n(${it}) -> ${it}.${path}\n)`;
543
+ }
544
+ else {
545
+ return `${frozenListKt}.toKeyedList(\n${inputExpr},\n"${path}",\n(${it}) -> ${it}.${path},\n(${it}) -> ${itemToFrozenExpr}\n)`;
546
+ }
547
+ }
548
+ else {
549
+ if (itemToFrozenExpr === it) {
550
+ return `${frozenListKt}.toFrozenList(${inputExpr})`;
551
+ }
552
+ else {
553
+ return `${frozenListKt}.toFrozenList(\n${inputExpr},\n(${it}) -> ${itemToFrozenExpr}\n)`;
554
+ }
555
+ }
556
+ }
557
+ case "optional": {
558
+ const otherExpr = this.toFrozenExpression(it, type.other, "never-null", it + "_");
559
+ return `${inputExpr}.map(\n(${it}) -> ${otherExpr}\n)`;
560
+ }
561
+ case "record": {
562
+ return nullability === "can-be-null"
563
+ ? `java.util.Objects.requireNonNull(${inputExpr})`
564
+ : inputExpr;
565
+ }
566
+ }
567
+ }
568
+ push(...code) {
569
+ this.code += code.join("");
570
+ }
571
+ pushEol() {
572
+ this.code += "\n";
573
+ }
574
+ joinLinesAndFixFormatting() {
575
+ const indentUnit = " ";
576
+ let result = "";
577
+ // The indent at every line is obtained by repeating indentUnit N times,
578
+ // where N is the length of this array.
579
+ const contextStack = [];
580
+ // Returns the last element in `contextStack`.
581
+ const peakTop = () => contextStack.at(-1);
582
+ const getMatchingLeftBracket = (r) => {
583
+ switch (r) {
584
+ case "}":
585
+ return "{";
586
+ case ")":
587
+ return "(";
588
+ case "]":
589
+ return "[";
590
+ case ">":
591
+ return "<";
592
+ }
593
+ };
594
+ for (let line of this.code.split("\n")) {
595
+ line = line.trim();
596
+ if (line.length <= 0) {
597
+ // Don't indent empty lines.
598
+ result += "\n";
599
+ continue;
600
+ }
601
+ const firstChar = line[0];
602
+ switch (firstChar) {
603
+ case "}":
604
+ case ")":
605
+ case "]":
606
+ case ">": {
607
+ const left = getMatchingLeftBracket(firstChar);
608
+ while (contextStack.pop() !== left) {
609
+ if (contextStack.length <= 0) {
610
+ throw Error();
611
+ }
612
+ }
613
+ break;
614
+ }
615
+ case ".": {
616
+ if (peakTop() !== ".") {
617
+ contextStack.push(".");
618
+ }
619
+ break;
620
+ }
621
+ }
622
+ const indent = indentUnit.repeat(contextStack.length);
623
+ result += `${indent}${line.trimEnd()}\n`;
624
+ if (line.startsWith("//")) {
625
+ continue;
626
+ }
627
+ const lastChar = line.slice(-1);
628
+ switch (lastChar) {
629
+ case "{":
630
+ case "(":
631
+ case "[":
632
+ case "<": {
633
+ // The next line will be indented
634
+ contextStack.push(lastChar);
635
+ break;
636
+ }
637
+ case ":":
638
+ case "=": {
639
+ if (peakTop() !== ":") {
640
+ contextStack.push(":");
641
+ }
642
+ break;
643
+ }
644
+ case ";":
645
+ case ",": {
646
+ if (peakTop() === "." || peakTop() === ":") {
647
+ contextStack.pop();
648
+ }
649
+ }
650
+ }
651
+ }
652
+ return (result
653
+ // Remove spaces enclosed within curly brackets if that's all there is.
654
+ .replace(/\{\s+\}/g, "{}")
655
+ // Remove spaces enclosed within round brackets if that's all there is.
656
+ .replace(/\(\s+\)/g, "()")
657
+ // Remove spaces enclosed within square brackets if that's all there is.
658
+ .replace(/\[\s+\]/g, "[]")
659
+ // Remove empty line following an open curly bracket.
660
+ .replace(/(\{\n *)\n/g, "$1")
661
+ // Remove empty line preceding a closed curly bracket.
662
+ .replace(/\n(\n *\})/g, "$1")
663
+ // Coalesce consecutive empty lines.
664
+ .replace(/\n\n\n+/g, "\n\n")
665
+ .replace(/\n\n$/g, "\n"));
666
+ }
667
+ }
668
+ function getRecordId(struct) {
669
+ const modulePath = struct.modulePath;
670
+ const qualifiedRecordName = struct.recordAncestors
671
+ .map((r) => r.name.text)
672
+ .join(".");
673
+ return `${modulePath}:${qualifiedRecordName}`;
674
+ }
675
+ function toJavaStringLiteral(input) {
676
+ const escaped = input.replace(/[\\"\x00-\x1f\x7f-\xff]/g, (char) => {
677
+ switch (char) {
678
+ case '"':
679
+ return '\\"';
680
+ case "\\":
681
+ return "\\\\";
682
+ case "\b":
683
+ return "\\b";
684
+ case "\f":
685
+ return "\\f";
686
+ case "\n":
687
+ return "\\n";
688
+ case "\r":
689
+ return "\\r";
690
+ case "\t":
691
+ return "\\t";
692
+ default: {
693
+ // Handle non-printable characters using Unicode escapes (\\uXXXX)
694
+ const code = char.charCodeAt(0).toString(16).padStart(4, "0");
695
+ return `\\u${code}`;
696
+ }
697
+ }
698
+ });
699
+ return `"${escaped}"`;
700
+ }
701
+ function commentify(textOrLines) {
702
+ const text = (typeof textOrLines === "string" ? textOrLines : textOrLines.join("\n"))
703
+ .trim()
704
+ .replace(/\n{3,}/g, "\n\n")
705
+ .replace("*/", "* /");
706
+ if (text.length <= 0) {
707
+ return "";
708
+ }
709
+ const lines = text.split("\n");
710
+ if (lines.length === 1) {
711
+ return `/** ${text} */\n`;
712
+ }
713
+ else {
714
+ return ["/**\n", ...lines.map((line) => ` * ${line}\n`), " */\n"].join("");
715
+ }
716
+ }
717
+ function docToCommentText(doc) {
718
+ return doc.pieces
719
+ .map((p) => {
720
+ switch (p.kind) {
721
+ case "text":
722
+ return p.text;
723
+ case "reference":
724
+ return "`" + p.referenceRange.text.slice(1, -1) + "`";
725
+ }
726
+ })
727
+ .join("");
728
+ }
729
+ export const GENERATOR = new JavaCodeGenerator();
730
+ //# sourceMappingURL=index.js.map