structurize 2.16.2__py3-none-any.whl

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.
Files changed (51) hide show
  1. avrotize/__init__.py +63 -0
  2. avrotize/__main__.py +6 -0
  3. avrotize/_version.py +34 -0
  4. avrotize/asn1toavro.py +160 -0
  5. avrotize/avrotize.py +152 -0
  6. avrotize/avrotocpp.py +483 -0
  7. avrotize/avrotocsharp.py +992 -0
  8. avrotize/avrotocsv.py +121 -0
  9. avrotize/avrotodatapackage.py +173 -0
  10. avrotize/avrotodb.py +1383 -0
  11. avrotize/avrotogo.py +476 -0
  12. avrotize/avrotographql.py +197 -0
  13. avrotize/avrotoiceberg.py +210 -0
  14. avrotize/avrotojava.py +1023 -0
  15. avrotize/avrotojs.py +250 -0
  16. avrotize/avrotojsons.py +481 -0
  17. avrotize/avrotojstruct.py +345 -0
  18. avrotize/avrotokusto.py +364 -0
  19. avrotize/avrotomd.py +137 -0
  20. avrotize/avrotools.py +168 -0
  21. avrotize/avrotoparquet.py +208 -0
  22. avrotize/avrotoproto.py +359 -0
  23. avrotize/avrotopython.py +622 -0
  24. avrotize/avrotorust.py +435 -0
  25. avrotize/avrotots.py +598 -0
  26. avrotize/avrotoxsd.py +344 -0
  27. avrotize/commands.json +2433 -0
  28. avrotize/common.py +829 -0
  29. avrotize/constants.py +5 -0
  30. avrotize/csvtoavro.py +132 -0
  31. avrotize/datapackagetoavro.py +76 -0
  32. avrotize/dependency_resolver.py +348 -0
  33. avrotize/jsonstoavro.py +1698 -0
  34. avrotize/jsonstostructure.py +2642 -0
  35. avrotize/jstructtoavro.py +878 -0
  36. avrotize/kstructtoavro.py +93 -0
  37. avrotize/kustotoavro.py +455 -0
  38. avrotize/parquettoavro.py +157 -0
  39. avrotize/proto2parser.py +498 -0
  40. avrotize/proto3parser.py +403 -0
  41. avrotize/prototoavro.py +382 -0
  42. avrotize/structuretocsharp.py +2005 -0
  43. avrotize/structuretojsons.py +498 -0
  44. avrotize/structuretopython.py +772 -0
  45. avrotize/xsdtoavro.py +413 -0
  46. structurize-2.16.2.dist-info/METADATA +805 -0
  47. structurize-2.16.2.dist-info/RECORD +51 -0
  48. structurize-2.16.2.dist-info/WHEEL +5 -0
  49. structurize-2.16.2.dist-info/entry_points.txt +2 -0
  50. structurize-2.16.2.dist-info/licenses/LICENSE +201 -0
  51. structurize-2.16.2.dist-info/top_level.txt +1 -0
avrotize/avrotojava.py ADDED
@@ -0,0 +1,1023 @@
1
+ # pylint: disable=too-many-arguments, too-many-locals, too-many-branches, too-many-statements, line-too-long
2
+
3
+ """ Generates Java classes from Avro schema """
4
+ import json
5
+ import os
6
+ from typing import Dict, List, Tuple, Union
7
+ from avrotize.constants import AVRO_VERSION, JACKSON_VERSION, JDK_VERSION
8
+
9
+ from avrotize.common import pascal, camel, is_generic_avro_type
10
+
11
+ INDENT = ' '
12
+ POM_CONTENT = """<?xml version="1.0" encoding="UTF-8"?>
13
+ <project xmlns="http://maven.apache.org/POM/4.0.0"
14
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
15
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
16
+ <modelVersion>4.0.0</modelVersion>
17
+ <groupId>{groupid}</groupId>
18
+ <artifactId>{artifactid}</artifactId>
19
+ <version>1.0-SNAPSHOT</version>
20
+ <properties>
21
+ <maven.compiler.source>{JDK_VERSION}</maven.compiler.source>
22
+ <maven.compiler.target>{JDK_VERSION}</maven.compiler.target>
23
+ </properties>
24
+ <dependencies>
25
+ <dependency>
26
+ <groupId>org.apache.avro</groupId>
27
+ <artifactId>avro</artifactId>
28
+ <version>{AVRO_VERSION}</version>
29
+ </dependency>
30
+ <dependency>
31
+ <groupId>com.fasterxml.jackson</groupId>
32
+ <artifactId>jackson-bom</artifactId>
33
+ <version>{JACKSON_VERSION}</version>
34
+ <type>pom</type>
35
+ </dependency>
36
+ </dependencies>
37
+ </project>
38
+ """
39
+
40
+ PREAMBLE_TOBYTEARRAY = \
41
+ """
42
+ byte[] result = null;
43
+ String mediaType = contentType.split(";")[0].trim().toLowerCase();
44
+ """
45
+
46
+
47
+ EPILOGUE_TOBYTEARRAY_COMPRESSION = \
48
+ """
49
+ if (result != null && mediaType.endsWith("+gzip")) {
50
+ try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
51
+ GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream)) {
52
+ gzipOutputStream.write(result);
53
+ gzipOutputStream.finish();
54
+ result = byteArrayOutputStream.toByteArray();
55
+ } catch (IOException e) {
56
+ throw new UnsupportedOperationException("Error compressing data to gzip");
57
+ }
58
+ }
59
+ """
60
+
61
+ EPILOGUE_TOBYTEARRAY = \
62
+ """
63
+ throw new UnsupportedOperationException("Unsupported media type + mediaType");
64
+ """
65
+
66
+ PREAMBLE_FROMDATA_COMPRESSION = \
67
+ """
68
+ if (mediaType.endsWith("+gzip")) {
69
+ InputStream stream = null;
70
+
71
+ if (data instanceof InputStream) {
72
+ stream = (InputStream) data;
73
+ } else if (data instanceof byte[]) {
74
+ stream = new ByteArrayInputStream((byte[]) data);
75
+ } else {
76
+ throw new UnsupportedOperationException("Data is not of a supported type for gzip decompression");
77
+ }
78
+
79
+ try (InputStream gzipStream = new GZIPInputStream(stream);
80
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
81
+ byte[] buffer = new byte[1024];
82
+ int bytesRead;
83
+ while ((bytesRead = gzipStream.read(buffer)) != -1) {
84
+ outputStream.write(buffer, 0, bytesRead);
85
+ }
86
+ data = outputStream.toByteArray();
87
+ } catch (IOException e) {
88
+ e.printStackTrace();
89
+ }
90
+ }
91
+ """
92
+
93
+
94
+ JSON_FROMDATA_THROWS = \
95
+ ",JsonProcessingException, IOException"
96
+ JSON_FROMDATA = \
97
+ """
98
+ if ( mediaType == "application/json") {
99
+ if (data instanceof byte[]) {
100
+ ByteArrayInputStream stream = new ByteArrayInputStream((byte[]) data);
101
+ return (new ObjectMapper()).readValue(stream, {typeName}.class);
102
+ }
103
+ else if (data instanceof InputStream) {
104
+ return (new ObjectMapper()).readValue((InputStream)data, {typeName}.class);
105
+ }
106
+ else if (data instanceof JsonNode) {
107
+ return (new ObjectMapper()).readValue(((JsonNode)data).toString(), {typeName}.class);
108
+ }
109
+ else if ( data instanceof String) {
110
+ return (new ObjectMapper()).readValue(((String)data), {typeName}.class);
111
+ }
112
+ throw new UnsupportedOperationException("Data is not of a supported type for JSON conversion to {typeName}");
113
+ }
114
+ """
115
+ JSON_TOBYTEARRAY_THROWS = ",JsonProcessingException"
116
+ JSON_TOBYTEARRAY = \
117
+ """
118
+ if ( mediaType == "application/json") {
119
+ result = new ObjectMapper().writeValueAsBytes(this);
120
+ }
121
+ """
122
+
123
+ AVRO_FROMDATA_THROWS = ",IOException"
124
+ AVRO_FROMDATA = \
125
+ """
126
+ if ( mediaType == "avro/binary" || mediaType == "application/vnd.apache.avro+avro") {
127
+ if (data instanceof byte[]) {
128
+ return AVROREADER.read(new {typeName}(), DecoderFactory.get().binaryDecoder((byte[])data, null));
129
+ } else if (data instanceof InputStream) {
130
+ return AVROREADER.read(new {typeName}(), DecoderFactory.get().binaryDecoder((InputStream)data, null));
131
+ }
132
+ throw new UnsupportedOperationException("Data is not of a supported type for Avro conversion to {typeName}");
133
+ } else if ( mediaType == "avro/json" || mediaType == "application/vnd.apache.avro+json") {
134
+ if (data instanceof byte[]) {
135
+ return AVROREADER.read(new {typeName}(), DecoderFactory.get().jsonDecoder({typeName}.AVROSCHEMA, new ByteArrayInputStream((byte[])data)));
136
+ } else if (data instanceof InputStream) {
137
+ return AVROREADER.read(new {typeName}(), DecoderFactory.get().jsonDecoder({typeName}.AVROSCHEMA, (InputStream)data));
138
+ } else if (data instanceof String) {
139
+ return AVROREADER.read(new {typeName}(), DecoderFactory.get().jsonDecoder({typeName}.AVROSCHEMA, (String)data));
140
+ }
141
+ throw new UnsupportedOperationException("Data is not of a supported type for Avro conversion to {typeName}");
142
+ }
143
+ """
144
+
145
+
146
+ AVRO_TOBYTEARRAY_THROWS = ",IOException"
147
+ AVRO_TOBYTEARRAY = \
148
+ """
149
+ if ( mediaType == "avro/binary" || mediaType == "application/vnd.apache.avro+avro") {
150
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
151
+ Encoder encoder = EncoderFactory.get().binaryEncoder(out, null);
152
+ AVROWRITER.write(this, encoder);
153
+ encoder.flush();
154
+ result = out.toByteArray();
155
+ }
156
+ else if ( mediaType == "avro/json" || mediaType == "application/vnd.apache.avro+json") {
157
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
158
+ Encoder encoder = EncoderFactory.get().jsonEncoder({typeName}.AVROSCHEMA, out);
159
+ AVROWRITER.write(this, encoder);
160
+ encoder.flush();
161
+ result = out.toByteArray();
162
+ }
163
+ """
164
+
165
+
166
+ JsonNode = Dict[str, 'JsonNode'] | List['JsonNode'] | str | None
167
+
168
+
169
+ def flatten_type_name(name: str) -> str:
170
+ """Strips the namespace from a name"""
171
+ if name.endswith('[]'):
172
+ return flatten_type_name(name[:-2]+'Array')
173
+ base_name = pascal(name.replace(' ', '').split('.')[-1].replace('>', '').replace('<', '').replace(',', ''))
174
+ return base_name
175
+
176
+
177
+ def is_java_reserved_word(word: str) -> bool:
178
+ """Checks if a word is a Java reserved word"""
179
+ reserved_words = [
180
+ 'abstract', 'assert', 'boolean', 'break', 'byte', 'case', 'catch', 'char', 'class', 'const',
181
+ 'continue', 'default', 'do', 'double', 'else', 'enum', 'extends', 'final', 'finally', 'float',
182
+ 'for', 'goto', 'if', 'implements', 'import', 'instanceof', 'int', 'interface', 'long', 'native',
183
+ 'new', 'package', 'private', 'protected', 'public', 'return', 'short', 'static', 'strictfp',
184
+ 'super', 'switch', 'synchronized', 'this', 'throw', 'throws', 'transient', 'try', 'void', 'volatile',
185
+ 'while', 'true', 'false', 'null', 'record',
186
+ ]
187
+ return word in reserved_words
188
+
189
+
190
+ class AvroToJava:
191
+ """Converts Avro schema to Java classes, including Jackson annotations and Avro SpecificRecord methods"""
192
+
193
+ def __init__(self, base_package: str = '') -> None:
194
+ self.base_package = base_package.replace('.', '/')
195
+ self.output_dir = os.getcwd()
196
+ self.avro_annotation = False
197
+ self.jackson_annotations = False
198
+ self.pascal_properties = False
199
+ self.generated_types_avro_namespace: Dict[str,str] = {}
200
+ self.generated_types_java_package: Dict[str,str] = {}
201
+
202
+ def qualified_name(self, package: str, name: str) -> str:
203
+ """Concatenates package and name using a dot separator"""
204
+ slash_package_name = package.replace('.', '/')
205
+ safe_package_slash = self.safe_package(slash_package_name.lower())
206
+ safe_package = safe_package_slash.replace('/', '.')
207
+ return f"{safe_package}.{name}" if package else name
208
+
209
+ def join_packages(self, parent_package: str, package: str) -> str:
210
+ """Joins package and name using a dot separator"""
211
+ if parent_package and package:
212
+ return f"{parent_package}.{package}".lower()
213
+ elif parent_package:
214
+ return parent_package.lower()
215
+ return package.lower()
216
+
217
+ class JavaType:
218
+ """Java type definition"""
219
+
220
+ def __init__(self, type_name: str, union_types: List['AvroToJava.JavaType'] | None = None, is_class: bool = False, is_enum: bool = False) -> None:
221
+ self.type_name = type_name
222
+ self.union_types = union_types
223
+ self.is_class = is_class
224
+ self.is_enum = is_enum
225
+
226
+ def safe_identifier(self, name: str, class_name: str = '') -> str:
227
+ """Converts a name to a safe Java identifier"""
228
+ if is_java_reserved_word(name):
229
+ return f"_{name}"
230
+ if class_name and name == class_name:
231
+ return f"{name}_"
232
+ return name
233
+
234
+ def safe_package(self, packageName: str) -> str:
235
+ """Converts a name to a safe Java identifier by checking each path segment"""
236
+ segments = packageName.split('/')
237
+ safe_segments = [
238
+ self.safe_identifier(segment)
239
+ for segment in segments
240
+ ]
241
+
242
+ return '/'.join(safe_segments)
243
+
244
+ def map_primitive_to_java(self, avro_type: str, is_optional: bool) -> JavaType:
245
+ """Maps Avro primitive types to Java types"""
246
+ optional_mapping = {
247
+ 'null': 'Void',
248
+ 'boolean': 'Boolean',
249
+ 'int': 'Integer',
250
+ 'long': 'Long',
251
+ 'float': 'Float',
252
+ 'double': 'Double',
253
+ 'bytes': 'byte[]',
254
+ 'string': 'String',
255
+ }
256
+ required_mapping = {
257
+ 'null': 'void',
258
+ 'boolean': 'boolean',
259
+ 'int': 'int',
260
+ 'long': 'long',
261
+ 'float': 'float',
262
+ 'double': 'double',
263
+ 'bytes': 'byte[]',
264
+ 'string': 'String',
265
+ }
266
+ if '.' in avro_type:
267
+ type_name = avro_type.split('.')[-1]
268
+ package_name = '.'.join(avro_type.split('.')[:-1]).lower()
269
+ avro_type = self.qualified_name(package_name, type_name)
270
+ if avro_type in self.generated_types_avro_namespace:
271
+ kind = self.generated_types_avro_namespace[avro_type]
272
+ qualified_class_name = self.qualified_name(self.base_package, avro_type)
273
+ return AvroToJava.JavaType(qualified_class_name, is_class=kind=="class", is_enum=kind=="enum")
274
+ else:
275
+ return AvroToJava.JavaType(required_mapping.get(avro_type, avro_type) if not is_optional else optional_mapping.get(avro_type, avro_type))
276
+
277
+ def is_java_primitive(self, java_type: JavaType) -> bool:
278
+ """Checks if a Java type is a primitive type"""
279
+ return java_type.type_name in [
280
+ 'void', 'boolean', 'int', 'long', 'float', 'double', 'byte[]', 'String',
281
+ 'Boolean', 'Integer', 'Long', 'Float', 'Double', 'Void']
282
+
283
+ def is_java_optional_type(self, java_type: JavaType) -> bool:
284
+ """Checks if a Java type is an optional type"""
285
+ return java_type.type_name in ['Void', 'Boolean', 'Integer', 'Long', 'Float', 'Double']
286
+
287
+ def is_java_numeric_type(self, java_type: JavaType) -> bool:
288
+ """Checks if a Java type is a numeric type"""
289
+ return java_type.type_name in ['int', 'long', 'float', 'double', 'Integer', 'Long', 'Float', 'Double']
290
+
291
+ def is_java_integer_type(self, java_type: JavaType) -> bool:
292
+ """Checks if a Java type is an integer type"""
293
+ return java_type.type_name in ['int', 'long', 'Integer', 'Long']
294
+
295
+ def convert_avro_type_to_java(self, class_name: str, field_name: str, avro_type: Union[str, Dict, List], parent_package: str, nullable: bool = False) -> JavaType:
296
+ """Converts Avro type to Java type"""
297
+ if isinstance(avro_type, str):
298
+ return self.map_primitive_to_java(avro_type, nullable)
299
+ elif isinstance(avro_type, list):
300
+ if (is_generic_avro_type(avro_type)):
301
+ return AvroToJava.JavaType('Object')
302
+ non_null_types = [t for t in avro_type if t != 'null']
303
+ if len(non_null_types) == 1:
304
+ if isinstance(non_null_types[0], str):
305
+ return self.map_primitive_to_java(non_null_types[0], True)
306
+ else:
307
+ return self.convert_avro_type_to_java(class_name, field_name, non_null_types[0], parent_package)
308
+ else:
309
+ if self.jackson_annotations:
310
+ return AvroToJava.JavaType(self.generate_embedded_union_class_jackson(class_name, field_name, non_null_types, parent_package, write_file=True), is_class=True)
311
+ else:
312
+ types: List[AvroToJava.JavaType] = [self.convert_avro_type_to_java(
313
+ class_name, field_name, t, parent_package) for t in non_null_types]
314
+ return AvroToJava.JavaType('Object', types)
315
+ elif isinstance(avro_type, dict):
316
+ if avro_type['type'] in ['record', 'enum']:
317
+ return self.generate_class_or_enum(avro_type, parent_package, write_file=True)
318
+ elif avro_type['type'] == 'fixed':
319
+ if 'logicalType' in avro_type and avro_type['logicalType'] == 'decimal':
320
+ return AvroToJava.JavaType('BigDecimal')
321
+ return AvroToJava.JavaType('byte[]')
322
+ elif avro_type['type'] == 'bytes' and 'logicalType' in avro_type:
323
+ if avro_type['logicalType'] == 'decimal':
324
+ return AvroToJava.JavaType('BigDecimal')
325
+ elif avro_type['type'] == 'array':
326
+ item_type = self.convert_avro_type_to_java(class_name, field_name, avro_type['items'], parent_package, nullable=True).type_name
327
+ return AvroToJava.JavaType(f"List<{item_type}>")
328
+ elif avro_type['type'] == 'map':
329
+ values_type = self.convert_avro_type_to_java(class_name, field_name, avro_type['values'], parent_package, nullable=True).type_name
330
+ return AvroToJava.JavaType(f"Map<String,{values_type}>")
331
+ elif 'logicalType' in avro_type:
332
+ if avro_type['logicalType'] == 'date':
333
+ return AvroToJava.JavaType('LocalDate')
334
+ elif avro_type['logicalType'] == 'time-millis' or avro_type['logicalType'] == 'time-micros':
335
+ return AvroToJava.JavaType('LocalTime')
336
+ elif avro_type['logicalType'] == 'timestamp-millis' or avro_type['logicalType'] == 'timestamp-micros':
337
+ return AvroToJava.JavaType('Instant')
338
+ elif avro_type['logicalType'] == 'local-timestamp-millis' or avro_type['logicalType'] == 'local-timestamp-micros':
339
+ return AvroToJava.JavaType('LocalDateTime')
340
+ elif avro_type['logicalType'] == 'uuid':
341
+ return AvroToJava.JavaType('UUID')
342
+ elif avro_type['logicalType'] == 'duration':
343
+ return AvroToJava.JavaType('Duration')
344
+ return self.convert_avro_type_to_java(class_name, field_name, avro_type['type'], parent_package)
345
+ return 'Object'
346
+
347
+ def generate_class_or_enum(self, avro_schema: Dict, parent_package: str, write_file: bool = True) -> JavaType:
348
+ """ Generates a Java class or enum from an Avro schema """
349
+ if avro_schema['type'] == 'record':
350
+ return self.generate_class(avro_schema, parent_package, write_file)
351
+ elif avro_schema['type'] == 'enum':
352
+ return self.generate_enum(avro_schema, parent_package, write_file)
353
+ return AvroToJava.JavaType('Object')
354
+
355
+ def generate_class(self, avro_schema: Dict, parent_package: str, write_file: bool) -> JavaType:
356
+ """ Generates a Java class from an Avro record schema """
357
+ class_definition = ''
358
+ if 'doc' in avro_schema:
359
+ class_definition += f"/** {avro_schema['doc']} */\n"
360
+ namespace = avro_schema.get('namespace', parent_package)
361
+ if not 'namespace' in avro_schema:
362
+ avro_schema['namespace'] = namespace
363
+ package = self.join_packages(self.base_package, namespace).replace('.', '/').lower()
364
+ package = package.replace('.', '/').lower()
365
+ package = self.safe_package(package)
366
+ class_name = self.safe_identifier(avro_schema['name'])
367
+ namespace_qualified_name = self.qualified_name(namespace,avro_schema['name'])
368
+ qualified_class_name = self.qualified_name(package.replace('/', '.'), class_name)
369
+ if namespace_qualified_name in self.generated_types_avro_namespace:
370
+ return AvroToJava.JavaType(qualified_class_name, is_class=True)
371
+ self.generated_types_avro_namespace[namespace_qualified_name] = "class"
372
+ self.generated_types_java_package[qualified_class_name] = "class"
373
+ fields_str = [self.generate_property(class_name, field, namespace) for field in avro_schema.get('fields', [])]
374
+ class_body = "\n".join(fields_str)
375
+ class_definition += f"public class {class_name}"
376
+ if self.avro_annotation:
377
+ class_definition += " implements SpecificRecord"
378
+ class_definition += " {\n"
379
+ class_definition += f"{INDENT}public {class_name}() {{}}\n"
380
+ class_definition += class_body
381
+
382
+ if self.avro_annotation:
383
+ class_definition += f"\n{INDENT}public {class_name}(GenericData.Record record) {{\n"
384
+ class_definition += f"{INDENT*2}for( int i = 0; i < record.getSchema().getFields().size(); i++ ) {{\n"
385
+ class_definition += f"{INDENT*3}this.put(i, record.get(i));\n"
386
+ class_definition += f"{INDENT*2}}}\n"
387
+ class_definition += f"{INDENT}}}\n"
388
+
389
+ if self.avro_annotation:
390
+ avro_schema_json = json.dumps(avro_schema)
391
+ avro_schema_json = avro_schema_json.replace('"', '§')
392
+ avro_schema_json = f"\"+\n{INDENT}\"".join(
393
+ [avro_schema_json[i:i+80] for i in range(0, len(avro_schema_json), 80)])
394
+ avro_schema_json = avro_schema_json.replace('§', '\\"')
395
+ class_definition += f"\n\n{INDENT}public static final Schema AVROSCHEMA = new Schema.Parser().parse(\n{INDENT}\"{avro_schema_json}\");"
396
+ class_definition += f"\n{INDENT}public static final DatumWriter<{class_name}> AVROWRITER = new SpecificDatumWriter<{class_name}>(AVROSCHEMA);"
397
+ class_definition += f"\n{INDENT}public static final DatumReader<{class_name}> AVROREADER = new SpecificDatumReader<{class_name}>(AVROSCHEMA);\n"
398
+
399
+ if self.jackson_annotations:
400
+ class_definition += f"\n{INDENT}@JsonIgnore"
401
+ class_definition += f"\n{INDENT}@Override\n{INDENT}public Schema getSchema(){{ return AVROSCHEMA; }}\n"
402
+ class_definition += self.generate_avro_get_method(class_name, avro_schema.get('fields', []), namespace)
403
+ class_definition += self.generate_avro_put_method(class_name, avro_schema.get('fields', []), namespace)
404
+
405
+ # emit toByteArray method
406
+ class_definition += f"\n\n{INDENT}/**\n{INDENT} * Converts the object to a byte array\n{INDENT} * @param contentType the content type of the byte array\n{INDENT} * @return the byte array\n{INDENT} */\n"
407
+ class_definition += f"{INDENT}public byte[] toByteArray(String contentType) throws UnsupportedOperationException" + \
408
+ f"{ JSON_TOBYTEARRAY_THROWS if self.jackson_annotations else '' }" + \
409
+ f"{ AVRO_TOBYTEARRAY_THROWS if self.avro_annotation else '' } {{"
410
+ if self.jackson_annotations or self.avro_annotation:
411
+ class_definition += f'\n{INDENT*2}'.join((PREAMBLE_TOBYTEARRAY).split("\n"))
412
+ if self.avro_annotation:
413
+ class_definition += f'\n{INDENT*2}'+f'\n{INDENT*2}'.join(
414
+ AVRO_TOBYTEARRAY.strip().replace("{typeName}", class_name).split("\n"))
415
+ if self.jackson_annotations:
416
+ class_definition += f'\n{INDENT*2}'+f'\n{INDENT*2}'.join(
417
+ JSON_TOBYTEARRAY.strip().replace("{typeName}", class_name).split("\n"))
418
+ if self.avro_annotation or self.jackson_annotations:
419
+ class_definition += f'\n{INDENT*2}'.join(EPILOGUE_TOBYTEARRAY_COMPRESSION.split("\n"))
420
+ class_definition += f'\n{INDENT*2}if ( result != null ) {{ return result; }}'
421
+ class_definition += (f'\n{INDENT*2}'.join((EPILOGUE_TOBYTEARRAY.strip()).split("\n")))+f"\n{INDENT}}}"
422
+
423
+ # emit fromData factory method
424
+ class_definition += f"\n\n{INDENT}/**\n{INDENT} * Converts the data to an object\n{INDENT} * @param data the data to convert\n{INDENT} * @param contentType the content type of the data\n{INDENT} * @return the object\n{INDENT} */\n"
425
+ class_definition += f"{INDENT}public static {class_name} fromData(Object data, String contentType) throws UnsupportedOperationException" + \
426
+ f"{ JSON_FROMDATA_THROWS if self.jackson_annotations else '' }" + \
427
+ f"{ AVRO_FROMDATA_THROWS if self.avro_annotation else '' } {{"
428
+ class_definition += f'\n{INDENT*2}if ( data instanceof {class_name}) return ({class_name})data;'
429
+
430
+ if self.avro_annotation or self.jackson_annotations:
431
+ class_definition += f'\n{INDENT*2}String mediaType = contentType.split(";")[0].trim().toLowerCase();'
432
+ class_definition += f'\n{INDENT*2}'.join((PREAMBLE_FROMDATA_COMPRESSION).split("\n"))
433
+ if self.avro_annotation:
434
+ class_definition += f'\n{INDENT*2}'+f'\n{INDENT*2}'.join(
435
+ AVRO_FROMDATA.strip().replace("{typeName}", class_name).split("\n"))
436
+ if self.jackson_annotations:
437
+ class_definition += f'\n{INDENT*2}'+f'\n{INDENT*2}'.join(
438
+ JSON_FROMDATA.strip().replace("{typeName}", class_name).split("\n"))
439
+ class_definition += f"\n{INDENT*2}throw new UnsupportedOperationException(\"Unsupported media type \"+ contentType);\n{INDENT}}}"
440
+
441
+ if self.jackson_annotations:
442
+ class_definition += self.create_is_json_match_method(avro_schema, avro_schema.get('namespace', namespace), class_name)
443
+
444
+ class_definition += "\n}"
445
+
446
+ if write_file:
447
+ self.write_to_file(package, class_name, class_definition)
448
+ return AvroToJava.JavaType(qualified_class_name, is_class=True)
449
+
450
+ def create_is_json_match_method(self, avro_schema, parent_namespace, class_name) -> str:
451
+ """ Generates the isJsonMatch method for a class using Jackson """
452
+ predicates = ''
453
+ class_definition = ''
454
+ class_definition += f"\n\n{INDENT}/**\n{INDENT} * Checks if the JSON node matches the schema\n{INDENT}"
455
+ class_definition += f"\n{INDENT}@param node The JSON node to check */"
456
+ class_definition += f"\n{INDENT}public static boolean isJsonMatch(com.fasterxml.jackson.databind.JsonNode node)\n{INDENT}{{"
457
+ field_defs = ''
458
+
459
+ field_count = 0
460
+ for field in avro_schema.get('fields', []):
461
+ if field_count > 0:
462
+ field_defs += f" && \n{INDENT*3}"
463
+ field_count += 1
464
+ field_name = field['name']
465
+ if field_name == class_name:
466
+ field_name += "_"
467
+ field_type = self.convert_avro_type_to_java(class_name, field_name, field['type'], parent_namespace)
468
+ predicate, clause = self.get_is_json_match_clause(class_name, field_name, field_type)
469
+ field_defs += clause
470
+ if predicate:
471
+ predicates += predicate + "\n"
472
+ if ( len(predicates) > 0 ):
473
+ class_definition += f'\n{INDENT*2}'+f'\n{INDENT*2}'.join(predicates.split('\n'))
474
+ class_definition += f"\n{INDENT*2}return {field_defs}"
475
+ class_definition += f";\n{INDENT}}}"
476
+ return class_definition
477
+
478
+ def get_is_json_match_clause(self, class_name: str, field_name: str, field_type: JavaType) -> Tuple[str, str]:
479
+ """ Generates the isJsonMatch clause for a field using Jackson """
480
+ class_definition = ''
481
+ predicates = ''
482
+ field_name_js = field_name
483
+ is_optional = self.is_java_optional_type(field_type)
484
+
485
+ if is_optional:
486
+ node_check = f"!node.has(\"{field_name_js}\") || node.get(\"{field_name_js}\").isNull() || node.get(\"{field_name_js}\")"
487
+ else:
488
+ node_check = f"node.has(\"{field_name_js}\") && node.get(\"{field_name_js}\")"
489
+
490
+ if field_type.type_name == 'byte[]':
491
+ class_definition += f"({node_check}.isBinary())"
492
+ elif field_type.type_name == 'string' or field_type.type_name == 'String':
493
+ class_definition += f"({node_check}.isTextual())"
494
+ elif field_type.type_name == 'int' or field_type.type_name == 'Integer':
495
+ class_definition += f"({node_check}.canConvertToInt())"
496
+ elif field_type.type_name == 'long' or field_type.type_name == 'Long':
497
+ class_definition += f"({node_check}.canConvertToLong())"
498
+ elif field_type.type_name == 'float' or field_type.type_name == 'Float':
499
+ class_definition += f"({node_check}.isFloat())"
500
+ elif field_type.type_name == 'double' or field_type.type_name == 'Double':
501
+ class_definition += f"({node_check}.isDouble())"
502
+ elif field_type.type_name == 'BigDecimal':
503
+ class_definition += f"({node_check}.isBigDecimal())"
504
+ elif field_type.type_name == 'boolean' or field_type.type_name == 'Boolean':
505
+ class_definition += f"({node_check}.isBoolean())"
506
+ elif field_type.type_name == 'UUID':
507
+ class_definition += f"({node_check}.isTextual())"
508
+ elif field_type.type_name == 'LocalDate':
509
+ class_definition += f"({node_check}.isTextual())"
510
+ elif field_type.type_name == 'LocalTime':
511
+ class_definition += f"({node_check}.isTextual())"
512
+ elif field_type.type_name == 'Instant':
513
+ class_definition += f"({node_check}.isTextual())"
514
+ elif field_type.type_name == 'LocalDateTime':
515
+ class_definition += f"({node_check}.isTextual())"
516
+ elif field_type.type_name == 'Duration':
517
+ class_definition += f"({node_check}.isTextual())"
518
+ elif field_type.type_name == "Object":
519
+ class_definition += f"({node_check}.isObject())"
520
+ elif field_type.type_name.startswith("List<"):
521
+ items_type = field_type.type_name[5:-1]
522
+ pred = f"Predicate<JsonNode> val{field_name_js} = (JsonNode n) -> n.isArray() && !n.elements().hasNext() || "
523
+ pred_test = self.predicate_test(items_type)
524
+ if pred_test:
525
+ pred += "n.elements().next()" + pred_test
526
+ elif items_type in self.generated_types_java_package:
527
+ kind = self.generated_types_java_package[items_type]
528
+ if kind == "enum":
529
+ pred += f"n.elements().next().isTextual() && Enum.valueOf({items_type}.class, n.elements().next().asText()) != null"
530
+ else:
531
+ pred += f"{items_type}.isJsonMatch(n.elements().next())"
532
+ else:
533
+ pred += "true"
534
+ predicates += pred + ";"
535
+ class_definition += f"(node.has(\"{field_name_js}\") && val{field_name_js}.test(node.get(\"{field_name_js}\")))"
536
+ elif field_type.type_name.startswith("Map<"):
537
+ comma_offset = field_type.type_name.find(',')+1
538
+ values_type = field_type.type_name[comma_offset:-1]
539
+ pred = f"Predicate<JsonNode> val{field_name_js} = (JsonNode n) -> n.isObject() && !n.elements().hasNext() || "
540
+ pred_test = self.predicate_test(values_type)
541
+ if pred_test:
542
+ pred += "n.elements().next()" + pred_test
543
+ elif values_type in self.generated_types_java_package:
544
+ kind = self.generated_types_java_package[values_type]
545
+ if kind == "enum":
546
+ pred += f"n.elements().next().isTextual() && Enum.valueOf({values_type}.class, n.elements().next().asText()) != null"
547
+ else:
548
+ pred += f"{values_type}.isJsonMatch(n.elements().next())"
549
+ else:
550
+ pred += "true"
551
+ predicates += pred + ";"
552
+ class_definition += f"(node.has(\"{field_name_js}\") && val{field_name_js}.test(node.get(\"{field_name_js}\")))"
553
+ elif field_type.is_class:
554
+ class_definition += f"(node.has(\"{field_name_js}\") && {field_type.type_name}.isJsonMatch(node.get(\"{field_name_js}\")))"
555
+ elif field_type.is_enum:
556
+ class_definition += f"(node.get(\"{field_name_js}\").isTextual() && Enum.valueOf({field_type.type_name}.class, node.get(\"{field_name_js}\").asText()) != null)"
557
+ else:
558
+ is_union = False
559
+ field_union = pascal(field_name) + 'Union'
560
+ if field_type == field_union:
561
+ field_union = class_name + "." + pascal(field_name) + 'Union'
562
+ type_kind = self.generated_types_avro_namespace[field_union] if field_union in self.generated_types_avro_namespace else "class"
563
+ if type_kind == "union":
564
+ is_union = True
565
+ class_definition += f"({node_check}.isObject() && {field_type.type_name}.isJsonMatch(node.get(\"{field_name_js}\")))"
566
+ if not is_union:
567
+ class_definition += f"(node.has(\"{field_name_js}\"))"
568
+ return predicates, class_definition
569
+
570
+ def predicate_test(self, items_type):
571
+ """ Generates the predicate test for a list or map"""
572
+ if items_type == "String":
573
+ return ".isTextual()"
574
+ elif items_type in ['int', 'Integer']:
575
+ return ".canConvertToInt()"
576
+ elif items_type in ['long', 'Long']:
577
+ return ".canConvertToLong()"
578
+ elif items_type in ['float', 'Float', 'double', 'Double', 'decimal']:
579
+ return ".isNumber()"
580
+ elif items_type in ['boolean', 'Boolean']:
581
+ return ".isBoolean()"
582
+ elif items_type == 'byte[]':
583
+ return ".isBinary()"
584
+ elif items_type == 'UUID':
585
+ return ".isTextual()"
586
+ elif items_type == 'LocalDate':
587
+ return ".isTextual()"
588
+ elif items_type == 'LocalTime':
589
+ return ".isTextual()"
590
+ elif items_type == 'Instant':
591
+ return ".isTextual()"
592
+ elif items_type == 'LocalDateTime':
593
+ return ".isTextual()"
594
+ elif items_type == 'Duration':
595
+ return ".isTextual()"
596
+ elif items_type == "Object":
597
+ return ".isObject()"
598
+ return ""
599
+
600
+ def get_is_json_match_clause_type(self, element_name: str, class_name: str, field_type: JavaType) -> str:
601
+ """ Generates the isJsonMatch clause for a field using Jackson """
602
+ predicates = ''
603
+ class_definition = ''
604
+ is_optional = field_type.type_name[-1] == '?'
605
+ #is_optional = field_type[-1] == '?'
606
+ #field_type = field_type[:-1] if is_optional else field_type
607
+ is_optional = False
608
+ node_check = f"{element_name}.isMissingNode() == false && {element_name}"
609
+ null_check = f"{element_name}.isNull()" if is_optional else "false"
610
+ if field_type.type_name == 'byte[]':
611
+ class_definition += f"({node_check}.isBinary()){f' || {null_check}' if is_optional else ''}"
612
+ elif field_type.type_name == 'String':
613
+ class_definition += f"({node_check}.isTextual()){f' || {null_check}' if is_optional else ''}"
614
+ elif self.is_java_numeric_type(field_type):
615
+ class_definition += f"({node_check}.isNumber()){f' || {null_check}' if is_optional else ''}"
616
+ elif field_type.type_name == 'bool' or field_type.type_name == 'Boolean':
617
+ class_definition += f"({node_check}.isBoolean()){f' || {null_check}' if is_optional else ''}"
618
+ elif field_type.type_name.startswith("List<"):
619
+ items_type = field_type.type_name[5:-1]
620
+ predicates += f"Predicate<JsonNode> val{element_name}. = (JsonNode n) -> n.isObject() && n.fields().hasNext() && n.fields().next().getValue().isTextual();"
621
+ class_definition += f"({node_check}.isArray()){f' || {null_check}' if is_optional else ''}"
622
+ elif field_type.type_name.startswith("Map<"):
623
+ values_type = field_type.type_name[4:-1]
624
+ class_definition += f"({node_check}.isObject()){f' || {null_check}' if is_optional else ''}"
625
+ elif field_type.is_class:
626
+ class_definition += f"({null_check} || {field_type.type_name}.isJsonMatch({element_name}))"
627
+ elif field_type.is_enum:
628
+ class_definition += f"({null_check} || ({node_check}.isTextual() && Enum.valueOf({field_type.type_name}.class, {element_name}.asText()) != null))"
629
+ else:
630
+ is_union = False
631
+ field_union = pascal(element_name) + 'Union'
632
+ if field_type == field_union:
633
+ field_union = class_name + "." + pascal(element_name) + 'Union'
634
+ type_kind = self.generated_types_avro_namespace[field_union] if field_union in self.generated_types_avro_namespace else "class"
635
+ if type_kind == "union":
636
+ is_union = True
637
+ class_definition += f"({null_check} || {field_type}.isJsonMatch({element_name}))"
638
+ if not is_union:
639
+ class_definition += f"({node_check}.isObject()){f' || {null_check}' if is_optional else ''}"
640
+
641
+ return class_definition
642
+
643
+ def generate_avro_get_method(self, class_name: str, fields: List[Dict], parent_package: str) -> str:
644
+ """ Generates the get method for SpecificRecord """
645
+ get_method = f"\n{INDENT}@Override\n{INDENT}public Object get(int field$) {{\n"
646
+ get_method += f"{INDENT * 2}switch (field$) {{\n"
647
+ for index, field in enumerate(fields):
648
+ field_name = pascal(field['name']) if self.pascal_properties else field['name']
649
+ field_name = self.safe_identifier(field_name, class_name)
650
+ field_type = self.convert_avro_type_to_java(class_name, field_name, field['type'], parent_package)
651
+ if field_type.type_name in self.generated_types_avro_namespace and self.generated_types_avro_namespace[field_type.type_name] == "union":
652
+ get_method += f"{INDENT * 3}case {index}: return this.{field_name}!=null?this.{field_name}.toObject():null;\n"
653
+ else:
654
+ get_method += f"{INDENT * 3}case {index}: return this.{field_name};\n"
655
+ get_method += f"{INDENT * 3}default: throw new AvroRuntimeException(\"Bad index: \" + field$);\n"
656
+ get_method += f"{INDENT * 2}}}\n{INDENT}}}\n"
657
+ return get_method
658
+
659
+ def generate_avro_put_method(self, class_name: str, fields: List[Dict], parent_package: str) -> str:
660
+ """ Generates the put method for SpecificRecord """
661
+ suppress_unchecked = False
662
+ put_method = f"\n{INDENT}@Override\n{INDENT}public void put(int field$, Object value$) {{\n"
663
+ put_method += f"{INDENT * 2}switch (field$) {{\n"
664
+ for index, field in enumerate(fields):
665
+ field_name = pascal(field['name']) if self.pascal_properties else field['name']
666
+ field_name = self.safe_identifier(field_name, class_name)
667
+ field_type = self.convert_avro_type_to_java(class_name, field_name, field['type'], parent_package)
668
+ if field_type.type_name.startswith("List<") or field_type.type_name.startswith("Map<"):
669
+ suppress_unchecked = True
670
+ if field_type.type_name in self.generated_types_avro_namespace and self.generated_types_avro_namespace[field_type.type_name] == "union":
671
+ put_method += f"{INDENT * 3}case {index}: this.{field_name} = new {field_type.type_name}((GenericData.Record)value$); break;\n"
672
+ else:
673
+ if field_type.type_name == 'String':
674
+ put_method += f"{INDENT * 3}case {index}: this.{field_name} = value$.toString(); break;\n"
675
+ else:
676
+ put_method += f"{INDENT * 3}case {index}: this.{field_name} = ({field_type.type_name})value$; break;\n"
677
+ put_method += f"{INDENT * 3}default: throw new AvroRuntimeException(\"Bad index: \" + field$);\n"
678
+ put_method += f"{INDENT * 2}}}\n{INDENT}}}\n"
679
+ if suppress_unchecked:
680
+ put_method = f"\n{INDENT}@SuppressWarnings(\"unchecked\"){put_method}"
681
+ return put_method
682
+
683
+ def generate_enum(self, avro_schema: Dict, parent_package: str, write_file: bool) -> JavaType:
684
+ """ Generates a Java enum from an Avro enum schema """
685
+ enum_definition = ''
686
+ if 'doc' in avro_schema:
687
+ enum_definition += f"/** {avro_schema['doc']} */\n"
688
+
689
+ package = self.join_packages(self.base_package, avro_schema.get('namespace', parent_package)).replace('.', '/').lower()
690
+ enum_name = self.safe_identifier(avro_schema['name'])
691
+ type_name = self.qualified_name(package.replace('/', '.'), enum_name)
692
+ self.generated_types_avro_namespace[self.qualified_name(avro_schema.get('namespace', parent_package),avro_schema['name'])] = "enum"
693
+ self.generated_types_java_package[type_name] = "enum"
694
+ symbols = avro_schema.get('symbols', [])
695
+ symbols_str = ', '.join([symbol.upper() for symbol in symbols])
696
+ enum_definition += f"public enum {enum_name} {{\n"
697
+ enum_definition += f"{INDENT}{symbols_str};\n"
698
+ enum_definition += "}\n"
699
+ if write_file:
700
+ self.write_to_file(package, enum_name, enum_definition)
701
+ return AvroToJava.JavaType(type_name, is_enum=True)
702
+
703
+ def generate_embedded_union_class_jackson(self, class_name: str, field_name: str, avro_type: List, parent_package: str, write_file: bool) -> str:
704
+ """ Generates an embedded Union Class for Java using Jackson """
705
+ class_definition_ctors = class_definition_decls = class_definition_read = class_definition_write = class_definition = ''
706
+ class_definition_toobject = class_definition_fromobjectctor = class_definition_genericrecordctor = ''
707
+
708
+ list_is_json_match: List[str] = []
709
+ union_class_name = class_name + pascal(field_name) + 'Union'
710
+ package = self.join_packages(self.base_package, parent_package).replace('.', '/').lower()
711
+ union_types: List[AvroToJava.JavaType] = [self.convert_avro_type_to_java(class_name, field_name + "Option" + str(i), t, parent_package) for i, t in enumerate(avro_type)]
712
+ for i, union_type in enumerate(union_types):
713
+ # we need the nullable version (wrapper) of all primitive types
714
+ if self.is_java_primitive(union_type):
715
+ union_type = self.map_primitive_to_java(union_type.type_name, True)
716
+ union_variable_name = union_type.type_name
717
+ is_dict = is_list = False
718
+ if union_type.type_name.startswith("Map<"):
719
+ # handle Map types
720
+ is_dict = True
721
+ # find the comma
722
+ union_variable_name = flatten_type_name(union_type.type_name)
723
+ elif union_type.type_name.startswith("List<"):
724
+ # handle List types
725
+ is_list = True
726
+ union_variable_name = flatten_type_name(union_type.type_name)
727
+ elif union_type.type_name == "byte[]":
728
+ union_variable_name = "Bytes"
729
+ else:
730
+ union_variable_name = union_type.type_name.rsplit('.', 1)[-1]
731
+
732
+ union_variable_name = self.safe_identifier(union_variable_name, class_name)
733
+
734
+ # Constructor for each type
735
+ class_definition_ctors += \
736
+ f"{INDENT*1}public {union_class_name}({union_type.type_name} {union_variable_name}) {{\n{INDENT*2}this._{camel(union_variable_name)} = {union_variable_name};\n{INDENT*1}}}\n"
737
+
738
+ # Declarations
739
+ class_definition_decls += \
740
+ f"{INDENT*1}private {union_type.type_name} _{camel(union_variable_name)};\n" + \
741
+ f"{INDENT*1}public {union_type.type_name} get{union_variable_name}() {{ return _{camel(union_variable_name)}; }}\n";
742
+
743
+ class_definition_toobject += f"{INDENT*2}if (_{camel(union_variable_name)} != null) {{\n{INDENT*3}return _{camel(union_variable_name)};\n{INDENT*2}}}\n"
744
+
745
+ if self.avro_annotation and union_type.is_class:
746
+ class_definition_genericrecordctor += f"{INDENT*2}if ( {union_type.type_name}.AVROSCHEMA.getName().equals(record.getSchema().getName()) && {union_type.type_name}.AVROSCHEMA.getNamespace().equals(record.getSchema().getNamespace()) ) {{"
747
+ class_definition_genericrecordctor += f"\n{INDENT*3}this._{camel(union_variable_name)} = new {union_type.type_name}(record);\n{INDENT*3}return;\n{INDENT*2}}}\n"
748
+
749
+ # there can only be one list and one map in the union, so we don't need to differentiate this any further
750
+ if is_list:
751
+ class_definition_fromobjectctor += f"{INDENT*2}if (obj instanceof List<?>) {{\n{INDENT*3}this._{camel(union_variable_name)} = ({union_type.type_name})obj;\n{INDENT*3}return;\n{INDENT*2}}}\n"
752
+ elif is_dict:
753
+ class_definition_fromobjectctor += f"{INDENT*2}if (obj instanceof Map<?,?>) {{\n{INDENT*3}this._{camel(union_variable_name)} = ({union_type.type_name})obj;\n{INDENT*3}return;\n{INDENT*2}}}\n"
754
+ else:
755
+ class_definition_fromobjectctor += f"{INDENT*2}if (obj instanceof {union_type.type_name}) {{\n{INDENT*3}this._{camel(union_variable_name)} = ({union_type.type_name})obj;\n{INDENT*3}return;\n{INDENT*2}}}\n"
756
+
757
+ # Read method logic
758
+ if is_dict:
759
+ class_definition_read += f"{INDENT*3}if (node.isObject()) {{\n{INDENT*4}{union_type.type_name} map = mapper.readValue(node.toString(), new TypeReference<{union_type.type_name}>(){{}});\n{INDENT*3}return new {union_class_name}(map);\n{INDENT*3}}}\n"
760
+ elif is_list:
761
+ class_definition_read += f"{INDENT*3}if (node.isArray()) {{\n{INDENT*4}{union_type.type_name} list = mapper.readValue(node.toString(), new TypeReference<{union_type.type_name}>(){{}});\n{INDENT*4}return new {union_class_name}(list);\n{INDENT*3}}}\n"
762
+ elif self.is_java_primitive(union_type):
763
+ if union_type.type_name == "String":
764
+ class_definition_read += f"{INDENT*3}if (node.isTextual()) {{\n{INDENT*4}return new {union_class_name}(node.asText());\n{INDENT*3}}}\n"
765
+ elif union_type.type_name == "byte[]":
766
+ class_definition_read += f"{INDENT*3}if (node.isBinary()) {{\n{INDENT*4}return new {union_class_name}(node.binaryValue());\n{INDENT*3}}}\n"
767
+ elif union_type.type_name in ["int", "Int"]:
768
+ class_definition_read += f"{INDENT*3}if (node.canConvertToInt()) {{\n{INDENT*4}return new {union_class_name}(node.asInt());\n{INDENT*3}}}\n"
769
+ elif union_type.type_name in ["long", "Long"]:
770
+ class_definition_read += f"{INDENT*3}if (node.canConvertToLong()) {{\n{INDENT*4}return new {union_class_name}(node.asLong());\n{INDENT*3}}}\n"
771
+ elif union_type.type_name in ["float", "Float"]:
772
+ class_definition_read += f"{INDENT*3}if (node.isFloat()) {{\n{INDENT*4}return new {union_class_name}(node.floatValue());\n{INDENT*3}}}\n"
773
+ elif union_type.type_name in ["double", "Double"]:
774
+ class_definition_read += f"{INDENT*3}if (node.isDouble()) {{\n{INDENT*4}return new {union_class_name}(node.doubleValue());\n{INDENT*3}}}\n"
775
+ elif union_type.type_name == "decimal":
776
+ class_definition_read += f"{INDENT*3}if (node.isBigDecimal()) {{\n{INDENT*4}return new {union_class_name}(node.decimalValue());\n{INDENT*3}}}\n"
777
+ elif union_type.type_name in ["boolean", "Boolean"]:
778
+ class_definition_read += f"{INDENT*3}if (node.isBoolean()) {{\n{INDENT*4}return new {union_class_name}(node.asBoolean());\n{INDENT*3}}}\n"
779
+ else:
780
+ if union_type.is_enum:
781
+ class_definition_read += f"{INDENT*3}if (node.isTextual()) {{\n{INDENT*4}return new {union_class_name}(Enum.valueOf({union_type.type_name}.class, node.asText()));\n{INDENT*3}}}\n"
782
+ else:
783
+ class_definition_read += f"{INDENT*3}if (node.isObject() && {union_type.type_name}.isJsonMatch(node)) {{\n{INDENT*4}return new {union_class_name}(mapper.readValue(node.toString(), {union_type.type_name}.class));\n{INDENT*3}}}\n"
784
+
785
+ # Write method logic
786
+ class_definition_write += f"{INDENT*3}{union_type.type_name} {camel(union_variable_name)}Value = value.get{union_variable_name}();\n{INDENT*3}if ({camel(union_variable_name)}Value != null) {{\n{INDENT*4}generator.writeObject({camel(union_variable_name)}Value);\n{INDENT*4}return;\n{INDENT*3}}}\n"
787
+
788
+ # JSON match method logic
789
+ gij = self.get_is_json_match_clause_type("node", class_name, union_type)
790
+ if gij:
791
+ list_is_json_match.append(gij)
792
+
793
+ class_definition = f"@JsonSerialize(using = {union_class_name}.Serializer.class)\n"
794
+ class_definition += f"@JsonDeserialize(using = {union_class_name}.Deserializer.class)\n"
795
+ class_definition += f"public class {union_class_name} {{\n"
796
+ class_definition += class_definition_decls
797
+ class_definition += f"\n{INDENT}public " + union_class_name + "() {}\n"
798
+ if self.avro_annotation:
799
+ class_definition += f"\n{INDENT}public {union_class_name}(GenericData.Record record) {{\n"
800
+ class_definition += class_definition_genericrecordctor
801
+ class_definition += f"{INDENT*2}throw new UnsupportedOperationException(\"No record type is set in the union\");\n"
802
+ class_definition += f"{INDENT}}}\n"
803
+ class_definition += f"\n{INDENT}public {union_class_name}(Object obj) {{\n"
804
+ class_definition += class_definition_fromobjectctor
805
+ class_definition += f"{INDENT*2}throw new UnsupportedOperationException(\"No record type is set in the union\");\n"
806
+ class_definition += f"{INDENT}}}\n"
807
+ class_definition += class_definition_ctors
808
+ class_definition += f"\n{INDENT}public Object toObject() {{\n"
809
+ class_definition += class_definition_toobject
810
+ class_definition += f"{INDENT*2}throw new UnsupportedOperationException(\"No record type is set in the union\");\n"
811
+ class_definition += f"{INDENT}}}\n"
812
+ class_definition += f"\n{INDENT}public static class Serializer extends JsonSerializer<" + union_class_name + "> {\n"
813
+ class_definition += f"{INDENT*2}@Override\n"
814
+ class_definition += f"{INDENT*2}public void serialize(" + union_class_name + " value, JsonGenerator generator, SerializerProvider serializers) throws IOException {\n"
815
+ class_definition += class_definition_write
816
+ class_definition += f"{INDENT*3}throw new UnsupportedOperationException(\"No record type is set in the union\");\n"
817
+ class_definition += f"{INDENT*2}}}\n{INDENT}}}\n"
818
+ class_definition += f"\n{INDENT}public static class Deserializer extends JsonDeserializer<" + union_class_name + "> {\n"
819
+ class_definition += f"{INDENT*2}@Override\n"
820
+ class_definition += f"{INDENT*2}public " + union_class_name + " deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {\n"
821
+ class_definition += f"{INDENT*3}ObjectMapper mapper = (ObjectMapper) p.getCodec();\n"
822
+ class_definition += f"{INDENT*3}JsonNode node = mapper.readTree(p);\n"
823
+ class_definition += class_definition_read
824
+ class_definition += f"{INDENT*3}throw new UnsupportedOperationException(\"No record type matched the JSON data\");\n"
825
+ class_definition += f"{INDENT*2}}}\n{INDENT}}}\n"
826
+ class_definition += f"\n{INDENT*1}public static boolean isJsonMatch(JsonNode node) {{\n"
827
+ class_definition += f"{INDENT*2}return " + " || ".join(list_is_json_match) + ";\n"
828
+ class_definition += f"{INDENT*1}}}\n}}\n"
829
+
830
+ if write_file:
831
+ self.write_to_file(package, union_class_name, class_definition)
832
+ self.generated_types_avro_namespace[union_class_name] = "union" # Track union types
833
+ self.generated_types_java_package[union_class_name] = "union" # Track union types
834
+ return union_class_name
835
+
836
+
837
+ def generate_property(self, class_name: str, field: Dict, parent_package: str) -> str:
838
+ """ Generates a Java property definition """
839
+ field_name = pascal(field['name']) if self.pascal_properties else field['name']
840
+ field_type = self.convert_avro_type_to_java(class_name, field_name, field['type'], parent_package)
841
+ safe_field_name = self.safe_identifier(field_name, class_name)
842
+ property_def = ''
843
+ if 'doc' in field:
844
+ property_def += f"{INDENT}/** {field['doc']} */\n"
845
+ if self.jackson_annotations:
846
+ property_def += f"{INDENT}@JsonProperty(\"{field['name']}\")\n"
847
+ property_def += f"{INDENT}private {field_type.type_name} {safe_field_name};\n"
848
+ property_def += f"{INDENT}public {field_type.type_name} get{pascal(field_name)}() {{ return {safe_field_name}; }}\n"
849
+ property_def += f"{INDENT}public void set{pascal(field_name)}({field_type.type_name} {safe_field_name}) {{ this.{safe_field_name} = {safe_field_name}; }}\n"
850
+ if field_type.union_types:
851
+ for union_type in field_type.union_types:
852
+ if union_type.type_name.startswith("List<") or union_type.type_name.startswith("Map<"):
853
+ property_def += f"{INDENT}@SuppressWarnings(\"unchecked\")\n"
854
+ property_def += f"{INDENT}public {union_type.type_name} get{pascal(field_name)}As{flatten_type_name(union_type.type_name)}() {{ return ({union_type.type_name}){safe_field_name}; }}\n"
855
+ property_def += f"{INDENT}public void set{pascal(field_name)}As{flatten_type_name(union_type.type_name)}({union_type.type_name} {safe_field_name}) {{ this.{safe_field_name} = {safe_field_name}; }}\n"
856
+ return property_def
857
+
858
+ def write_to_file(self, package: str, name: str, definition: str):
859
+ """ Writes a Java class or enum to a file """
860
+ package = package.lower()
861
+ package = self.safe_package(package)
862
+ directory_path = os.path.join(
863
+ self.output_dir, package.replace('.', os.sep).replace('/', os.sep))
864
+ if not os.path.exists(directory_path):
865
+ os.makedirs(directory_path, exist_ok=True)
866
+ file_path = os.path.join(directory_path, f"{name}.java")
867
+
868
+ with open(file_path, 'w', encoding='utf-8') as file:
869
+ if package:
870
+ file.write(f"package {package.replace('/', '.')};\n\n")
871
+ if "List<" in definition:
872
+ file.write("import java.util.List;\n")
873
+ if "Map<" in definition:
874
+ file.write("import java.util.Map;\n")
875
+ if "Predicate<" in definition:
876
+ file.write("import java.util.function.Predicate;\n")
877
+ if "BigDecimal" in definition:
878
+ file.write("import java.math.BigDecimal;\n")
879
+ if "LocalDate" in definition:
880
+ file.write("import java.time.LocalDate;\n")
881
+ if "LocalTime" in definition:
882
+ file.write("import java.time.LocalTime;\n")
883
+ if "Instant" in definition:
884
+ file.write("import java.time.Instant;\n")
885
+ if "LocalDateTime" in definition:
886
+ file.write("import java.time.LocalDateTime;\n")
887
+ if "UUID" in definition:
888
+ file.write("import java.util.UUID;\n")
889
+ if "Duration" in definition:
890
+ file.write("import java.time.Duration;\n")
891
+
892
+ if self.avro_annotation:
893
+ if 'AvroRuntimeException' in definition:
894
+ file.write("import org.apache.avro.AvroRuntimeException;\n")
895
+ if 'Schema' in definition:
896
+ file.write("import org.apache.avro.Schema;\n")
897
+ if 'GenericData' in definition:
898
+ file.write("import org.apache.avro.generic.GenericData;\n")
899
+ if 'DatumReader' in definition:
900
+ file.write("import org.apache.avro.io.DatumReader;\n")
901
+ if 'DatumWriter' in definition:
902
+ file.write("import org.apache.avro.io.DatumWriter;\n")
903
+ if 'DecoderFactory' in definition:
904
+ file.write("import org.apache.avro.io.DecoderFactory;\n")
905
+ if 'EncoderFactory' in definition:
906
+ file.write("import org.apache.avro.io.EncoderFactory;\n")
907
+ if 'SpecificDatumReader' in definition:
908
+ file.write("import org.apache.avro.specific.SpecificDatumReader;\n")
909
+ if 'SpecificDatumWriter' in definition:
910
+ file.write("import org.apache.avro.specific.SpecificDatumWriter;\n")
911
+ if 'SpecificRecord' in definition:
912
+ file.write("import org.apache.avro.specific.SpecificRecord;\n")
913
+ if 'Encoder' in definition:
914
+ file.write("import org.apache.avro.io.Encoder;\n")
915
+ if self.jackson_annotations:
916
+ if 'JsonNode' in definition:
917
+ file.write("import com.fasterxml.jackson.databind.JsonNode;\n")
918
+ if 'ObjectMapper' in definition:
919
+ file.write("import com.fasterxml.jackson.databind.ObjectMapper;\n")
920
+ if 'JsonSerialize' in definition:
921
+ file.write("import com.fasterxml.jackson.databind.annotation.JsonSerialize;\n")
922
+ if 'JsonDeserialize' in definition:
923
+ file.write("import com.fasterxml.jackson.databind.annotation.JsonDeserialize;\n")
924
+ if 'JsonSerializer' in definition:
925
+ file.write("import com.fasterxml.jackson.databind.JsonSerializer;\n")
926
+ if 'SerializerProvider' in definition:
927
+ file.write("import com.fasterxml.jackson.databind.SerializerProvider;\n")
928
+ if 'JsonDeserializer' in definition:
929
+ file.write("import com.fasterxml.jackson.databind.JsonDeserializer;\n")
930
+ if 'DeserializationContext' in definition:
931
+ file.write("import com.fasterxml.jackson.databind.DeserializationContext;\n")
932
+ if 'JsonParser' in definition:
933
+ file.write("import com.fasterxml.jackson.core.JsonParser;\n")
934
+ if 'JsonIgnore' in definition:
935
+ file.write("import com.fasterxml.jackson.annotation.JsonIgnore;\n")
936
+ if 'JsonProperty' in definition:
937
+ file.write("import com.fasterxml.jackson.annotation.JsonProperty;\n")
938
+ if 'JsonProcessingException' in definition:
939
+ file.write("import com.fasterxml.jackson.core.JsonProcessingException;\n")
940
+ if 'JsonGenerator' in definition:
941
+ file.write("import com.fasterxml.jackson.core.JsonGenerator;\n")
942
+ if 'TypeReference' in definition:
943
+ file.write("import com.fasterxml.jackson.core.type.TypeReference;\n")
944
+ if self.avro_annotation or self.jackson_annotations:
945
+ if 'GZIPOutputStream' in definition:
946
+ file.write("import java.util.zip.GZIPOutputStream;\n")
947
+ if 'GZIPInputStream' in definition:
948
+ file.write("import java.util.zip.GZIPInputStream;\n")
949
+ if 'ByteArrayInputStream' in definition:
950
+ file.write("import java.io.ByteArrayInputStream;\n")
951
+ if "ByteArrayOutputStream" in definition:
952
+ file.write("import java.io.ByteArrayOutputStream;\n")
953
+ if "InputStream" in definition:
954
+ file.write("import java.io.InputStream;\n")
955
+ if "IOException" in definition:
956
+ file.write("import java.io.IOException;\n")
957
+ if "InflaterInputStream" in definition:
958
+ file.write("import java.util.zip.InflaterInputStream;\n")
959
+ file.write("\n")
960
+ file.write(definition)
961
+
962
+ def convert_schema(self, schema: JsonNode, output_dir: str):
963
+ """Converts Avro schema to Java"""
964
+ if not isinstance(schema, list):
965
+ schema = [schema]
966
+ if not os.path.exists(output_dir):
967
+ os.makedirs(output_dir, exist_ok=True)
968
+ pom_path = os.path.join(output_dir, "pom.xml")
969
+ if not os.path.exists(pom_path):
970
+ package_elements = self.base_package.split('.') if self.base_package else ["com", "example"]
971
+ groupid = '.'.join(package_elements[:-1]) if len(package_elements) > 1 else package_elements[0]
972
+ artifactid = package_elements[-1]
973
+ with open(pom_path, 'w', encoding='utf-8') as file:
974
+ file.write(POM_CONTENT.format(groupid=groupid, artifactid=artifactid, AVRO_VERSION=AVRO_VERSION, JACKSON_VERSION=JACKSON_VERSION, JDK_VERSION=JDK_VERSION, PACKAGE=self.base_package))
975
+ output_dir = os.path.join(
976
+ output_dir, "src/main/java".replace('/', os.sep))
977
+ if not os.path.exists(output_dir):
978
+ os.makedirs(output_dir, exist_ok=True)
979
+ self.output_dir = output_dir
980
+ for avro_schema in (x for x in schema if isinstance(x, dict)):
981
+ self.generate_class_or_enum(avro_schema, '')
982
+
983
+ def convert(self, avro_schema_path: str, output_dir: str):
984
+ """Converts Avro schema to Java"""
985
+ with open(avro_schema_path, 'r', encoding='utf-8') as file:
986
+ schema = json.load(file)
987
+ self.convert_schema(schema, output_dir)
988
+
989
+
990
+ def convert_avro_to_java(avro_schema_path, java_file_path, package_name='', pascal_properties=False, jackson_annotation=False, avro_annotation=False):
991
+ """_summary_
992
+
993
+ Converts Avro schema to C# classes
994
+
995
+ Args:
996
+ avro_schema_path (_type_): Avro input schema path
997
+ cs_file_path (_type_): Output C# file path
998
+ """
999
+ if not package_name:
1000
+ package_name = os.path.splitext(os.path.basename(java_file_path))[0].replace('-', '_').lower()
1001
+ avrotojava = AvroToJava()
1002
+ avrotojava.base_package = package_name
1003
+ avrotojava.pascal_properties = pascal_properties
1004
+ avrotojava.avro_annotation = avro_annotation
1005
+ avrotojava.jackson_annotations = jackson_annotation
1006
+ avrotojava.convert(avro_schema_path, java_file_path)
1007
+
1008
+
1009
+ def convert_avro_schema_to_java(avro_schema: JsonNode, output_dir: str, package_name='', pascal_properties=False, jackson_annotation=False, avro_annotation=False):
1010
+ """_summary_
1011
+
1012
+ Converts Avro schema to C# classes
1013
+
1014
+ Args:
1015
+ avro_schema (_type_): Avro schema as a dictionary or list of dictionaries
1016
+ output_dir (_type_): Output directory path
1017
+ """
1018
+ avrotojava = AvroToJava()
1019
+ avrotojava.base_package = package_name
1020
+ avrotojava.pascal_properties = pascal_properties
1021
+ avrotojava.avro_annotation = avro_annotation
1022
+ avrotojava.jackson_annotations = jackson_annotation
1023
+ avrotojava.convert_schema(avro_schema, output_dir)