structurize 2.17.0__py3-none-any.whl → 2.18.1__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.
avrotize/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '2.17.0'
32
- __version_tuple__ = version_tuple = (2, 17, 0)
31
+ __version__ = version = '2.18.1'
32
+ __version_tuple__ = version_tuple = (2, 18, 1)
33
33
 
34
- __commit_id__ = commit_id = 'gfbce894a4'
34
+ __commit_id__ = commit_id = 'gdae473138'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structurize
3
- Version: 2.17.0
3
+ Version: 2.18.1
4
4
  Summary: Tools to convert from and to JSON Structure from various other schema languages.
5
5
  Author-email: Clemens Vasters <clemensv@microsoft.com>
6
6
  Classifier: Programming Language :: Python :: 3
@@ -1,6 +1,6 @@
1
1
  avrotize/__init__.py,sha256=JjPSX7c686TV00J_x0Py9JwXS0aJl8vpLn81Y0ondkw,3606
2
2
  avrotize/__main__.py,sha256=5pY8dYAURcOnFRvgb6fgaOIa_SOzPLIWbU8-ZTQ0jG4,88
3
- avrotize/_version.py,sha256=rxOyQGpFc2ZRj-yKs4vV_8H_TMvi5KkUHofU5OzQOp0,714
3
+ avrotize/_version.py,sha256=tMC0K6V8_fTSI319xo2OQmmHkpAHWOafG-61857ehCk,714
4
4
  avrotize/asn1toavro.py,sha256=QDNwfBfXMxSH-k487CA3CaGCGDzOLs4PpVbbENm5uF0,8386
5
5
  avrotize/avrotize.py,sha256=VHFpBltMVBpyt0ju3ZWW725BKjQ4Fk-nrAy8udW-X44,5713
6
6
  avrotize/avrotocpp.py,sha256=hRZV247_TDD7Sm6_8sFx-UH5SueLLx2Wg6TvAVUX0iE,25693
@@ -47,7 +47,6 @@ avrotize/structuretodb.py,sha256=3QE_TCdNklGH5ymzGsEnX1sI4OhvX2AYKPH7xtR5tHk,439
47
47
  avrotize/structuretogo.py,sha256=VCEUz-5J8uRqX1hWaTimtfVzEsIB-gs4wxa308rYD0s,32470
48
48
  avrotize/structuretographql.py,sha256=wcGXnrup5v5saRa1BhR6o-X8q8ujsQMVqrFHQTBPjww,20468
49
49
  avrotize/structuretoiceberg.py,sha256=itKb33Kj-7-udk4eHTLmTEasIeh1ggpZ3e_bwCxLABM,15344
50
- avrotize/structuretojava.py,sha256=jG2Vcf1KdezWrZo5lsecxLnmnMw1rA96uOxVWJQ4Rso,43372
51
50
  avrotize/structuretojsons.py,sha256=PJrQBaf6yQHu5eFkePxbjPBEmL-fYfX2wj6OmH1jsWw,22495
52
51
  avrotize/structuretokusto.py,sha256=rOKgYIcm7ZK8RS-VvMFPNzPzwtv7c4dIKU-fKjrJLyM,30618
53
52
  avrotize/structuretomd.py,sha256=exfCldYbieVdduhotSoLrxsbphmyJQyeQso9qv4qyUw,13642
@@ -57,9 +56,9 @@ avrotize/structuretorust.py,sha256=ChRmO7uzU-pMdDdS0Vtg-MVUaOaNhNUPwH-ZKKOHglU,3
57
56
  avrotize/structuretots.py,sha256=PLV6W8k-yd7xkspUaQ-Vj90F26PTkB5HO0OkPJolkJ0,30800
58
57
  avrotize/structuretoxsd.py,sha256=01VpasyWSMOx04sILHLP7H-WkhGdXAEGKohUUfgrNf0,32797
59
58
  avrotize/xsdtoavro.py,sha256=nQtNH_3pEZBp67oUCPqzhvItEExHTe-8obsIfNRXt8Y,19064
60
- structurize-2.17.0.dist-info/licenses/LICENSE,sha256=xGtQGygTETTtDQJafZCUbpsed3GxO6grmqig-jGEuSk,11348
61
- structurize-2.17.0.dist-info/METADATA,sha256=YV0r7HWtiNW0IbudLO2LJWG2CLQjFxB5Q5Si6iOqLec,3670
62
- structurize-2.17.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
63
- structurize-2.17.0.dist-info/entry_points.txt,sha256=biIH7jA5auhVqfbwHVk2gmD_gvrPYKgjpCAn0JWZ-Rs,55
64
- structurize-2.17.0.dist-info/top_level.txt,sha256=yn-yQ0Cm1O9fbF8KJgv4IIvX4YRGelKgPqZF1wS5P50,9
65
- structurize-2.17.0.dist-info/RECORD,,
59
+ structurize-2.18.1.dist-info/licenses/LICENSE,sha256=xGtQGygTETTtDQJafZCUbpsed3GxO6grmqig-jGEuSk,11348
60
+ structurize-2.18.1.dist-info/METADATA,sha256=xN6pWXUXX1yXFT4kOEPm42CktTuLNrVPl8LUiWU_Sg8,3670
61
+ structurize-2.18.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
62
+ structurize-2.18.1.dist-info/entry_points.txt,sha256=biIH7jA5auhVqfbwHVk2gmD_gvrPYKgjpCAn0JWZ-Rs,55
63
+ structurize-2.18.1.dist-info/top_level.txt,sha256=yn-yQ0Cm1O9fbF8KJgv4IIvX4YRGelKgPqZF1wS5P50,9
64
+ structurize-2.18.1.dist-info/RECORD,,
@@ -1,853 +0,0 @@
1
- # pylint: disable=too-many-arguments, too-many-locals, too-many-branches, too-many-statements, line-too-long
2
-
3
- """ Generates Java classes from JSON Structure schema """
4
- import json
5
- import os
6
- from typing import Dict, List, Tuple, Union, Set, Optional, Any
7
- from avrotize.constants import JACKSON_VERSION
8
-
9
- from avrotize.common import pascal, camel, process_template
10
-
11
- INDENT = ' '
12
-
13
- JsonNode = Dict[str, 'JsonNode'] | List['JsonNode'] | str | None
14
-
15
-
16
- def flatten_type_name(name: str) -> str:
17
- """Strips the namespace from a name"""
18
- if name.endswith('[]'):
19
- return flatten_type_name(name[:-2]+'Array')
20
- base_name = pascal(name.replace(' ', '').split('.')[-1].replace('>', '').replace('<', '').replace(',', ''))
21
- return base_name
22
-
23
-
24
- def is_java_reserved_word(word: str) -> bool:
25
- """Checks if a word is a Java reserved word"""
26
- reserved_words = [
27
- 'abstract', 'assert', 'boolean', 'break', 'byte', 'case', 'catch', 'char', 'class', 'const',
28
- 'continue', 'default', 'do', 'double', 'else', 'enum', 'extends', 'final', 'finally', 'float',
29
- 'for', 'goto', 'if', 'implements', 'import', 'instanceof', 'int', 'interface', 'long', 'native',
30
- 'new', 'package', 'private', 'protected', 'public', 'return', 'short', 'static', 'strictfp',
31
- 'super', 'switch', 'synchronized', 'this', 'throw', 'throws', 'transient', 'try', 'void', 'volatile',
32
- 'while', 'true', 'false', 'null', 'record',
33
- ]
34
- return word in reserved_words
35
-
36
-
37
- class StructureToJava:
38
- """Converts JSON Structure schema to Java classes, including Jackson annotations"""
39
-
40
- def __init__(self, base_package: str = '') -> None:
41
- self.base_package = base_package.replace('.', '/')
42
- self.output_dir = os.getcwd()
43
- self.jackson_annotations = True # Always use Jackson annotations for JSON Structure
44
- self.pascal_properties = False
45
- self.generated_types_structure_namespace: Dict[str,str] = {}
46
- self.generated_types_java_package: Dict[str,str] = {}
47
- self.schema_doc: JsonNode = None
48
- self.schema_registry: Dict[str, Dict] = {}
49
-
50
- def qualified_name(self, package: str, name: str) -> str:
51
- """Concatenates package and name using a dot separator"""
52
- slash_package_name = package.replace('.', '/')
53
- safe_package_slash = self.safe_package(slash_package_name.lower())
54
- safe_package = safe_package_slash.replace('/', '.')
55
- return f"{safe_package}.{name}" if package else name
56
-
57
- def join_packages(self, parent_package: str, package: str) -> str:
58
- """Joins package and name using a dot separator"""
59
- if parent_package and package:
60
- return f"{parent_package}.{package}".lower()
61
- elif parent_package:
62
- return parent_package.lower()
63
- return package.lower()
64
-
65
- class JavaType:
66
- """Java type definition"""
67
-
68
- def __init__(self, type_name: str, union_types: List['StructureToJava.JavaType'] | None = None, is_class: bool = False, is_enum: bool = False) -> None:
69
- self.type_name = type_name
70
- self.union_types = union_types
71
- self.is_class = is_class
72
- self.is_enum = is_enum
73
-
74
- def safe_identifier(self, name: str, class_name: str = '') -> str:
75
- """Converts a name to a safe Java identifier"""
76
- if is_java_reserved_word(name):
77
- return f"_{name}"
78
- if class_name and name == class_name:
79
- return f"{name}_"
80
- return name
81
-
82
- def safe_package(self, packageName: str) -> str:
83
- """Converts a name to a safe Java identifier by checking each path segment"""
84
- segments = packageName.split('/')
85
- safe_segments = [
86
- self.safe_identifier(segment)
87
- for segment in segments
88
- ]
89
-
90
- return '/'.join(safe_segments)
91
-
92
- def map_primitive_to_java(self, structure_type: str, is_optional: bool) -> JavaType:
93
- """Maps JSON Structure primitive types to Java types"""
94
- optional_mapping = {
95
- 'null': 'Void',
96
- 'boolean': 'Boolean',
97
- 'string': 'String',
98
- 'integer': 'Integer',
99
- 'number': 'Double',
100
- 'int8': 'Byte',
101
- 'uint8': 'Short', # Java doesn't have unsigned byte, use Short
102
- 'int16': 'Short',
103
- 'uint16': 'Integer', # Java doesn't have unsigned short, use Integer
104
- 'int32': 'Integer',
105
- 'uint32': 'Long', # Java doesn't have unsigned int, use Long
106
- 'int64': 'Long',
107
- 'uint64': 'BigInteger', # Use BigInteger for uint64 to handle full range in JSON
108
- 'int128': 'BigInteger',
109
- 'uint128': 'BigInteger',
110
- 'float8': 'Float',
111
- 'float': 'Float',
112
- 'double': 'Double',
113
- 'binary32': 'Float',
114
- 'binary64': 'Double',
115
- 'decimal': 'BigDecimal',
116
- 'binary': 'byte[]',
117
- 'date': 'LocalDate',
118
- 'time': 'LocalTime',
119
- 'datetime': 'Instant',
120
- 'timestamp': 'Instant',
121
- 'duration': 'Duration',
122
- 'uuid': 'UUID',
123
- 'uri': 'URI',
124
- 'jsonpointer': 'String',
125
- 'any': 'Object'
126
- }
127
- required_mapping = {
128
- 'null': 'void',
129
- 'boolean': 'boolean',
130
- 'string': 'String',
131
- 'integer': 'int',
132
- 'number': 'double',
133
- 'int8': 'byte',
134
- 'uint8': 'short',
135
- 'int16': 'short',
136
- 'uint16': 'int',
137
- 'int32': 'int',
138
- 'uint32': 'long',
139
- 'int64': 'long',
140
- 'uint64': 'BigInteger', # Use BigInteger for uint64 to handle full range in JSON
141
- 'int128': 'BigInteger',
142
- 'uint128': 'BigInteger',
143
- 'float8': 'float',
144
- 'float': 'float',
145
- 'double': 'double',
146
- 'binary32': 'float',
147
- 'binary64': 'double',
148
- 'decimal': 'BigDecimal',
149
- 'binary': 'byte[]',
150
- 'date': 'LocalDate',
151
- 'time': 'LocalTime',
152
- 'datetime': 'Instant',
153
- 'timestamp': 'Instant',
154
- 'duration': 'Duration',
155
- 'uuid': 'UUID',
156
- 'uri': 'URI',
157
- 'jsonpointer': 'String',
158
- 'any': 'Object'
159
- }
160
- if '.' in structure_type:
161
- type_name = structure_type.split('.')[-1]
162
- package_name = '.'.join(structure_type.split('.')[:-1]).lower()
163
- structure_type = self.qualified_name(package_name, type_name)
164
- if structure_type in self.generated_types_structure_namespace:
165
- kind = self.generated_types_structure_namespace[structure_type]
166
- qualified_class_name = self.qualified_name(self.base_package, structure_type)
167
- return StructureToJava.JavaType(qualified_class_name, is_class=kind=="class", is_enum=kind=="enum")
168
- else:
169
- return StructureToJava.JavaType(required_mapping.get(structure_type, structure_type) if not is_optional else optional_mapping.get(structure_type, structure_type))
170
-
171
- def is_java_primitive(self, java_type: JavaType) -> bool:
172
- """Checks if a Java type is a primitive type"""
173
- return java_type.type_name in [
174
- 'void', 'boolean', 'int', 'long', 'float', 'double', 'byte', 'short', 'byte[]', 'String',
175
- 'Boolean', 'Integer', 'Long', 'Float', 'Double', 'Short', 'Byte', 'Void']
176
-
177
- def is_java_optional_type(self, java_type: JavaType) -> bool:
178
- """Checks if a Java type is an optional type"""
179
- return java_type.type_name in ['Void', 'Boolean', 'Integer', 'Long', 'Float', 'Double', 'Short', 'Byte']
180
-
181
- def is_java_numeric_type(self, java_type: JavaType) -> bool:
182
- """Checks if a Java type is a numeric type"""
183
- return java_type.type_name in ['int', 'long', 'float', 'double', 'short', 'byte', 'Integer', 'Long', 'Float', 'Double', 'Short', 'Byte', 'BigInteger', 'BigDecimal']
184
-
185
- def is_java_integer_type(self, java_type: JavaType) -> bool:
186
- """Checks if a Java type is an integer type"""
187
- return java_type.type_name in ['int', 'long', 'short', 'byte', 'Integer', 'Long', 'Short', 'Byte', 'BigInteger']
188
-
189
- def resolve_ref(self, ref: str, context_schema: Optional[Dict] = None) -> Optional[Dict]:
190
- """ Resolves a $ref to the actual schema definition """
191
- # Check if it's an absolute URI reference (schema with $id)
192
- if not ref.startswith('#/'):
193
- # Try to resolve from schema registry
194
- if ref in self.schema_registry:
195
- return self.schema_registry[ref]
196
- return None
197
-
198
- # Handle fragment-only references (internal to document)
199
- path = ref[2:].split('/')
200
- schema = context_schema if context_schema else self.schema_doc
201
-
202
- for part in path:
203
- if not isinstance(schema, dict) or part not in schema:
204
- return None
205
- schema = schema[part]
206
-
207
- return schema
208
-
209
- def register_schema_ids(self, schema: Dict, base_uri: str = '') -> None:
210
- """ Recursively registers schemas with $id keywords """
211
- if not isinstance(schema, dict):
212
- return
213
-
214
- # Register this schema if it has an $id
215
- if '$id' in schema:
216
- schema_id = schema['$id']
217
- # Handle relative URIs
218
- if base_uri and not schema_id.startswith(('http://', 'https://', 'urn:')):
219
- from urllib.parse import urljoin
220
- schema_id = urljoin(base_uri, schema_id)
221
- self.schema_registry[schema_id] = schema
222
- base_uri = schema_id # Update base URI for nested schemas
223
-
224
- # Recursively process definitions
225
- if 'definitions' in schema:
226
- for def_name, def_schema in schema['definitions'].items():
227
- if isinstance(def_schema, dict):
228
- self.register_schema_ids(def_schema, base_uri)
229
-
230
- # Recursively process properties
231
- if 'properties' in schema:
232
- for prop_name, prop_schema in schema['properties'].items():
233
- if isinstance(prop_schema, dict):
234
- self.register_schema_ids(prop_schema, base_uri)
235
-
236
- # Recursively process items, values, etc.
237
- for key in ['items', 'values', 'additionalProperties']:
238
- if key in schema and isinstance(schema[key], dict):
239
- self.register_schema_ids(schema[key], base_uri)
240
-
241
- def convert_structure_type_to_java(self, class_name: str, field_name: str, structure_type: Union[str, Dict, List], parent_package: str, nullable: bool = False) -> JavaType:
242
- """Converts JSON Structure type to Java type"""
243
- if isinstance(structure_type, str):
244
- return self.map_primitive_to_java(structure_type, nullable)
245
- elif isinstance(structure_type, list):
246
- # Handle type unions
247
- non_null_types = [t for t in structure_type if t != 'null']
248
- if len(non_null_types) == 1:
249
- if isinstance(non_null_types[0], str):
250
- return self.map_primitive_to_java(non_null_types[0], True)
251
- else:
252
- return self.convert_structure_type_to_java(class_name, field_name, non_null_types[0], parent_package)
253
- else:
254
- # Multiple non-null types - generate union class
255
- if self.jackson_annotations:
256
- return StructureToJava.JavaType(self.generate_embedded_union_class_jackson(class_name, field_name, non_null_types, parent_package, write_file=True), is_class=True)
257
- else:
258
- types: List[StructureToJava.JavaType] = [self.convert_structure_type_to_java(
259
- class_name, field_name, t, parent_package) for t in non_null_types]
260
- return StructureToJava.JavaType('Object', types)
261
- elif isinstance(structure_type, dict):
262
- # Handle $ref
263
- if '$ref' in structure_type:
264
- ref_schema = self.resolve_ref(structure_type['$ref'], self.schema_doc if isinstance(self.schema_doc, dict) else None)
265
- if ref_schema:
266
- ref_path = structure_type['$ref'].split('/')
267
- type_name = ref_path[-1]
268
- ref_namespace = '.'.join(ref_path[2:-1]) if len(ref_path) > 3 else parent_package
269
- return self.generate_class_or_enum(ref_schema, ref_namespace, write_file=True, explicit_name=type_name)
270
- return StructureToJava.JavaType('Object')
271
-
272
- # Handle enum keyword
273
- if 'enum' in structure_type:
274
- return self.generate_enum(structure_type, field_name, parent_package, write_file=True)
275
-
276
- # Handle type keyword
277
- if 'type' not in structure_type:
278
- return StructureToJava.JavaType('Object')
279
-
280
- struct_type = structure_type['type']
281
-
282
- # Handle complex types
283
- if struct_type == 'object':
284
- return self.generate_class(structure_type, parent_package, write_file=True)
285
- elif struct_type == 'array':
286
- item_type = self.convert_structure_type_to_java(class_name, field_name, structure_type.get('items', {'type': 'any'}), parent_package, nullable=True).type_name
287
- return StructureToJava.JavaType(f"List<{item_type}>")
288
- elif struct_type == 'set':
289
- item_type = self.convert_structure_type_to_java(class_name, field_name, structure_type.get('items', {'type': 'any'}), parent_package, nullable=True).type_name
290
- return StructureToJava.JavaType(f"Set<{item_type}>")
291
- elif struct_type == 'map':
292
- values_type = self.convert_structure_type_to_java(class_name, field_name, structure_type.get('values', {'type': 'any'}), parent_package, nullable=True).type_name
293
- return StructureToJava.JavaType(f"Map<String,{values_type}>")
294
- elif struct_type == 'choice':
295
- return self.generate_choice(structure_type, parent_package, write_file=True)
296
- elif struct_type == 'tuple':
297
- return self.generate_tuple(structure_type, parent_package, write_file=True)
298
- else:
299
- return self.convert_structure_type_to_java(class_name, field_name, struct_type, parent_package)
300
- return StructureToJava.JavaType('Object')
301
-
302
- def generate_class_or_enum(self, structure_schema: Dict, parent_package: str, write_file: bool = True, explicit_name: str = '') -> JavaType:
303
- """ Generates a Java class or enum from a JSON Structure schema """
304
- struct_type = structure_schema.get('type', 'object')
305
- if 'enum' in structure_schema:
306
- return self.generate_enum(structure_schema, explicit_name or structure_schema.get('name', 'UnnamedEnum'), parent_package, write_file)
307
- elif struct_type == 'object':
308
- return self.generate_class(structure_schema, parent_package, write_file, explicit_name=explicit_name)
309
- elif struct_type == 'choice':
310
- return self.generate_choice(structure_schema, parent_package, write_file, explicit_name=explicit_name)
311
- elif struct_type == 'tuple':
312
- return self.generate_tuple(structure_schema, parent_package, write_file, explicit_name=explicit_name)
313
- return StructureToJava.JavaType('Object')
314
-
315
- def generate_class(self, structure_schema: Dict, parent_package: str, write_file: bool, explicit_name: str = '') -> JavaType:
316
- """ Generates a Java class from a JSON Structure object schema """
317
-
318
- # Get name and namespace
319
- class_name = pascal(explicit_name if explicit_name else structure_schema.get('name', 'UnnamedClass'))
320
- schema_namespace = structure_schema.get('namespace', parent_package)
321
- if not 'namespace' in structure_schema:
322
- structure_schema['namespace'] = schema_namespace
323
- package = self.join_packages(self.base_package, schema_namespace).replace('.', '/').lower()
324
- package = package.replace('.', '/').lower()
325
- package = self.safe_package(package)
326
- class_name = self.safe_identifier(class_name)
327
- namespace_qualified_name = self.qualified_name(schema_namespace, explicit_name or structure_schema.get('name', 'UnnamedClass'))
328
- qualified_class_name = self.qualified_name(package.replace('/', '.'), class_name)
329
- if namespace_qualified_name in self.generated_types_structure_namespace:
330
- return StructureToJava.JavaType(qualified_class_name, is_class=True)
331
- self.generated_types_structure_namespace[namespace_qualified_name] = "class"
332
- self.generated_types_java_package[qualified_class_name] = "class"
333
-
334
- # Check if this is an abstract type
335
- is_abstract = structure_schema.get('abstract', False)
336
- deprecated = structure_schema.get('deprecated', False)
337
-
338
- # Generate documentation
339
- doc = structure_schema.get('description', structure_schema.get('doc', class_name))
340
-
341
- # Handle inheritance
342
- base_class = None
343
- if '$extends' in structure_schema:
344
- base_ref = structure_schema['$extends']
345
- base_schema = self.resolve_ref(base_ref, self.schema_doc if isinstance(self.schema_doc, dict) else None)
346
- if base_schema:
347
- ref_path = base_ref.split('/')
348
- base_name = ref_path[-1]
349
- ref_namespace = '.'.join(ref_path[2:-1]) if len(ref_path) > 3 else parent_package
350
- base_type = self.generate_class(base_schema, ref_namespace, write_file=True, explicit_name=base_name)
351
- base_class = base_type.type_name
352
-
353
- # Generate field information for template
354
- fields = []
355
- for prop_name, prop_schema in structure_schema.get('properties', {}).items():
356
- field_info = self.generate_field_info(class_name, prop_name, prop_schema, schema_namespace)
357
- fields.append(field_info)
358
-
359
- # Use template for class generation
360
- class_definition = process_template(
361
- "structuretojava/class_core.jinja",
362
- class_name=class_name,
363
- docstring=doc,
364
- is_abstract=is_abstract,
365
- deprecated=deprecated,
366
- base_class=base_class,
367
- fields=fields,
368
- jackson_annotation=self.jackson_annotations
369
- )
370
-
371
- # Generate Equals and GetHashCode using template
372
- properties = structure_schema.get('properties', {})
373
- non_const_properties = {k: v for k, v in properties.items() if 'const' not in v}
374
- field_names = []
375
- for prop_name in non_const_properties.keys():
376
- field_name_java = pascal(prop_name) if self.pascal_properties else prop_name
377
- safe_field_name = self.safe_identifier(field_name_java, class_name)
378
- field_names.append(safe_field_name)
379
-
380
- equals_hashcode = process_template(
381
- "structuretojava/equals_hashcode.jinja",
382
- class_name=class_name,
383
- fields=field_names,
384
- field_count=len(field_names)
385
- )
386
-
387
- class_definition = class_definition.rstrip() + equals_hashcode + "\n}\n"
388
-
389
- if write_file:
390
- self.write_to_file(package, class_name, class_definition)
391
- return StructureToJava.JavaType(qualified_class_name, is_class=True)
392
-
393
- def generate_field_info(self, class_name: str, prop_name: str, prop_schema: Dict, parent_package: str) -> Dict:
394
- """ Generates field information for template """
395
- field_name_java = pascal(prop_name) if self.pascal_properties else prop_name
396
- field_type = self.convert_structure_type_to_java(class_name, prop_name, prop_schema, parent_package)
397
- safe_field_name = self.safe_identifier(field_name_java, class_name)
398
-
399
- # Generate documentation
400
- doc = prop_schema.get('description', prop_schema.get('doc', field_name_java))
401
-
402
- # Check if this is a const field
403
- is_const = 'const' in prop_schema
404
- const_value = None
405
- if is_const:
406
- const_val = prop_schema['const']
407
- prop_type = field_type.type_name
408
- if prop_type.endswith('?'):
409
- prop_type = prop_type[:-1]
410
- const_value = self.format_const_value(const_val, prop_type)
411
-
412
- return {
413
- 'name': safe_field_name,
414
- 'original_name': prop_name,
415
- 'type': field_type.type_name,
416
- 'docstring': doc,
417
- 'is_const': is_const,
418
- 'const_value': const_value
419
- }
420
-
421
- def generate_property(self, class_name: str, field: Tuple[str, Dict], parent_package: str) -> str:
422
- """ Generates a Java property definition (legacy method for compatibility) """
423
- field_name, field_schema = field
424
- field_info = self.generate_field_info(class_name, field_name, field_schema, parent_package)
425
-
426
- property_def = f"{INDENT}/** {field_info['docstring']} */\n"
427
-
428
- if self.jackson_annotations and field_info['original_name'] != field_info['name']:
429
- property_def += f'{INDENT}@JsonProperty("{field_info["original_name"]}")\n'
430
-
431
- if field_info['is_const']:
432
- property_def += f"{INDENT}public static final {field_info['type']} {field_info['name']} = {field_info['const_value']};\n"
433
- else:
434
- property_def += f"{INDENT}private {field_info['type']} {field_info['name']};\n"
435
- property_def += f"{INDENT}public {field_info['type']} get{pascal(field_info['name'])}() {{ return {field_info['name']}; }}\n"
436
- property_def += f"{INDENT}public void set{pascal(field_info['name'])}({field_info['type']} {field_info['name']}) {{ this.{field_info['name']} = {field_info['name']}; }}\n"
437
-
438
- return property_def
439
-
440
- def format_const_value(self, value: Any, java_type: str) -> str:
441
- """ Formats a constant value for Java """
442
- if value is None:
443
- return "null"
444
- elif isinstance(value, bool):
445
- return "true" if value else "false"
446
- elif isinstance(value, str):
447
- return f'"{value}"'
448
- elif isinstance(value, (int, float)):
449
- if java_type in ['float', 'Float']:
450
- return f"{value}f"
451
- elif java_type in ['long', 'Long']:
452
- return f"{value}L"
453
- elif java_type in ['double', 'Double']:
454
- return f"{value}d"
455
- return str(value)
456
- return f"/* unsupported const value */"
457
-
458
- def generate_enum(self, structure_schema: Dict, field_name: str, parent_package: str, write_file: bool) -> JavaType:
459
- """ Generates a Java enum from JSON Structure enum schema """
460
-
461
- # Determine enum name
462
- enum_name = pascal(structure_schema.get('name', field_name + 'Enum'))
463
- schema_namespace = structure_schema.get('namespace', parent_package)
464
- package = self.join_packages(self.base_package, schema_namespace).replace('.', '/').lower()
465
- enum_name = self.safe_identifier(enum_name)
466
- type_name = self.qualified_name(package.replace('/', '.'), enum_name)
467
- namespace_qualified_name = self.qualified_name(schema_namespace, structure_schema.get('name', field_name + 'Enum'))
468
- self.generated_types_structure_namespace[namespace_qualified_name] = "enum"
469
- self.generated_types_java_package[type_name] = "enum"
470
-
471
- # Get enum values
472
- symbols = structure_schema.get('enum', [])
473
- if not symbols:
474
- return StructureToJava.JavaType('Object')
475
-
476
- # Generate documentation
477
- doc = structure_schema.get('description', structure_schema.get('doc', enum_name))
478
- deprecated = structure_schema.get('deprecated', False)
479
-
480
- # Determine base type
481
- base_type = structure_schema.get('type', 'string')
482
- numeric_types = ['int8', 'uint8', 'int16', 'uint16', 'int32', 'uint32', 'int64', 'uint64']
483
- is_numeric = base_type in numeric_types
484
-
485
- if is_numeric:
486
- java_base_type = self.map_primitive_to_java(base_type, False).type_name
487
- symbol_list = [{'name': f"VALUE_{value}".upper(), 'value': value} for value in symbols]
488
- enum_definition = process_template(
489
- "structuretojava/enum_core.jinja",
490
- class_name=enum_name,
491
- docstring=doc,
492
- deprecated=deprecated,
493
- is_numeric=True,
494
- numeric_type=java_base_type,
495
- symbols=symbol_list
496
- )
497
- else:
498
- # String enum
499
- safe_symbols = [self.safe_identifier(pascal(str(symbol).replace('-', '_').replace(' ', '_'))) for symbol in symbols]
500
- enum_definition = process_template(
501
- "structuretojava/enum_core.jinja",
502
- class_name=enum_name,
503
- docstring=doc,
504
- deprecated=deprecated,
505
- is_numeric=False,
506
- symbols=safe_symbols
507
- )
508
-
509
- if write_file:
510
- self.write_to_file(package, enum_name, enum_definition)
511
- return StructureToJava.JavaType(type_name, is_enum=True)
512
-
513
- def generate_choice(self, structure_schema: Dict, parent_package: str, write_file: bool, explicit_name: str = '') -> JavaType:
514
- """ Generates a choice (discriminated union) type """
515
- class_name = pascal(explicit_name if explicit_name else structure_schema.get('name', 'UnnamedChoice'))
516
- schema_namespace = structure_schema.get('namespace', parent_package)
517
- package = self.join_packages(self.base_package, schema_namespace).replace('.', '/').lower()
518
- package = self.safe_package(package)
519
- class_name = self.safe_identifier(class_name)
520
- namespace_qualified_name = self.qualified_name(schema_namespace, explicit_name or structure_schema.get('name', 'UnnamedChoice'))
521
- qualified_class_name = self.qualified_name(package.replace('/', '.'), class_name)
522
-
523
- if namespace_qualified_name in self.generated_types_structure_namespace:
524
- return StructureToJava.JavaType(qualified_class_name, is_class=True)
525
-
526
- self.generated_types_structure_namespace[namespace_qualified_name] = "class"
527
- self.generated_types_java_package[qualified_class_name] = "class"
528
-
529
- # Get choice definitions
530
- choices_dict = structure_schema.get('choices', {})
531
- doc = structure_schema.get('description', structure_schema.get('doc', class_name))
532
- deprecated = structure_schema.get('deprecated', False)
533
-
534
- # Build choice information for template
535
- choices = []
536
- for choice_name, choice_schema in choices_dict.items():
537
- choice_type_name = pascal(choice_name)
538
- value_type = self.convert_structure_type_to_java(class_name, choice_name, choice_schema, schema_namespace)
539
- choices.append({
540
- 'name': choice_name,
541
- 'type': choice_type_name,
542
- 'value_type': value_type.type_name,
543
- 'docstring': choice_schema.get('description', choice_schema.get('doc', f'{choice_name} variant'))
544
- })
545
-
546
- # Use template for choice generation
547
- choice_definition = process_template(
548
- "structuretojava/choice_core.jinja",
549
- class_name=class_name,
550
- docstring=doc,
551
- deprecated=deprecated,
552
- choices=choices,
553
- jackson_annotation=self.jackson_annotations
554
- )
555
-
556
- if write_file:
557
- self.write_to_file(package, class_name, choice_definition)
558
-
559
- return StructureToJava.JavaType(qualified_class_name, is_class=True)
560
-
561
- def generate_tuple(self, structure_schema: Dict, parent_package: str, write_file: bool, explicit_name: str = '') -> JavaType:
562
- """ Generates a tuple type - Per JSON Structure spec, tuples serialize as JSON arrays """
563
- class_name = pascal(explicit_name if explicit_name else structure_schema.get('name', 'UnnamedTuple'))
564
- schema_namespace = structure_schema.get('namespace', parent_package)
565
- package = self.join_packages(self.base_package, schema_namespace).replace('.', '/').lower()
566
- package = self.safe_package(package)
567
- class_name = self.safe_identifier(class_name)
568
- namespace_qualified_name = self.qualified_name(schema_namespace, explicit_name or structure_schema.get('name', 'UnnamedTuple'))
569
- qualified_class_name = self.qualified_name(package.replace('/', '.'), class_name)
570
-
571
- if namespace_qualified_name in self.generated_types_structure_namespace:
572
- return StructureToJava.JavaType(qualified_class_name, is_class=True)
573
-
574
- self.generated_types_structure_namespace[namespace_qualified_name] = "class"
575
- self.generated_types_java_package[qualified_class_name] = "class"
576
-
577
- # Get tuple order and properties
578
- properties = structure_schema.get('properties', {})
579
- tuple_order = structure_schema.get('tuple', [])
580
- doc = structure_schema.get('description', structure_schema.get('doc', class_name))
581
- deprecated = structure_schema.get('deprecated', False)
582
-
583
- # Build tuple element information in correct order
584
- elements = []
585
- for prop_name in tuple_order:
586
- if prop_name in properties:
587
- prop_schema = properties[prop_name]
588
- prop_type = self.convert_structure_type_to_java(class_name, prop_name, prop_schema, schema_namespace)
589
- field_name = pascal(prop_name) if self.pascal_properties else prop_name
590
- safe_field_name = self.safe_identifier(field_name, class_name)
591
- elements.append({
592
- 'name': safe_field_name,
593
- 'type': prop_type.type_name,
594
- 'docstring': prop_schema.get('description', prop_schema.get('doc', prop_name))
595
- })
596
-
597
- # Use template for tuple generation
598
- tuple_definition = process_template(
599
- "structuretojava/tuple_core.jinja",
600
- class_name=class_name,
601
- docstring=doc,
602
- deprecated=deprecated,
603
- elements=elements,
604
- jackson_annotation=self.jackson_annotations
605
- )
606
-
607
- if write_file:
608
- self.write_to_file(package, class_name, tuple_definition)
609
-
610
- return StructureToJava.JavaType(qualified_class_name, is_class=True)
611
-
612
- def generate_embedded_union_class_jackson(self, class_name: str, field_name: str, structure_types: List, parent_package: str, write_file: bool) -> str:
613
- """ Generates an embedded Union Class for Java using Jackson """
614
- # Simplified implementation - just return Object for unions
615
- return 'Object'
616
-
617
- def generate_equals_and_gethashcode(self, structure_schema: Dict, class_name: str, parent_namespace: str) -> str:
618
- """ Generates Equals and GetHashCode methods """
619
- code = "\n"
620
- properties = structure_schema.get('properties', {})
621
-
622
- # Filter out const properties
623
- non_const_properties = {k: v for k, v in properties.items() if 'const' not in v}
624
-
625
- if not non_const_properties:
626
- # Empty class
627
- code += f"{INDENT}@Override\n{INDENT}public boolean equals(Object obj) {{\n"
628
- code += f"{INDENT*2}return obj instanceof {class_name};\n"
629
- code += f"{INDENT}}}\n\n"
630
- code += f"{INDENT}@Override\n{INDENT}public int hashCode() {{\n"
631
- code += f"{INDENT*2}return 0;\n"
632
- code += f"{INDENT}}}\n"
633
- return code
634
-
635
- # Generate equals
636
- code += f"{INDENT}@Override\n{INDENT}public boolean equals(Object obj) {{\n"
637
- code += f"{INDENT*2}if (this == obj) return true;\n"
638
- code += f"{INDENT*2}if (!(obj instanceof {class_name})) return false;\n"
639
- code += f"{INDENT*2}{class_name} other = ({class_name}) obj;\n"
640
-
641
- equality_checks = []
642
- for prop_name in non_const_properties.keys():
643
- field_name_java = pascal(prop_name) if self.pascal_properties else prop_name
644
- safe_field_name = self.safe_identifier(field_name_java, class_name)
645
- equality_checks.append(f"Objects.equals(this.{safe_field_name}, other.{safe_field_name})")
646
-
647
- if len(equality_checks) == 1:
648
- code += f"{INDENT*2}return {equality_checks[0]};\n"
649
- else:
650
- code += f"{INDENT*2}return " + f"\n{INDENT*3}&& ".join(equality_checks) + ";\n"
651
-
652
- code += f"{INDENT}}}\n\n"
653
-
654
- # Generate hashCode
655
- code += f"{INDENT}@Override\n{INDENT}public int hashCode() {{\n"
656
-
657
- hash_fields = []
658
- for prop_name in non_const_properties.keys():
659
- field_name_java = pascal(prop_name) if self.pascal_properties else prop_name
660
- safe_field_name = self.safe_identifier(field_name_java, class_name)
661
- hash_fields.append(safe_field_name)
662
-
663
- if len(hash_fields) <= 8:
664
- code += f"{INDENT*2}return Objects.hash({', '.join(hash_fields)});\n"
665
- else:
666
- code += f"{INDENT*2}int result = Objects.hash({', '.join(hash_fields[:8])});\n"
667
- for i in range(8, len(hash_fields)):
668
- code += f"{INDENT*2}result = 31 * result + Objects.hashCode({hash_fields[i]});\n"
669
- code += f"{INDENT*2}return result;\n"
670
-
671
- code += f"{INDENT}}}\n"
672
-
673
- return code
674
-
675
- def write_to_file(self, package: str, name: str, definition: str):
676
- """ Writes a Java class or enum to a file """
677
- package = package.lower()
678
- package = self.safe_package(package)
679
- directory_path = os.path.join(
680
- self.output_dir, package.replace('.', os.sep).replace('/', os.sep))
681
- if not os.path.exists(directory_path):
682
- os.makedirs(directory_path, exist_ok=True)
683
- file_path = os.path.join(directory_path, f"{name}.java")
684
-
685
- with open(file_path, 'w', encoding='utf-8') as file:
686
- if package:
687
- file.write(f"package {package.replace('/', '.')};\n\n")
688
- if "List<" in definition:
689
- file.write("import java.util.List;\n")
690
- if "Set<" in definition:
691
- file.write("import java.util.Set;\n")
692
- if "Map<" in definition:
693
- file.write("import java.util.Map;\n")
694
- if "BigDecimal" in definition:
695
- file.write("import java.math.BigDecimal;\n")
696
- if "BigInteger" in definition:
697
- file.write("import java.math.BigInteger;\n")
698
- if "LocalDate" in definition:
699
- file.write("import java.time.LocalDate;\n")
700
- if "LocalTime" in definition:
701
- file.write("import java.time.LocalTime;\n")
702
- if "Instant" in definition:
703
- file.write("import java.time.Instant;\n")
704
- if "LocalDateTime" in definition:
705
- file.write("import java.time.LocalDateTime;\n")
706
- if "UUID" in definition:
707
- file.write("import java.util.UUID;\n")
708
- if "Duration" in definition:
709
- file.write("import java.time.Duration;\n")
710
- if "URI" in definition:
711
- file.write("import java.net.URI;\n")
712
- if "Objects" in definition:
713
- file.write("import java.util.Objects;\n")
714
-
715
- if self.jackson_annotations:
716
- if 'JsonProperty' in definition:
717
- file.write("import com.fasterxml.jackson.annotation.JsonProperty;\n")
718
- if 'JsonNode' in definition:
719
- file.write("import com.fasterxml.jackson.databind.JsonNode;\n")
720
- if 'ObjectMapper' in definition:
721
- file.write("import com.fasterxml.jackson.databind.ObjectMapper;\n")
722
- if 'JsonSerialize' in definition:
723
- file.write("import com.fasterxml.jackson.databind.annotation.JsonSerialize;\n")
724
- if 'JsonDeserialize' in definition:
725
- file.write("import com.fasterxml.jackson.databind.annotation.JsonDeserialize;\n")
726
- if 'JsonSerializer' in definition:
727
- file.write("import com.fasterxml.jackson.databind.JsonSerializer;\n")
728
- if 'SerializerProvider' in definition:
729
- file.write("import com.fasterxml.jackson.databind.SerializerProvider;\n")
730
- if 'JsonDeserializer' in definition:
731
- file.write("import com.fasterxml.jackson.databind.JsonDeserializer;\n")
732
- if 'DeserializationContext' in definition:
733
- file.write("import com.fasterxml.jackson.databind.DeserializationContext;\n")
734
- if 'JsonParser' in definition:
735
- file.write("import com.fasterxml.jackson.core.JsonParser;\n")
736
- if 'JsonIgnore' in definition:
737
- file.write("import com.fasterxml.jackson.annotation.JsonIgnore;\n")
738
- if 'JsonProcessingException' in definition:
739
- file.write("import com.fasterxml.jackson.core.JsonProcessingException;\n")
740
- if 'JsonGenerator' in definition:
741
- file.write("import com.fasterxml.jackson.core.JsonGenerator;\n")
742
- if 'TypeReference' in definition:
743
- file.write("import com.fasterxml.jackson.core.type.TypeReference;\n")
744
- if 'JsonFormat' in definition:
745
- file.write("import com.fasterxml.jackson.annotation.JsonFormat;\n")
746
- if 'JsonCreator' in definition:
747
- file.write("import com.fasterxml.jackson.annotation.JsonCreator;\n")
748
- if 'JsonValue' in definition:
749
- file.write("import com.fasterxml.jackson.annotation.JsonValue;\n")
750
- if 'JsonTypeInfo' in definition:
751
- file.write("import com.fasterxml.jackson.annotation.JsonTypeInfo;\n")
752
- if 'JsonSubTypes' in definition:
753
- file.write("import com.fasterxml.jackson.annotation.JsonSubTypes;\n")
754
- file.write("import com.fasterxml.jackson.core.JsonGenerator;\n")
755
- if 'TypeReference' in definition:
756
- file.write("import com.fasterxml.jackson.core.type.TypeReference;\n")
757
- file.write("\n")
758
- file.write(definition)
759
-
760
- def convert_schema(self, schema: JsonNode, output_dir: str):
761
- """Converts JSON Structure schema to Java"""
762
- if not isinstance(schema, list):
763
- schema = [schema]
764
- if not os.path.exists(output_dir):
765
- os.makedirs(output_dir, exist_ok=True)
766
- pom_path = os.path.join(output_dir, "pom.xml")
767
- if not os.path.exists(pom_path):
768
- package_elements = self.base_package.split('.') if self.base_package else ["com", "example"]
769
- groupid = '.'.join(package_elements[:-1]) if len(package_elements) > 1 else package_elements[0]
770
- artifactid = package_elements[-1]
771
- pom_content = process_template(
772
- "structuretojava/pom.xml.jinja",
773
- groupid=groupid,
774
- artifactid=artifactid,
775
- jackson_version=JACKSON_VERSION
776
- )
777
- with open(pom_path, 'w', encoding='utf-8') as file:
778
- file.write(pom_content)
779
- output_dir = os.path.join(
780
- output_dir, "src/main/java".replace('/', os.sep))
781
- if not os.path.exists(output_dir):
782
- os.makedirs(output_dir, exist_ok=True)
783
- self.output_dir = output_dir
784
-
785
- # Register all schemas with $id keywords
786
- for structure_schema in (x for x in schema if isinstance(x, dict)):
787
- self.schema_doc = structure_schema
788
- self.register_schema_ids(structure_schema)
789
-
790
- # Generate classes
791
- for structure_schema in (x for x in schema if isinstance(x, dict)):
792
- self.schema_doc = structure_schema
793
- if 'definitions' in structure_schema:
794
- self.process_definitions(structure_schema['definitions'], '')
795
- if 'type' in structure_schema or 'enum' in structure_schema:
796
- self.generate_class_or_enum(structure_schema, '')
797
-
798
- def process_definitions(self, definitions: Dict, namespace_path: str) -> None:
799
- """ Processes the definitions section recursively """
800
- for name, definition in definitions.items():
801
- if isinstance(definition, dict):
802
- if 'type' in definition or 'enum' in definition:
803
- # This is a type definition
804
- current_namespace = namespace_path
805
- self.generate_class_or_enum(definition, current_namespace, write_file=True, explicit_name=name)
806
- else:
807
- # This might be a nested namespace
808
- new_namespace = f"{namespace_path}.{name}" if namespace_path else name
809
- self.process_definitions(definition, new_namespace)
810
-
811
- def convert(self, structure_schema_path: str, output_dir: str):
812
- """Converts JSON Structure schema to Java"""
813
- with open(structure_schema_path, 'r', encoding='utf-8') as file:
814
- schema = json.load(file)
815
- self.convert_schema(schema, output_dir)
816
-
817
-
818
- def convert_structure_to_java(structure_schema_path, java_file_path, package_name='', pascal_properties=False, jackson_annotation=True):
819
- """
820
- Converts JSON Structure schema to Java classes
821
-
822
- Args:
823
- structure_schema_path: JSON Structure input schema path
824
- java_file_path: Output Java directory path
825
- package_name: Base package name
826
- pascal_properties: Use PascalCase for properties
827
- jackson_annotation: Add Jackson annotations (always True for Structure)
828
- """
829
- if not package_name:
830
- package_name = os.path.splitext(os.path.basename(java_file_path))[0].replace('-', '_').lower()
831
- structuretojava = StructureToJava()
832
- structuretojava.base_package = package_name
833
- structuretojava.pascal_properties = pascal_properties
834
- structuretojava.jackson_annotations = jackson_annotation
835
- structuretojava.convert(structure_schema_path, java_file_path)
836
-
837
-
838
- def convert_structure_schema_to_java(structure_schema: JsonNode, output_dir: str, package_name='', pascal_properties=False, jackson_annotation=True):
839
- """
840
- Converts JSON Structure schema to Java classes
841
-
842
- Args:
843
- structure_schema: JSON Structure schema as a dictionary or list of dictionaries
844
- output_dir: Output directory path
845
- package_name: Base package name
846
- pascal_properties: Use PascalCase for properties
847
- jackson_annotation: Add Jackson annotations (always True for Structure)
848
- """
849
- structuretojava = StructureToJava()
850
- structuretojava.base_package = package_name
851
- structuretojava.pascal_properties = pascal_properties
852
- structuretojava.jackson_annotations = jackson_annotation
853
- structuretojava.convert_schema(structure_schema, output_dir)