1use super::*;
2
3fn conv(name: &str) -> String {
4 name.replace("Bool", "Boolean")
5 .replace("Int32", "Int")
6 .replace("Int64", "Long")
7 .replace("Float32", "Float")
8 .replace("Float64", "Double")
9}
10
11pub struct Generator {
12 files: HashMap<String, String>,
13}
14
15fn type_name(schema: &Schema) -> String {
16 match schema {
17 Schema::Bool => "Boolean".to_owned(),
18 Schema::Int32 => "Int".to_owned(),
19 Schema::Int64 => "Long".to_owned(),
20 Schema::Float32 => "Float".to_owned(),
21 Schema::Float64 => "Double".to_owned(),
22 Schema::String => "String".to_owned(),
23 Schema::Struct(Struct { name, .. })
24 | Schema::OneOf {
25 base_name: name, ..
26 }
27 | Schema::Enum {
28 base_name: name, ..
29 } => format!("model.{}", name.camel_case(conv)),
30 Schema::Option(inner) => format!("{}?", type_name(inner)),
31 Schema::Vec(inner) => format!("Array<{}>", type_name(inner)),
32 Schema::Map(key, value) => format!("MutableMap<{}, {}>", type_name(key), type_name(value)),
33 }
34}
35
36fn default_value(schema: &Schema) -> String {
37 match schema {
38 Schema::Bool => "false".to_owned(),
39 Schema::Int32 => "0".to_owned(),
40 Schema::Int64 => "0L".to_owned(),
41 Schema::Float32 => "0.0f".to_owned(),
42 Schema::Float64 => "0.0".to_owned(),
43 Schema::Option(_) => "null".to_owned(),
44 Schema::String
45 | Schema::Struct(_)
46 | Schema::OneOf { .. }
47 | Schema::Enum { .. }
48 | Schema::Vec(_)
49 | Schema::Map(_, _) => unreachable!(),
50 }
51}
52
53fn can_lateinit(schema: &Schema) -> bool {
54 match schema {
55 Schema::Bool
56 | Schema::Int32
57 | Schema::Int64
58 | Schema::Float32
59 | Schema::Float64
60 | Schema::Option(_) => false,
61 Schema::String
62 | Schema::Struct(_)
63 | Schema::OneOf { .. }
64 | Schema::Enum { .. }
65 | Schema::Vec(_)
66 | Schema::Map(_, _) => true,
67 }
68}
69
70fn index_var_name(index_var: &mut usize) -> String {
71 let result = "ijk".chars().nth(*index_var).unwrap();
72 *index_var += 1;
73 result.to_string()
74}
75
76fn var_name(name: &str) -> &str {
77 match name.rfind('.') {
78 Some(index) => &name[(index + 1)..],
79 None => name,
80 }
81}
82
83fn write_struct(
84 writer: &mut Writer,
85 struc: &Struct,
86 base: Option<(&Name, usize)>,
87) -> std::fmt::Result {
88 if let Some((base_name, _)) = base {
90 writeln!(
91 writer,
92 "class {} : {} {{",
93 struc.name.camel_case(conv),
94 base_name.camel_case(conv)
95 )?;
96 } else {
97 writeln!(writer, "class {} {{", struc.name.camel_case(conv))?;
98 }
99 writer.inc_ident();
100
101 for field in &struc.fields {
103 writeln!(
104 writer,
105 "{}var {}: {}{}",
106 if can_lateinit(&field.schema) {
107 "lateinit "
108 } else {
109 ""
110 },
111 field.name.mixed_case(conv),
112 type_name(&field.schema),
113 if can_lateinit(&field.schema) {
114 String::new()
115 } else {
116 format!(" = {}", default_value(&field.schema))
117 }
118 )?;
119 }
120
121 writeln!(writer, "constructor() {{}}")?;
123 if !struc.fields.is_empty() {
124 write!(writer, "constructor(")?;
125 for (index, field) in struc.fields.iter().enumerate() {
126 if index > 0 {
127 write!(writer, ", ")?;
128 }
129 write!(
130 writer,
131 "{}: {}",
132 field.name.mixed_case(conv),
133 type_name(&field.schema),
134 )?;
135 }
136 writeln!(writer, ") {{")?;
137 for field in &struc.fields {
138 writeln!(
139 writer,
140 " this.{} = {}",
141 field.name.mixed_case(conv),
142 field.name.mixed_case(conv)
143 )?;
144 }
145 writeln!(writer, "}}")?;
146 }
147
148 writeln!(writer, "companion object {{")?;
150 writer.inc_ident();
151 if let Some((_, tag)) = base {
152 writeln!(writer, "val TAG = {}", tag)?;
153 }
154 writeln!(writer, "@Throws(java.io.IOException::class)")?;
155 writeln!(
156 writer,
157 "fun readFrom(stream: java.io.InputStream): {} {{",
158 struc.name.camel_case(conv),
159 )?;
160 writer.inc_ident();
161 writeln!(writer, "val result = {}()", struc.name.camel_case(conv),)?;
162 for field in &struc.fields {
163 fn assign(
164 writer: &mut Writer,
165 to: &str,
166 schema: &Schema,
167 index_var: &mut usize,
168 ) -> std::fmt::Result {
169 match schema {
170 Schema::Bool => {
171 writeln!(writer, "{} = StreamUtil.readBoolean(stream)", to)?;
172 }
173 Schema::Int32 => {
174 writeln!(writer, "{} = StreamUtil.readInt(stream)", to)?;
175 }
176 Schema::Int64 => {
177 writeln!(writer, "{} = StreamUtil.readLong(stream)", to)?;
178 }
179 Schema::Float32 => {
180 writeln!(writer, "{} = StreamUtil.readFloat(stream)", to)?;
181 }
182 Schema::Float64 => {
183 writeln!(writer, "{} = StreamUtil.readDouble(stream)", to)?;
184 }
185 Schema::String => {
186 writeln!(writer, "{} = StreamUtil.readString(stream)", to)?;
187 }
188 Schema::Struct(Struct { name, .. })
189 | Schema::OneOf {
190 base_name: name, ..
191 } => {
192 writeln!(
193 writer,
194 "{} = model.{}.readFrom(stream)",
195 to,
196 name.camel_case(conv)
197 )?;
198 }
199 Schema::Option(inner) => {
200 writeln!(writer, "if (StreamUtil.readBoolean(stream)) {{")?;
201 writer.inc_ident();
202 assign(writer, to, inner, index_var)?;
203 writer.dec_ident();
204 writeln!(writer, "}} else {{")?;
205 writeln!(writer, " {} = null", to)?;
206 writeln!(writer, "}}")?;
207 }
208 Schema::Vec(inner) => {
209 writeln!(writer, "{} = Array(StreamUtil.readInt(stream), {{", to)?;
210 writer.inc_ident();
211 writeln!(writer, "var {}Value: {}", var_name(to), type_name(inner))?;
212 assign(writer, &format!("{}Value", var_name(to)), inner, index_var)?;
213 writeln!(writer, "{}Value", var_name(to))?;
214 writer.dec_ident();
215 writeln!(writer, "}})")?;
216 }
217 Schema::Map(key_type, value_type) => {
218 let to_size = format!("{}Size", var_name(to));
219 writeln!(writer, "val {} = StreamUtil.readInt(stream)", to_size)?;
220 writeln!(writer, "{} = mutableMapOf()", to)?;
221 let index_var_name = index_var_name(index_var);
222 writeln!(writer, "for ({} in 0 until {}) {{", index_var_name, to_size)?;
223 writer.inc_ident();
224 writeln!(writer, "var {}Key: {}", var_name(to), type_name(key_type))?;
225 assign(writer, &format!("{}Key", var_name(to)), key_type, index_var)?;
226 writeln!(
227 writer,
228 "var {}Value: {}",
229 var_name(to),
230 type_name(value_type)
231 )?;
232 assign(
233 writer,
234 &format!("{}Value", var_name(to)),
235 value_type,
236 index_var,
237 )?;
238 writeln!(
239 writer,
240 "{}.put({}Key, {}Value)",
241 to,
242 var_name(to),
243 var_name(to)
244 )?;
245 writer.dec_ident();
246 writeln!(writer, "}}")?;
247 }
248 Schema::Enum {
249 documentation: _,
250 base_name,
251 variants,
252 } => {
253 writeln!(writer, "when (StreamUtil.readInt(stream)) {{")?;
254 for (tag, variant) in variants.iter().enumerate() {
255 write!(writer, "{} ->", tag)?;
256 writeln!(
257 writer,
258 "{} = model.{}.{}",
259 to,
260 base_name.camel_case(conv),
261 variant.name.shouty_snake_case(conv)
262 )?;
263 }
264 writeln!(
265 writer,
266 "else -> throw java.io.IOException(\"Unexpected tag value\")"
267 )?;
268 writeln!(writer, "}}")?;
269 }
270 }
271 Ok(())
272 }
273 assign(
274 writer,
275 &format!("result.{}", field.name.mixed_case(conv)),
276 &field.schema,
277 &mut 0,
278 )?;
279 }
280 writeln!(writer, "return result")?;
281 writer.dec_ident();
282 writeln!(writer, "}}")?;
283 writer.dec_ident();
284 writeln!(writer, "}}")?;
285
286 writeln!(writer, "@Throws(java.io.IOException::class)")?;
288 writeln!(
289 writer,
290 "{}fun writeTo(stream: java.io.OutputStream) {{",
291 if base.is_some() { "override " } else { "" }
292 )?;
293 writer.inc_ident();
294 if base.is_some() {
295 writeln!(writer, "StreamUtil.writeInt(stream, TAG)")?;
296 }
297 if let Some(magic) = struc.magic {
298 writeln!(writer, "StreamUtil.writeInt(stream, {})", magic)?;
299 }
300 for field in &struc.fields {
301 fn write(writer: &mut Writer, value: &str, schema: &Schema) -> std::fmt::Result {
302 match schema {
303 Schema::Bool => {
304 writeln!(writer, "StreamUtil.writeBoolean(stream, {})", value)?;
305 }
306 Schema::Int32 => {
307 writeln!(writer, "StreamUtil.writeInt(stream, {})", value)?;
308 }
309 Schema::Int64 => {
310 writeln!(writer, "StreamUtil.writeLong(stream, {})", value)?;
311 }
312 Schema::Float32 => {
313 writeln!(writer, "StreamUtil.writeFloat(stream, {})", value)?;
314 }
315 Schema::Float64 => {
316 writeln!(writer, "StreamUtil.writeDouble(stream, {})", value)?;
317 }
318 Schema::String => {
319 writeln!(writer, "StreamUtil.writeString(stream, {})", value)?;
320 }
321 Schema::Struct(_) | Schema::OneOf { .. } => {
322 writeln!(writer, "{}.writeTo(stream)", value)?;
323 }
324 Schema::Option(inner) => {
325 writeln!(writer, "val {} = {};", var_name(value), value)?;
326 writeln!(writer, "if ({} == null) {{", var_name(value))?;
327 writeln!(writer, " StreamUtil.writeBoolean(stream, false)")?;
328 writeln!(writer, "}} else {{")?;
329 writer.inc_ident();
330 writeln!(writer, "StreamUtil.writeBoolean(stream, true)")?;
331 write(writer, &var_name(value), inner)?;
332 writer.dec_ident();
333 writeln!(writer, "}}")?;
334 }
335 Schema::Vec(inner) => {
336 writeln!(writer, "StreamUtil.writeInt(stream, {}.size)", value)?;
337 writeln!(writer, "for ({}Element in {}) {{", var_name(value), value)?;
338 writer.inc_ident();
339 write(writer, &format!("{}Element", var_name(value)), inner)?;
340 writer.dec_ident();
341 writeln!(writer, "}}")?;
342 }
343 Schema::Map(key_type, value_type) => {
344 writeln!(writer, "StreamUtil.writeInt(stream, {}.size)", value)?;
345 writeln!(writer, "for ({}Entry in {}) {{", var_name(value), value,)?;
346 writer.inc_ident();
347 write(writer, &format!("{}Entry.key", var_name(value)), key_type)?;
348 write(
349 writer,
350 &format!("{}Entry.value", var_name(value)),
351 value_type,
352 )?;
353 writer.dec_ident();
354 writeln!(writer, "}}")?;
355 }
356 Schema::Enum { .. } => {
357 writeln!(writer, "StreamUtil.writeInt(stream, {}.tag)", value)?;
358 }
359 }
360 Ok(())
361 }
362 write(writer, &field.name.mixed_case(conv), &field.schema)?;
363 }
364 writer.dec_ident();
365 writeln!(writer, "}}")?;
366 writer.dec_ident();
367 writeln!(writer, "}}")?;
368 Ok(())
369}
370
371impl crate::Generator for Generator {
372 type Options = ();
373 fn new(_name: &str, _version: &str, _: ()) -> Self {
374 let mut files = HashMap::new();
375 files.insert(
376 "util/StreamUtil.kt".to_owned(),
377 include_str!("StreamUtil.kt").to_owned(),
378 );
379 Self { files }
380 }
381 fn result(self) -> GenResult {
382 self.files.into()
383 }
384 fn add_only(&mut self, schema: &Schema) {
385 match schema {
386 Schema::Enum {
387 documentation: _,
388 base_name,
389 variants,
390 } => {
391 let file_name = format!("model/{}.kt", base_name.camel_case(conv));
392 let mut writer = Writer::new();
393 writeln!(writer, "package model").unwrap();
394 writeln!(writer).unwrap();
395 writeln!(writer, "import util.StreamUtil").unwrap();
396 writeln!(writer).unwrap();
397 writeln!(
398 writer,
399 "enum class {} private constructor(var tag: Int) {{",
400 base_name.camel_case(conv)
401 )
402 .unwrap();
403 writer.inc_ident();
404 for (index, variant) in variants.iter().enumerate() {
405 writeln!(
406 writer,
407 "{}({}){}",
408 variant.name.shouty_snake_case(conv),
409 index,
410 if index + 1 < variants.len() { "," } else { "" }
411 )
412 .unwrap();
413 }
414 writer.dec_ident();
415 writeln!(writer, "}}").unwrap();
416 self.files.insert(file_name, writer.get());
417 }
418 Schema::Struct(struc) => {
419 let file_name = format!("model/{}.kt", struc.name.camel_case(conv));
420 let mut writer = Writer::new();
421 writeln!(writer, "package model").unwrap();
422 writeln!(writer).unwrap();
423 writeln!(writer, "import util.StreamUtil").unwrap();
424 writeln!(writer).unwrap();
425 write_struct(&mut writer, struc, None).unwrap();
426 self.files.insert(file_name, writer.get());
427 }
428 Schema::OneOf {
429 documentation: _,
430 base_name,
431 variants,
432 } => {
433 let file_name = format!("model/{}.kt", base_name.camel_case(conv));
434 let mut writer = Writer::new();
435 writeln!(writer, "package model").unwrap();
436 writeln!(writer).unwrap();
437 writeln!(writer, "import util.StreamUtil").unwrap();
438 writeln!(writer).unwrap();
439 writeln!(writer, "abstract class {} {{", base_name.camel_case(conv)).unwrap();
440 {
441 writer.inc_ident();
442 writeln!(writer, "@Throws(java.io.IOException::class)").unwrap();
443 writeln!(writer, "abstract fun writeTo(stream: java.io.OutputStream)").unwrap();
444 writeln!(writer, "companion object {{").unwrap();
445 writer.inc_ident();
446 writeln!(writer, "@Throws(java.io.IOException::class)").unwrap();
447 writeln!(
448 writer,
449 "fun readFrom(stream: java.io.InputStream): {} {{",
450 base_name.camel_case(conv)
451 )
452 .unwrap();
453 {
454 writer.inc_ident();
455 writeln!(writer, "when (StreamUtil.readInt(stream)) {{").unwrap();
456 writer.inc_ident();
457 for variant in variants {
458 write!(writer, "{}.TAG -> ", variant.name.camel_case(conv)).unwrap();
459 writeln!(
460 writer,
461 "return {}.readFrom(stream)",
462 variant.name.camel_case(conv)
463 )
464 .unwrap();
465 }
466 writeln!(
467 writer,
468 "else -> throw java.io.IOException(\"Unexpected tag value\")"
469 )
470 .unwrap();
471 writer.dec_ident();
472 writeln!(writer, "}}").unwrap();
473 writer.dec_ident();
474 }
475 writeln!(writer, "}}").unwrap();
476 writer.dec_ident();
477 writeln!(writer, "}}").unwrap();
478 for (tag, variant) in variants.iter().enumerate() {
479 writeln!(writer).unwrap();
480 write_struct(&mut writer, variant, Some((base_name, tag))).unwrap();
481 }
482 writer.dec_ident();
483 }
484 writeln!(writer, "}}").unwrap();
485 self.files.insert(file_name, writer.get());
486 }
487 Schema::Bool
488 | Schema::Int32
489 | Schema::Int64
490 | Schema::Float32
491 | Schema::Float64
492 | Schema::String
493 | Schema::Option(_)
494 | Schema::Vec(_)
495 | Schema::Map(_, _) => {}
496 }
497 }
498}