• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

aas-core-works / aas-core-codegen / 17360913018

31 Aug 2025 06:35PM UTC coverage: 82.105% (+0.1%) from 82.003%
17360913018

push

github

web-flow
Upgrade pylint to 3.3.8 (#551)

We upgrade pylint to the latest version so that we can still check the
code with the newest Python 3.13.

28501 of 34713 relevant lines covered (82.1%)

3.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.88
/aas_core_codegen/java/structure/_generate.py
1
"""Generate the Java data structures from the intermediate representation."""
2
import io
4✔
3
import textwrap
4✔
4
from typing import (
4✔
5
    cast,
6
    Dict,
7
    List,
8
    Optional,
9
    Tuple,
10
    Union,
11
)
12

13
from icontract import ensure, require
4✔
14

15
from aas_core_codegen import intermediate
4✔
16
from aas_core_codegen import specific_implementations
4✔
17
from aas_core_codegen.common import (
4✔
18
    assert_never,
19
    Error,
20
    Identifier,
21
    Stripped,
22
    indent_but_first_line,
23
)
24
from aas_core_codegen.java import (
4✔
25
    common as java_common,
26
    description as java_description,
27
    naming as java_naming,
28
)
29
from aas_core_codegen.java.common import (
4✔
30
    INDENT as I,
31
    INDENT2 as II,
32
    INDENT3 as III,
33
)
34
from aas_core_codegen.intermediate import (
4✔
35
    ListTypeAnnotation,
36
    OptionalTypeAnnotation,
37
    OurTypeAnnotation,
38
    PrimitiveTypeAnnotation,
39
    TypeAnnotationUnion,
40
    construction as intermediate_construction,
41
)
42

43

44
# region Checks
45

46

47
def _human_readable_identifier(
4✔
48
    something: Union[
49
        intermediate.Enumeration, intermediate.AbstractClass, intermediate.ConcreteClass
50
    ]
51
) -> str:
52
    """
53
    Represent ``something`` in a human-readable text.
54

55
    The reader should be able to trace ``something`` back to the meta-model.
56
    """
57
    result: str
58

59
    if isinstance(something, intermediate.Enumeration):
×
60
        result = f"meta-model enumeration {something.name!r}"
×
61
    elif isinstance(something, intermediate.AbstractClass):
×
62
        result = f"meta-model abstract class {something.name!r}"
×
63
    elif isinstance(something, intermediate.ConcreteClass):
×
64
        result = f"meta-model concrete class {something.name!r}"
×
65
    else:
66
        assert_never(something)
×
67

68
    return result
×
69

70

71
def _verify_intra_structure_collisions(
4✔
72
    our_type: intermediate.OurType,
73
) -> Optional[Error]:
74
    """Verify that no member names collide in the Java structure of our type."""
75
    errors = []  # type: List[Error]
4✔
76

77
    if isinstance(our_type, intermediate.Enumeration):
4✔
78
        pass
4✔
79

80
    elif isinstance(our_type, intermediate.ConstrainedPrimitive):
4✔
81
        pass
4✔
82

83
    elif isinstance(our_type, intermediate.Class):
4✔
84
        observed_member_names = {}  # type: Dict[Identifier, str]
4✔
85

86
        for prop in our_type.properties:
4✔
87
            prop_name = java_naming.property_name(prop.name)
4✔
88
            if prop_name in observed_member_names:
4✔
89
                # BEFORE-RELEASE (mristin, 2021-12-13): test
90
                errors.append(
×
91
                    Error(
92
                        prop.parsed.node,
93
                        f"Java property {prop_name!r} corresponding "
94
                        f"to the meta-model property {prop.name!r} collides with "
95
                        f"the {observed_member_names[prop_name]}",
96
                    )
97
                )
98
            else:
99
                observed_member_names[prop_name] = (
4✔
100
                    f"Java property {prop_name!r} corresponding to "
101
                    f"the meta-model property {prop.name!r}"
102
                )
103

104
        for method in our_type.methods:
4✔
105
            method_name = java_naming.method_name(method.name)
4✔
106

107
            if method_name in observed_member_names:
4✔
108
                # BEFORE-RELEASE (mristin, 2021-12-13): test
109
                errors.append(
×
110
                    Error(
111
                        method.parsed.node,
112
                        f"Java method {method_name!r} corresponding "
113
                        f"to the meta-model method {method.name!r} collides with "
114
                        f"the {observed_member_names[method_name]}",
115
                    )
116
                )
117
            else:
118
                observed_member_names[method_name] = (
4✔
119
                    f"Java method {method_name!r} corresponding to "
120
                    f"the meta-model method {method.name!r}"
121
                )
122

123
    else:
124
        assert_never(our_type)
×
125

126
    if len(errors) > 0:
4✔
127
        errors.append(
×
128
            Error(
129
                our_type.parsed.node,
130
                f"Naming collision(s) in Java code for our type {our_type.name!r}",
131
                underlying=errors,
132
            )
133
        )
134

135
    return None
4✔
136

137

138
def _verify_structure_name_collisions(
4✔
139
    symbol_table: intermediate.SymbolTable,
140
) -> List[Error]:
141
    """Verify that the Java names of the structures do not collide."""
142
    observed_structure_names: Dict[
4✔
143
        Identifier,
144
        Union[
145
            intermediate.Enumeration,
146
            intermediate.AbstractClass,
147
            intermediate.ConcreteClass,
148
        ],
149
    ] = dict()
150

151
    errors = []  # type: List[Error]
4✔
152

153
    # region Inter-structure collisions
154

155
    for our_type in symbol_table.our_types:
4✔
156
        if not isinstance(
4✔
157
            our_type,
158
            (
159
                intermediate.Enumeration,
160
                intermediate.AbstractClass,
161
                intermediate.ConcreteClass,
162
            ),
163
        ):
164
            continue
4✔
165

166
        if isinstance(our_type, intermediate.Enumeration):
4✔
167
            name = java_naming.enum_name(our_type.name)
4✔
168
            other = observed_structure_names.get(name, None)
4✔
169

170
            if other is not None:
4✔
171
                errors.append(
×
172
                    Error(
173
                        our_type.parsed.node,
174
                        f"The Java name {name!r} for the enumeration {our_type.name!r} "
175
                        f"collides with the same Java name "
176
                        f"coming from the {_human_readable_identifier(other)}",
177
                    )
178
                )
179
            else:
180
                observed_structure_names[name] = our_type
4✔
181

182
        elif isinstance(
4✔
183
            our_type, (intermediate.AbstractClass, intermediate.ConcreteClass)
184
        ):
185
            interface_name = java_naming.interface_name(our_type.name)
4✔
186

187
            other = observed_structure_names.get(interface_name, None)
4✔
188

189
            if other is not None:
4✔
190
                errors.append(
×
191
                    Error(
192
                        our_type.parsed.node,
193
                        f"The Java name {interface_name!r} of the interface "
194
                        f"for the class {our_type.name!r} "
195
                        f"collides with the same Java name "
196
                        f"coming from the {_human_readable_identifier(other)}",
197
                    )
198
                )
199
            else:
200
                observed_structure_names[interface_name] = our_type
4✔
201

202
            if isinstance(our_type, intermediate.ConcreteClass):
4✔
203
                class_name = java_naming.class_name(our_type.name)
4✔
204

205
                other = observed_structure_names.get(class_name, None)
4✔
206

207
                if other is not None:
4✔
208
                    errors.append(
×
209
                        Error(
210
                            our_type.parsed.node,
211
                            f"The Java name {class_name!r} "
212
                            f"for the class {our_type.name!r} "
213
                            f"collides with the same Java name "
214
                            f"coming from the {_human_readable_identifier(other)}",
215
                        )
216
                    )
217
                else:
218
                    observed_structure_names[class_name] = our_type
4✔
219
        else:
220
            assert_never(our_type)
×
221

222
    # endregion
223

224
    # region Intra-structure collisions
225

226
    for our_type in symbol_table.our_types:
4✔
227
        collision_error = _verify_intra_structure_collisions(our_type=our_type)
4✔
228

229
        if collision_error is not None:
4✔
230
            errors.append(collision_error)
×
231

232
    # endregion
233

234
    return errors
4✔
235

236

237
class VerifiedIntermediateSymbolTable(intermediate.SymbolTable):
4✔
238
    """Represent a verified symbol table which can be used for code generation."""
239

240
    # noinspection PyInitNewSignature
241
    def __new__(
4✔
242
        cls, symbol_table: intermediate.SymbolTable
243
    ) -> "VerifiedIntermediateSymbolTable":
244
        raise AssertionError("Only for type annotation")
×
245

246

247
@ensure(lambda result: (result[0] is None) ^ (result[1] is None))
4✔
248
def verify(
4✔
249
    symbol_table: intermediate.SymbolTable,
250
) -> Tuple[Optional[VerifiedIntermediateSymbolTable], Optional[List[Error]]]:
251
    """Verify that Java code can be generated from the ``symbol_table``."""
252

253
    errors = []  # type: List[Error]
4✔
254

255
    structure_name_collisions = _verify_structure_name_collisions(
4✔
256
        symbol_table=symbol_table
257
    )
258

259
    errors.extend(structure_name_collisions)
4✔
260

261
    if len(errors) > 0:
4✔
262
        return None, errors
×
263

264
    return cast(VerifiedIntermediateSymbolTable, symbol_table), None
4✔
265

266

267
# endregion
268

269
# region Generation
270

271

272
def _beneath_optional_or_list(
4✔
273
    type_anno: TypeAnnotationUnion,
274
) -> Union[PrimitiveTypeAnnotation, OurTypeAnnotation]:
275
    while isinstance(type_anno, (ListTypeAnnotation, OptionalTypeAnnotation)):
4✔
276
        if isinstance(type_anno, ListTypeAnnotation):
4✔
277
            type_anno = type_anno.items
4✔
278
        else:
279
            type_anno = type_anno.value
4✔
280

281
    return type_anno
4✔
282

283

284
def _has_descendable_properties(cls: intermediate.Class) -> bool:
4✔
285
    for prop in cls.properties:
4✔
286
        type_anno = _beneath_optional_or_list(prop.type_annotation)
4✔
287

288
        if not isinstance(type_anno, intermediate.OurTypeAnnotation):
4✔
289
            continue
4✔
290

291
        our_type = type_anno.our_type
4✔
292

293
        if isinstance(
4✔
294
            our_type,
295
            (
296
                intermediate.Enumeration,
297
                intermediate.ConstrainedPrimitive,
298
            ),
299
        ):
300
            continue
4✔
301

302
        descendability = intermediate.map_descendability(
4✔
303
            type_annotation=prop.type_annotation
304
        )
305

306
        if descendability:
4✔
307
            return True
4✔
308

309
    return False
4✔
310

311

312
def _generate_descend_body(cls: intermediate.ConcreteClass, recurse: bool) -> Stripped:
4✔
313
    """Generate the iterator function body for recursive and non-recursive descend methods.
314

315
    We leverage lazily evaluated streams to iterate over the object stream one by one.
316
    """
317
    class_name = java_naming.class_name(cls.name)
4✔
318

319
    blocks = [
4✔
320
        Stripped("Stream<IClass> memberStream = Stream.empty();")
321
    ]  # type: List[Stripped]
322

323
    # region Streams
324

325
    for prop in cls.properties:
4✔
326
        descendability = intermediate.map_descendability(
4✔
327
            type_annotation=prop.type_annotation
328
        )
329

330
        if not descendability[prop.type_annotation]:
4✔
331
            continue
4✔
332

333
        prop_expr = None  # type: Optional[Stripped]
4✔
334

335
        prop_name = java_naming.property_name(prop.name)
4✔
336

337
        type_anno = intermediate.beneath_optional(prop.type_annotation)
4✔
338

339
        if isinstance(type_anno, intermediate.PrimitiveTypeAnnotation):
4✔
340
            continue
×
341
        elif isinstance(type_anno, intermediate.OurTypeAnnotation):
4✔
342
            if isinstance(type_anno.our_type, intermediate.Enumeration):
4✔
343
                continue
×
344
            elif isinstance(type_anno.our_type, intermediate.ConstrainedPrimitive):
4✔
345
                continue
×
346
            elif isinstance(
4✔
347
                type_anno.our_type,
348
                (intermediate.AbstractClass, intermediate.ConcreteClass),
349
            ):
350
                if not descendability[type_anno] or not recurse:
4✔
351
                    prop_expr = Stripped(
4✔
352
                        f"Stream.<IClass>of({class_name}.this.{prop_name})"
353
                    )
354
                else:
355
                    prop_expr = Stripped(
4✔
356
                        f"""\
357
Stream.concat(Stream.<IClass>of({class_name}.this.{prop_name}),
358
{I}StreamSupport.stream({class_name}.this.{prop_name}.descend().spliterator(), false))"""
359
                    )
360
            else:
361
                assert_never(type_anno.our_type)
×
362

363
        elif isinstance(type_anno, intermediate.ListTypeAnnotation):
4✔
364
            assert isinstance(
4✔
365
                type_anno.items, intermediate.OurTypeAnnotation
366
            ) and isinstance(
367
                type_anno.items.our_type,
368
                (intermediate.AbstractClass, intermediate.ConcreteClass),
369
            ), (
370
                f"We expect only list of classes "
371
                f"at the moment, but you specified {type_anno}. "
372
                f"Please contact the developers if you need this feature."
373
            )
374

375
            if not recurse:
4✔
376
                prop_expr = Stripped(f"{class_name}.this.{prop_name}.stream()")
4✔
377
            else:
378
                prop_expr = Stripped(
4✔
379
                    f"""\
380
{class_name}.this.{prop_name}.stream()
381
{I}.flatMap(item -> Stream.concat(Stream.<IClass>of(item),
382
{II}StreamSupport.stream(item.descend().spliterator(), false)))"""
383
                )
384

385
        else:
386
            assert_never(type_anno)
×
387

388
        stream_stmt = Stripped(
4✔
389
            f"""\
390
if ({prop_name} != null) {{
391
{I}memberStream = Stream.concat(memberStream,
392
{II}{indent_but_first_line(prop_expr, II)});
393
}}"""
394
        )
395

396
        blocks.append(stream_stmt)
4✔
397

398
    # endregion
399

400
    blocks.append(Stripped("return memberStream;"))
4✔
401

402
    return Stripped("\n\n".join(blocks))
4✔
403

404

405
def _generate_descend_iterable(
4✔
406
    cls: intermediate.ConcreteClass, recursive: bool
407
) -> Stripped:
408
    """Generate the iterator for the descend method."""
409

410
    cls_name = java_naming.class_name(cls.name)
4✔
411

412
    iterable_name: Stripped
413

414
    if recursive:
4✔
415
        iterable_name = Stripped(f"{cls_name}RecursiveIterable")
4✔
416
    else:
417
        iterable_name = Stripped(f"{cls_name}Iterable")
4✔
418

419
    iterable_body = _generate_descend_body(cls, recursive)
4✔
420

421
    iterable = Stripped(
4✔
422
        f"""\
423
private class {iterable_name} implements Iterable<IClass> {{
424
{I}@Override
425
{I}public Iterator<IClass> iterator() {{
426
{II}Stream<IClass> stream = stream();
427

428
{II}return stream.iterator();
429
{I}}}
430

431
{I}@Override
432
{I}public void forEach(Consumer<? super IClass> action) {{
433
{II}Stream<IClass> stream = stream();
434

435
{II}stream.forEach(action);
436
{I}}}
437

438
{I}@Override
439
{I}public Spliterator<IClass> spliterator() {{
440
{II}Stream<IClass> stream = stream();
441

442
{II}return stream.spliterator();
443
{I}}}
444

445
{I}private Stream<IClass> stream() {{
446
{II}{indent_but_first_line(iterable_body, II)}
447
{I}}}
448
}}"""
449
    )
450

451
    return iterable
4✔
452

453

454
def _generate_descend_method(
4✔
455
    cls: intermediate.ConcreteClass, descendable: bool
456
) -> Stripped:
457
    """Generate the recursive ``Descend`` method for the concrete class ``cls``."""
458

459
    cls_name = java_naming.class_name(cls.name)
4✔
460

461
    iterable_name = Stripped(f"{cls_name}RecursiveIterable")
4✔
462

463
    descend_body: Stripped
464

465
    if descendable:
4✔
466
        descend_body = Stripped(f"return new {iterable_name}();")
4✔
467
    else:
468
        descend_body = Stripped("return Collections.emptyList();")
4✔
469

470
    return Stripped(
4✔
471
        f"""\
472
/**
473
 * Iterate recursively over all the class instances referenced from this instance.
474
 */
475
public Iterable<IClass> descend() {{
476
{I}{descend_body}
477
}}"""
478
    )
479

480

481
def _generate_descend_once_method(
4✔
482
    cls: intermediate.ConcreteClass, descendable: bool
483
) -> Stripped:
484
    """Generate the recursive ``Descend`` method for the concrete class ``cls``."""
485

486
    cls_name = java_naming.class_name(cls.name)
4✔
487

488
    iterable_name = Stripped(f"{cls_name}Iterable")
4✔
489

490
    descend_body: Stripped
491
    if descendable:
4✔
492
        descend_body = Stripped(f"return new {iterable_name}();")
4✔
493
    else:
494
        descend_body = Stripped("return Collections.emptyList();")
4✔
495

496
    return Stripped(
4✔
497
        f"""\
498
/**
499
 * Iterate over all the class instances referenced from this instance.
500
 */
501
public Iterable<IClass> descendOnce() {{
502
{I}{descend_body}
503
}}"""
504
    )
505

506

507
def _generate_imports_for_interface(
4✔
508
    cls: intermediate.ClassUnion,
509
    package: java_common.PackageIdentifier,
510
) -> Stripped:
511
    """
512
    Generate necessary Java Platform imports for the given class ``cls``.
513

514
    The ``package`` defines the root Java package.
515
    """
516
    imports = [
4✔
517
        Stripped(f"{package}.types.enums.*"),
518
        Stripped(f"{package}.types.impl.*"),
519
        Stripped(f"{package}.types.model.*"),
520
        Stripped("java.util.List"),
521
    ]  # type: List[Stripped]
522

523
    if len(cls.inheritances) == 0:
4✔
524
        import_name = Stripped(f"{package}.types.{java_common.INTERFACE_PKG}.IClass")
4✔
525
        imports.append(import_name)
4✔
526
    else:
527
        for inheritance in cls.inheritances:
4✔
528
            super_name = java_naming.interface_name(inheritance.name)
4✔
529

530
            import_name = Stripped(
4✔
531
                f"{package}.types.{java_common.INTERFACE_PKG}.{super_name}"
532
            )
533

534
            imports.append(import_name)
4✔
535

536
    if any(prop for prop in cls.properties if prop.specified_for is cls):
4✔
537
        imports.append(Stripped("java.util.Optional"))
4✔
538

539
    return Stripped("\n".join(map(lambda imp: f"import {imp};", imports)))
4✔
540

541

542
def _generate_imports_for_class(
4✔
543
    cls: intermediate.Class,
544
    package: java_common.PackageIdentifier,
545
) -> Stripped:
546
    """
547
    Generate necessary Java Platform imports for the given class ``cls``.
548

549
    The ``package`` defines the root Java package.
550
    """
551
    if cls.is_implementation_specific:
4✔
552
        return Stripped("")
×
553

554
    imports = [
4✔
555
        Stripped(f"{package}.visitation.IVisitor"),
556
        Stripped(f"{package}.visitation.IVisitorWithContext"),
557
        Stripped(f"{package}.visitation.ITransformer"),
558
        Stripped(f"{package}.visitation.ITransformerWithContext"),
559
        Stripped(f"{package}.types.enums.*"),
560
        Stripped(f"{package}.types.impl.*"),
561
        Stripped(f"{package}.types.model.*"),
562
        Stripped("java.util.Collections"),
563
        Stripped("java.util.List"),
564
        Stripped("java.util.Optional"),
565
        Stripped("java.util.Objects"),
566
    ]  # type: List[Stripped]
567

568
    if _has_descendable_properties(cls):
4✔
569
        imports.extend(
4✔
570
            [
571
                Stripped("java.util.Iterator"),
572
                Stripped("java.util.Spliterator"),
573
                Stripped("java.util.function.Consumer"),
574
                Stripped("java.util.stream.Stream"),
575
                Stripped("java.util.stream.StreamSupport"),
576
            ]
577
        )
578

579
    interface_name = java_naming.interface_name(cls.name)
4✔
580

581
    interface_import = Stripped(
4✔
582
        f"{package}.types.{java_common.INTERFACE_PKG}.{interface_name}"
583
    )
584

585
    imports.append(interface_import)
4✔
586

587
    if any(prop for prop in cls.properties if prop.specified_for is cls):
4✔
588
        imports.extend(
4✔
589
            [
590
                Stripped("java.util.Collections"),
591
                Stripped("java.util.List"),
592
                Stripped("java.util.Objects"),
593
            ]
594
        )
595

596
    return Stripped("\n".join(map(lambda imp: f"import {imp};", imports)))
4✔
597

598

599
@ensure(lambda result: (result[0] is None) ^ (result[1] is None))
4✔
600
def _generate_interface(
4✔
601
    cls: intermediate.ClassUnion, package: java_common.PackageIdentifier
602
) -> Tuple[Optional[Stripped], Optional[Error]]:
603
    """
604
    Generate Java interface for the given class ``cls``.
605

606
    The ``package`` defines the root Java package.
607
    """
608
    writer = io.StringIO()
4✔
609

610
    if cls.description is not None:
4✔
611
        comment, comment_errors = java_description.generate_comment_for_our_type(
4✔
612
            description=cls.description,
613
            context=java_description.Context(package=package, cls_or_enum=cls),
614
        )
615

616
        if comment_errors is not None:
4✔
617
            return None, Error(
×
618
                cls.description.parsed.node,
619
                "Failed to generate the documentation comment",
620
                comment_errors,
621
            )
622

623
        assert comment is not None
4✔
624

625
        writer.write(comment)
4✔
626
        writer.write("\n")
4✔
627

628
    name = java_naming.interface_name(cls.name)
4✔
629

630
    inheritances = [inheritance.name for inheritance in cls.inheritances]
4✔
631
    if len(inheritances) == 0:
4✔
632
        inheritances = [Identifier("Class")]
4✔
633

634
    inheritance_names = list(map(java_naming.interface_name, inheritances))
4✔
635

636
    assert len(inheritances) > 0
4✔
637
    if len(inheritances) == 1:
4✔
638
        writer.write(
4✔
639
            f"""\
640
public interface {name} extends {inheritance_names[0]} {{\n"""
641
        )
642
    else:
643
        writer.write(
4✔
644
            f"""
645
public interface {name} extends\n"""
646
        )
647
        for i, inheritance_name in enumerate(inheritance_names):
4✔
648
            if i > 0:
4✔
649
                writer.write(",\n")
4✔
650

651
            writer.write(textwrap.indent(inheritance_name, II))
4✔
652

653
        writer.write(" {\n")
4✔
654

655
    # Code blocks separated by double newlines and indented once
656
    blocks = []  # type: List[Stripped]
4✔
657

658
    # region Getters and setters
659

660
    for prop in cls.properties:
4✔
661
        if prop.specified_for is not cls:
4✔
662
            continue
4✔
663

664
        type_anno = intermediate.beneath_optional(prop.type_annotation)
4✔
665

666
        prop_type = java_common.generate_type(type_annotation=prop.type_annotation)
4✔
667
        arg_type = java_common.generate_type(type_annotation=type_anno)
4✔
668

669
        prop_name = java_naming.property_name(prop.name)
4✔
670

671
        getter_name = java_naming.getter_name(prop.name)
4✔
672
        setter_name = java_naming.setter_name(prop.name)
4✔
673

674
        if prop.description is not None:
4✔
675
            (
4✔
676
                prop_comment,
677
                prop_comment_errors,
678
            ) = java_description.generate_comment_for_property(
679
                description=prop.description,
680
                context=java_description.Context(package=package, cls_or_enum=cls),
681
            )
682

683
            if prop_comment_errors is not None:
4✔
684
                return None, Error(
×
685
                    prop.description.parsed.node,
686
                    f"Failed to generate the documentation comment "
687
                    f"for the property {prop.name!r}",
688
                    prop_comment_errors,
689
                )
690

691
            blocks.append(Stripped(f"{prop_comment}\n{prop_type} {getter_name}();"))
4✔
692
        else:
693
            blocks.append(Stripped(f"{prop_type} {getter_name}();"))
×
694

695
        blocks.append(Stripped(f"void {setter_name}({arg_type} {prop_name});"))
4✔
696

697
    # endregion
698

699
    # region Signatures
700

701
    for method in cls.methods:
4✔
702
        if method.specified_for is not cls:
4✔
703
            continue
4✔
704

705
        signature_blocks = []  # type: List[Stripped]
4✔
706

707
        if method.description is not None:
4✔
708
            (
×
709
                signature_comment,
710
                signature_comment_errors,
711
            ) = java_description.generate_comment_for_signature(
712
                description=method.description,
713
                context=java_description.Context(package=package, cls_or_enum=cls),
714
            )
715

716
            if signature_comment_errors is not None:
×
717
                return None, Error(
×
718
                    method.description.parsed.node,
719
                    f"Failed to generate the documentation comment "
720
                    f"for the method {method.name!r}",
721
                    signature_comment_errors,
722
                )
723

724
            assert signature_comment is not None
×
725

726
            signature_blocks.append(signature_comment)
×
727

728
        # fmt: off
729
        returns = (
4✔
730
            java_common.generate_type(type_annotation=method.returns)
731
            if method.returns is not None else "void"
732
        )
733
        # fmt: on
734

735
        arg_codes = []  # type: List[Stripped]
4✔
736
        for arg in method.arguments:
4✔
737
            arg_type = java_common.generate_type(type_annotation=arg.type_annotation)
×
738
            arg_name = java_naming.argument_name(arg.name)
×
739
            arg_codes.append(Stripped(f"{arg_type} {arg_name}"))
×
740

741
        signature_name = java_naming.method_name(method.name)
4✔
742
        if len(arg_codes) > 2:
4✔
743
            arg_block = ",\n".join(arg_codes)
×
744
            arg_block_indented = textwrap.indent(arg_block, I)
×
745
            signature_blocks.append(
×
746
                Stripped(f"{returns} {signature_name}(\n{arg_block_indented});")
747
            )
748
        elif len(arg_codes) == 1:
4✔
749
            signature_blocks.append(
×
750
                Stripped(f"{returns} {signature_name}({arg_codes[0]});")
751
            )
752
        else:
753
            assert len(arg_codes) == 0
4✔
754
            signature_blocks.append(Stripped(f"{returns} {signature_name}();"))
4✔
755

756
        blocks.append(Stripped("\n".join(signature_blocks)))
4✔
757

758
    for prop in cls.properties:
4✔
759
        if prop.specified_for is not cls:
4✔
760
            continue
4✔
761

762
        if isinstance(
4✔
763
            prop.type_annotation, intermediate.OptionalTypeAnnotation
764
        ) and isinstance(prop.type_annotation.value, intermediate.ListTypeAnnotation):
765
            prop_name = java_naming.property_name(prop.name)
4✔
766
            method_name = f"over{java_naming.class_name(prop.name)}OrEmpty"
4✔
767
            items_type = java_common.generate_type(prop.type_annotation.value.items)
4✔
768
            blocks.append(
4✔
769
                Stripped(
770
                    f"""\
771
/**
772
 * Iterate over {prop_name}, if set, and otherwise return an empty enumerable.
773
 */
774
Iterable<{items_type}> {method_name}();"""
775
                )
776
            )
777

778
    # endregion
779

780
    if len(blocks) == 0:
4✔
781
        blocks = [Stripped("// Intentionally empty.")]
4✔
782

783
    for i, code in enumerate(blocks):
4✔
784
        if i > 0:
4✔
785
            writer.write("\n\n")
4✔
786

787
        writer.write(textwrap.indent(code, I))
4✔
788

789
    writer.write("\n}")
4✔
790

791
    return Stripped(writer.getvalue()), None
4✔
792

793

794
@require(lambda cls: not cls.is_implementation_specific)
4✔
795
@require(lambda cls: not cls.constructor.is_implementation_specific)
4✔
796
@ensure(lambda result: (result[0] is not None) ^ (result[1] is not None))
4✔
797
def _generate_mandatory_constructor(
4✔
798
    cls: intermediate.ConcreteClass,
799
) -> Tuple[Optional[Stripped], Optional[Error]]:
800
    """
801
    Generate a default constructor for the given concrete class ``cls``.
802

803
    Return empty string if there is an empty constructor or no default constructor
804
    can be constructed.
805
    """
806
    if (
4✔
807
        len(cls.constructor.arguments) == 0
808
        and len(cls.constructor.inlined_statements) == 0
809
    ):
810
        return Stripped(""), None
×
811

812
    if all(
4✔
813
        isinstance(arg.type_annotation, intermediate.OptionalTypeAnnotation)
814
        for arg in cls.constructor.arguments
815
    ):
816
        return Stripped(""), None
4✔
817

818
    cls_name = java_naming.class_name(cls.name)
4✔
819

820
    blocks = []  # type: List[str]
4✔
821

822
    arg_codes = []  # type: List[Stripped]
4✔
823
    for arg in cls.constructor.arguments:
4✔
824
        type_anno = arg.type_annotation
4✔
825

826
        if isinstance(type_anno, intermediate.OptionalTypeAnnotation):
4✔
827
            continue
4✔
828

829
        arg_type = java_common.generate_type(type_annotation=type_anno)
4✔
830

831
        arg_name = java_naming.argument_name(arg.name)
4✔
832

833
        arg_codes.append(Stripped(f"{arg_type} {arg_name}"))
4✔
834

835
    assert len(arg_codes) > 0
4✔
836

837
    if len(arg_codes) == 1:
4✔
838
        blocks.append(f"public {cls_name}({arg_codes[0]}) {{")
4✔
839
    else:
840
        arg_block = ",\n".join(arg_codes)
4✔
841
        arg_block_indented = textwrap.indent(arg_block, I)
4✔
842
        blocks.append(f"public {cls_name}(\n{arg_block_indented}) {{")
4✔
843

844
    body = []  # type: List[Stripped]
4✔
845

846
    for stmt in cls.constructor.inlined_statements:
4✔
847
        if isinstance(stmt, intermediate_construction.AssignArgument):
4✔
848
            if stmt.default is None:
4✔
849
                prop_name = java_naming.property_name(stmt.name)
4✔
850

851
                arg_name = java_naming.argument_name(stmt.argument)
4✔
852

853
                if isinstance(
4✔
854
                    cls.properties_by_name[stmt.name].type_annotation,
855
                    intermediate.OptionalTypeAnnotation,
856
                ):
857
                    continue
4✔
858

859
                assignment = Stripped(
4✔
860
                    f"""\
861
this.{prop_name} = Objects.requireNonNull(
862
{I}{arg_name},
863
{I}"Argument \\"{arg_name}\\" must be non-null.");"""
864
                )
865

866
                body.append(assignment)
4✔
867
            else:
868
                if isinstance(stmt.default, intermediate_construction.EmptyList):
×
869
                    prop = cls.properties_by_name[stmt.name]
×
870

871
                    type_anno = intermediate.beneath_optional(prop.type_annotation)
×
872

873
                    prop_type = java_common.generate_type(
×
874
                        type_annotation=type_anno,
875
                    )
876

877
                    prop_name = java_naming.property_name(stmt.name)
×
878

879
                    arg_name = java_naming.argument_name(stmt.argument)
×
880

881
                    # Write the assignment as a ternary operator
882

883
                    assignment = Stripped(
×
884
                        f"""\
885
this.{prop_name} = ({arg_name} != null)
886
{I}? {arg_name}
887
{I}: new {prop_type}();"""
888
                    )
889

890
                    body.append(assignment)
×
891
                elif isinstance(
×
892
                    stmt.default, intermediate_construction.DefaultEnumLiteral
893
                ):
894
                    enum_name = java_naming.enum_name(stmt.default.enum.name)
×
895

896
                    enum_literal = java_naming.enum_literal_name(
×
897
                        stmt.default.literal.name
898
                    )
899

900
                    prop_name = java_naming.property_name(stmt.name)
×
901

902
                    arg_name = java_naming.argument_name(stmt.argument)
×
903

904
                    # Write the assignment as a ternary operator
905

906
                    body.append(
×
907
                        Stripped(
908
                            f"""\
909
this.{prop_name} = ({arg_name} != null)
910
{I}? {arg_name}
911
{I}: {enum_name}.{enum_literal};"""
912
                        )
913
                    )
914
                else:
915
                    assert_never(stmt.default)
×
916

917
        else:
918
            assert_never(stmt)
×
919

920
    blocks.append("\n".join(textwrap.indent(stmt_code, I) for stmt_code in body))
4✔
921

922
    blocks.append("}")
4✔
923

924
    return Stripped("\n".join(blocks)), None
4✔
925

926

927
@require(lambda cls: not cls.is_implementation_specific)
4✔
928
@require(lambda cls: not cls.constructor.is_implementation_specific)
4✔
929
@ensure(lambda result: (result[0] is not None) ^ (result[1] is not None))
4✔
930
def _generate_full_constructor(
4✔
931
    cls: intermediate.ConcreteClass,
932
) -> Tuple[Optional[Stripped], Optional[Error]]:
933
    """
934
    Generate the constructor functions for the given concrete class ``cls``.
935

936
    Return empty string if there is an empty constructor.
937
    """
938
    if (
4✔
939
        len(cls.constructor.arguments) == 0
940
        and len(cls.constructor.inlined_statements) == 0
941
    ):
942
        return Stripped(""), None
×
943

944
    if not any(
4✔
945
        isinstance(arg.type_annotation, intermediate.OptionalTypeAnnotation)
946
        for arg in cls.constructor.arguments
947
    ):
948
        return Stripped(""), None
4✔
949

950
    cls_name = java_naming.class_name(cls.name)
4✔
951

952
    blocks = []  # type: List[str]
4✔
953

954
    arg_codes = []  # type: List[Stripped]
4✔
955
    for arg in cls.constructor.arguments:
4✔
956
        type_anno = intermediate.beneath_optional(arg.type_annotation)
4✔
957

958
        arg_type = java_common.generate_type(type_annotation=type_anno)
4✔
959

960
        arg_name = java_naming.argument_name(arg.name)
4✔
961

962
        arg_codes.append(Stripped(f"{arg_type} {arg_name}"))
4✔
963

964
    if len(arg_codes) == 0:
4✔
965
        blocks.append(f"public {cls_name}() {{")
×
966
    elif len(arg_codes) == 1:
4✔
967
        blocks.append(f"public {cls_name}({arg_codes[0]}) {{")
×
968
    else:
969
        arg_block = ",\n".join(arg_codes)
4✔
970
        arg_block_indented = textwrap.indent(arg_block, I)
4✔
971
        blocks.append(f"public {cls_name}(\n{arg_block_indented}) {{")
4✔
972

973
    body = []  # type: List[Stripped]
4✔
974

975
    for stmt in cls.constructor.inlined_statements:
4✔
976
        if isinstance(stmt, intermediate_construction.AssignArgument):
4✔
977
            if stmt.default is None:
4✔
978
                prop_name = java_naming.property_name(stmt.name)
4✔
979

980
                arg_name = java_naming.argument_name(stmt.argument)
4✔
981

982
                if isinstance(
4✔
983
                    cls.properties_by_name[stmt.name].type_annotation,
984
                    intermediate.OptionalTypeAnnotation,
985
                ):
986
                    assignment = Stripped(f"this.{prop_name} = {arg_name};")
4✔
987
                else:
988
                    assignment = Stripped(
4✔
989
                        f"""\
990
this.{prop_name} = Objects.requireNonNull(
991
{I}{arg_name},
992
{I}"Argument \\"{arg_name}\\" must be non-null.");"""
993
                    )
994

995
                body.append(assignment)
4✔
996
            else:
997
                if isinstance(stmt.default, intermediate_construction.EmptyList):
×
998
                    prop = cls.properties_by_name[stmt.name]
×
999

1000
                    type_anno = intermediate.beneath_optional(prop.type_annotation)
×
1001

1002
                    prop_type = java_common.generate_type(
×
1003
                        type_annotation=type_anno,
1004
                    )
1005

1006
                    prop_name = java_naming.property_name(stmt.name)
×
1007

1008
                    arg_name = java_naming.argument_name(stmt.argument)
×
1009

1010
                    # Write the assignment as a ternary operator
1011

1012
                    assignment = Stripped(
×
1013
                        f"""\
1014
this.{prop_name} = ({arg_name} != null)
1015
{I}? {arg_name}
1016
{I}: new {prop_type}();"""
1017
                    )
1018

1019
                    body.append(assignment)
×
1020
                elif isinstance(
×
1021
                    stmt.default, intermediate_construction.DefaultEnumLiteral
1022
                ):
1023
                    enum_name = java_naming.enum_name(stmt.default.enum.name)
×
1024

1025
                    enum_literal = java_naming.enum_literal_name(
×
1026
                        stmt.default.literal.name
1027
                    )
1028

1029
                    prop_name = java_naming.property_name(stmt.name)
×
1030

1031
                    arg_name = java_naming.argument_name(stmt.argument)
×
1032

1033
                    # Write the assignment as a ternary operator
1034

1035
                    body.append(
×
1036
                        Stripped(
1037
                            f"""\
1038
this.{prop_name} = ({arg_name} != null)
1039
{I}? {arg_name}
1040
{I}: {enum_name}.{enum_literal};"""
1041
                        )
1042
                    )
1043
                else:
1044
                    assert_never(stmt.default)
×
1045

1046
        else:
1047
            assert_never(stmt)
×
1048

1049
    blocks.append("\n".join(textwrap.indent(stmt_code, I) for stmt_code in body))
4✔
1050

1051
    blocks.append(Stripped("}"))
4✔
1052

1053
    return Stripped("\n".join(blocks)), None
4✔
1054

1055

1056
# fmt: off
1057
@ensure(lambda result: (result[0] is None) ^ (result[1] is None))
4✔
1058
# fmt: on
1059
def _generate_class(
4✔
1060
    cls: intermediate.ConcreteClass,
1061
    spec_impls: specific_implementations.SpecificImplementations,
1062
    package: java_common.PackageIdentifier,
1063
) -> Tuple[Optional[Stripped], Optional[Error]]:
1064
    """
1065
    Generate Java code for the class ``cls``.
1066

1067
    The ``package`` defines the root Java package.
1068
    """
1069
    # Code blocks to be later joined by double newlines and indented once
1070

1071
    blocks = []  # type: List[Stripped]
4✔
1072

1073
    errors = []  # type: List[Error]
4✔
1074

1075
    descendable = _has_descendable_properties(cls)
4✔
1076

1077
    # region Properties
1078

1079
    for prop in cls.properties:
4✔
1080
        type_anno = intermediate.beneath_optional(prop.type_annotation)
4✔
1081

1082
        prop_type = java_common.generate_type(type_annotation=type_anno)
4✔
1083

1084
        prop_name = java_naming.property_name(prop.name)
4✔
1085

1086
        prop_blocks = []  # type: List[Stripped]
4✔
1087

1088
        if prop.description is not None:
4✔
1089
            (
4✔
1090
                prop_comment,
1091
                prop_comment_errors,
1092
            ) = java_description.generate_comment_for_property(
1093
                description=prop.description,
1094
                context=java_description.Context(package=package, cls_or_enum=cls),
1095
            )
1096
            if prop_comment_errors:
4✔
1097
                return None, Error(
×
1098
                    prop.description.parsed.node,
1099
                    f"Failed to generate the documentation comment "
1100
                    f"for the property {prop.name!r}",
1101
                    prop_comment_errors,
1102
                )
1103

1104
            assert prop_comment is not None
4✔
1105

1106
            prop_blocks.append(prop_comment)
4✔
1107

1108
        prop_blocks.append(
4✔
1109
            Stripped(
1110
                f"""\
1111
private {prop_type} {prop_name};"""
1112
            )
1113
        )
1114

1115
        blocks.append(Stripped("\n".join(prop_blocks)))
4✔
1116

1117
    # endregion
1118

1119
    # region Methods
1120

1121
    # region Constructor
1122

1123
    if cls.constructor.is_implementation_specific:
4✔
1124
        implementation_key = specific_implementations.ImplementationKey(
×
1125
            f"Types/{cls.name}/{cls.name}.java"
1126
        )
1127
        implementation = spec_impls.get(implementation_key, None)
×
1128

1129
        if implementation is None:
×
1130
            errors.append(
×
1131
                Error(
1132
                    cls.parsed.node,
1133
                    f"The implementation of the implementation-specific constructor "
1134
                    f"is missing: {implementation_key}",
1135
                )
1136
            )
1137
        else:
1138
            blocks.append(implementation)
×
1139
    else:
1140
        mandatory_constructor_block, error = _generate_mandatory_constructor(cls=cls)
4✔
1141

1142
        if error is not None:
4✔
1143
            errors.append(error)
×
1144
        else:
1145
            if mandatory_constructor_block != "":
4✔
1146
                assert mandatory_constructor_block is not None
4✔
1147
                blocks.append(mandatory_constructor_block)
4✔
1148

1149
        full_constructor_block, error = _generate_full_constructor(cls=cls)
4✔
1150

1151
        if error is not None:
4✔
1152
            errors.append(error)
×
1153
        else:
1154
            if full_constructor_block != "":
4✔
1155
                assert full_constructor_block is not None
4✔
1156
                blocks.append(full_constructor_block)
4✔
1157

1158
    # endregion
1159

1160
    # region Getters and setters
1161

1162
    for prop in cls.properties:
4✔
1163
        type_anno = intermediate.beneath_optional(prop.type_annotation)
4✔
1164

1165
        prop_type = java_common.generate_type(type_annotation=prop.type_annotation)
4✔
1166

1167
        arg_type = java_common.generate_type(type_annotation=type_anno)
4✔
1168

1169
        prop_name = java_naming.property_name(prop.name)
4✔
1170

1171
        getter_name = java_naming.getter_name(prop.name)
4✔
1172
        setter_name = java_naming.setter_name(prop.name)
4✔
1173

1174
        get_set_blocks = []  # type: List[Stripped]
4✔
1175

1176
        if isinstance(prop.type_annotation, intermediate.OptionalTypeAnnotation):
4✔
1177
            get_set_blocks.append(
4✔
1178
                Stripped(
1179
                    f"""\
1180
@Override
1181
public {prop_type} {getter_name}() {{
1182
{I}return Optional.ofNullable({prop_name});
1183
}}"""
1184
                )
1185
            )
1186

1187
            get_set_blocks.append(
4✔
1188
                Stripped(
1189
                    f"""\
1190
@Override
1191
public void {setter_name}({arg_type} {prop_name}) {{
1192
{I}this.{prop_name} = {prop_name};
1193
}}"""
1194
                )
1195
            )
1196
        else:
1197
            get_set_blocks.append(
4✔
1198
                Stripped(
1199
                    f"""\
1200
@Override
1201
public {prop_type} {getter_name}() {{
1202
{I}return {prop_name};
1203
}}"""
1204
                )
1205
            )
1206

1207
            get_set_blocks.append(
4✔
1208
                Stripped(
1209
                    f"""\
1210
@Override
1211
public void {setter_name}({arg_type} {prop_name}) {{
1212
{I}this.{prop_name} = Objects.requireNonNull(
1213
{II}{prop_name},
1214
{II}"Argument \\"{prop_name}\\" must be non-null.");
1215
}}"""
1216
                )
1217
            )
1218

1219
        blocks.append(Stripped("\n\n".join(get_set_blocks)))
4✔
1220

1221
    # endregion
1222

1223
    # region OverXOrEmpty getter
1224

1225
    cls_name = java_naming.class_name(cls.name)
4✔
1226

1227
    for prop in cls.properties:
4✔
1228
        if isinstance(
4✔
1229
            prop.type_annotation, intermediate.OptionalTypeAnnotation
1230
        ) and isinstance(prop.type_annotation.value, intermediate.ListTypeAnnotation):
1231
            prop_name = java_naming.property_name(prop.name)
4✔
1232
            method_name = f"over{java_naming.class_name(prop.name)}OrEmpty"
4✔
1233
            getter_name = java_naming.getter_name(prop.name)
4✔
1234
            items_type = java_common.generate_type(prop.type_annotation.value.items)
4✔
1235

1236
            blocks.append(
4✔
1237
                Stripped(
1238
                    f"""\
1239
/**
1240
 * Iterate over {{@link {cls_name}#{prop_name}}}, if set,
1241
 * and otherwise return an empty iterator.
1242
 */
1243
public Iterable<{items_type}> {method_name}() {{
1244
{I}return {getter_name}().orElseGet(Collections::emptyList);
1245
}}"""
1246
                )
1247
            )
1248

1249
    # endregion
1250

1251
    # region Public methods
1252

1253
    for method in cls.methods:
4✔
1254
        if isinstance(method, intermediate.ImplementationSpecificMethod):
4✔
1255
            implementation_key = specific_implementations.ImplementationKey(
4✔
1256
                f"Types/{method.specified_for.name}/{method.name}.java"
1257
            )
1258

1259
            implementation = spec_impls.get(implementation_key, None)
4✔
1260

1261
            if implementation is None:
4✔
1262
                errors.append(
×
1263
                    Error(
1264
                        method.parsed.node,
1265
                        f"The implementation is missing for "
1266
                        f"the implementation-specific method: {implementation_key}",
1267
                    )
1268
                )
1269
                continue
×
1270

1271
            blocks.append(implementation)
4✔
1272
        else:
1273
            errors.append(
×
1274
                Error(
1275
                    cls.parsed.node,
1276
                    "At the moment, we do not transpile the method body and "
1277
                    "its contracts. We want to finish the meta-model for the V3 and "
1278
                    "fix de/serialization before taking on this rather hard task.",
1279
                )
1280
            )
1281

1282
    visit_name = java_naming.method_name(Identifier(f"visit_{cls.name}"))
4✔
1283

1284
    blocks.append(_generate_descend_method(cls=cls, descendable=descendable))
4✔
1285
    blocks.append(_generate_descend_once_method(cls=cls, descendable=descendable))
4✔
1286
    blocks.append(
4✔
1287
        Stripped(
1288
            f"""\
1289
/**
1290
 * Accept the {{@code visitor}} to visit this instance for double dispatch.
1291
 **/
1292
@Override
1293
public void accept(IVisitor visitor) {{
1294
{I}visitor.{visit_name}(this);
1295
}}"""
1296
        )
1297
    )
1298

1299
    blocks.append(
4✔
1300
        Stripped(
1301
            f"""\
1302
/**
1303
 * Accept the {{@code visitor}} to visit this instance for double dispatch
1304
 * with the {{@code context}}.
1305
 **/
1306
@Override
1307
public <ContextT> void accept(
1308
{II}IVisitorWithContext<ContextT> visitor,
1309
{II}ContextT context) {{
1310
{I}visitor.{visit_name}(this, context);
1311
}}"""
1312
        )
1313
    )
1314

1315
    transform_name = java_naming.method_name(Identifier(f"transform_{cls.name}"))
4✔
1316

1317
    blocks.append(
4✔
1318
        Stripped(
1319
            f"""\
1320
/**
1321
 * Accept the {{@code transformer}} to visit this instance for double dispatch.
1322
 **/
1323
@Override
1324
public <T> T transform(ITransformer<T> transformer) {{
1325
{I}return transformer.{transform_name}(this);
1326
}}"""
1327
        )
1328
    )
1329

1330
    blocks.append(
4✔
1331
        Stripped(
1332
            f"""\
1333
/**
1334
 * Accept the {{@code transformer}} to visit this instance for double dispatch
1335
 * with the {{@code context}}.
1336
 **/
1337
@Override
1338
public <ContextT, T> T transform(
1339
{II}ITransformerWithContext<ContextT, T> transformer,
1340
{II}ContextT context) {{
1341
{I}return transformer.{transform_name}(this, context);
1342
}}"""
1343
        )
1344
    )
1345

1346
    # endregion
1347

1348
    # endregion
1349

1350
    # region Inner classes
1351

1352
    if descendable:
4✔
1353
        blocks.append(_generate_descend_iterable(cls=cls, recursive=False))
4✔
1354

1355
        blocks.append(_generate_descend_iterable(cls=cls, recursive=True))
4✔
1356

1357
    # endregion
1358

1359
    if len(errors) > 0:
4✔
1360
        return None, Error(
×
1361
            cls.parsed.node,
1362
            f"Failed to generate the code for the class {cls.name}",
1363
            errors,
1364
        )
1365

1366
    interface_name = java_naming.interface_name(cls.name)
4✔
1367

1368
    name = java_naming.class_name(cls.name)
4✔
1369

1370
    writer = io.StringIO()
4✔
1371

1372
    if cls.description is not None:
4✔
1373
        comment, comment_errors = java_description.generate_comment_for_our_type(
4✔
1374
            description=cls.description,
1375
            context=java_description.Context(package=package, cls_or_enum=cls),
1376
        )
1377
        if comment_errors is not None:
4✔
1378
            return None, Error(
×
1379
                cls.description.parsed.node,
1380
                "Failed to generate the comment description",
1381
                comment_errors,
1382
            )
1383

1384
        assert comment is not None
4✔
1385

1386
        writer.write(comment)
4✔
1387
        writer.write("\n")
4✔
1388

1389
    writer.write(
4✔
1390
        f"""\
1391
public class {name} implements {interface_name} {{\n"""
1392
    )
1393

1394
    for i, block in enumerate(blocks):
4✔
1395
        if i > 0:
4✔
1396
            writer.write("\n\n")
4✔
1397

1398
        writer.write(textwrap.indent(block, I))
4✔
1399

1400
    writer.write("\n}")
4✔
1401

1402
    return Stripped(writer.getvalue()), None
4✔
1403

1404

1405
@ensure(lambda result: (result[0] is None) ^ (result[1] is None))
4✔
1406
def _generate_enum(
4✔
1407
    enum: intermediate.Enumeration, package: java_common.PackageIdentifier
1408
) -> Tuple[Optional[Stripped], Optional[Error]]:
1409
    """
1410
    Generate Java code for the enumeration `enum`.
1411

1412
    The ``package`` defines the root Java package.
1413
    """
1414
    writer = io.StringIO()
4✔
1415

1416
    if enum.description is not None:
4✔
1417
        comment, comment_errors = java_description.generate_comment_for_our_type(
4✔
1418
            description=enum.description,
1419
            context=java_description.Context(package=package, cls_or_enum=enum),
1420
        )
1421
        if comment_errors:
4✔
1422
            return None, Error(
×
1423
                enum.description.parsed.node,
1424
                "Failed to generate the documentation comment",
1425
                comment_errors,
1426
            )
1427

1428
        assert comment is not None
4✔
1429

1430
        writer.write(comment)
4✔
1431
        writer.write("\n")
4✔
1432

1433
    name = java_naming.enum_name(enum.name)
4✔
1434
    if len(enum.literals) == 0:
4✔
1435
        writer.write(
×
1436
            f"""\
1437
public enum {name} {{\n}}"""
1438
        )
1439
        return Stripped(writer.getvalue()), None
×
1440

1441
    writer.write(
4✔
1442
        f"""\
1443
public enum {name} {{\n"""
1444
    )
1445
    for i, literal in enumerate(enum.literals):
4✔
1446
        if i > 0:
4✔
1447
            writer.write(",\n")
4✔
1448

1449
        if literal.description:
4✔
1450
            (
4✔
1451
                literal_comment,
1452
                literal_comment_errors,
1453
            ) = java_description.generate_comment_for_enumeration_literal(
1454
                description=literal.description,
1455
                context=java_description.Context(package=package, cls_or_enum=enum),
1456
            )
1457

1458
            if literal_comment_errors:
4✔
1459
                return None, Error(
×
1460
                    literal.description.parsed.node,
1461
                    f"Failed to generate the comment "
1462
                    f"for the enumeration literal {literal.name!r}",
1463
                    literal_comment_errors,
1464
                )
1465

1466
            assert literal_comment is not None
4✔
1467

1468
            writer.write(textwrap.indent(literal_comment, I))
4✔
1469
            writer.write("\n")
4✔
1470

1471
        writer.write(
4✔
1472
            textwrap.indent(
1473
                f"{java_naming.enum_literal_name(literal.name)}",
1474
                I,
1475
            )
1476
        )
1477

1478
    writer.write("\n}")
4✔
1479

1480
    return Stripped(writer.getvalue()), None
4✔
1481

1482

1483
@require(lambda file_name: file_name.endswith(".java"))
4✔
1484
def _generate_java_file(
4✔
1485
    file_name: Stripped,
1486
    imports: Optional[Stripped],
1487
    code: Stripped,
1488
    package: java_common.PackageIdentifier,
1489
) -> java_common.JavaFile:
1490
    writer = io.StringIO()
4✔
1491

1492
    writer.write(
4✔
1493
        f"""\
1494
{java_common.WARNING}
1495

1496
package {package};\n\n"""
1497
    )
1498

1499
    if imports is not None and len(imports) > 0:
4✔
1500
        writer.write(f"{imports}")
4✔
1501
        writer.write("\n\n")
4✔
1502

1503
    writer.write(
4✔
1504
        f"""\
1505
{code}
1506

1507
{java_common.WARNING}
1508
"""
1509
    )
1510

1511
    file_content = writer.getvalue()
4✔
1512

1513
    return java_common.JavaFile(file_name, file_content)
4✔
1514

1515

1516
def _generate_iclass(
4✔
1517
    package: java_common.PackageIdentifier,
1518
) -> java_common.JavaFile:
1519
    structure_name = Stripped("IClass")
4✔
1520
    file_name = java_common.interface_package_path(structure_name)
4✔
1521
    file_content = f"""\
4✔
1522
{java_common.WARNING}
1523

1524
package {package}.types.model;
1525

1526
import {package}.visitation.ITransformer;
1527
import {package}.visitation.ITransformerWithContext;
1528
import {package}.visitation.IVisitor;
1529
import {package}.visitation.IVisitorWithContext;
1530
import java.lang.Iterable;
1531

1532
/**
1533
 * Represent a general class of an AAS model.
1534
 */
1535
public interface IClass {{
1536
{I}/**
1537
{I} * Iterate over all the class instances referenced from this instance
1538
{I} * without further recursion.
1539
{I} */
1540
{I}Iterable<IClass> descendOnce();
1541

1542
{I}/**
1543
{I} * Iterate recursively over all the class instances referenced from this instance.
1544
{I} */
1545
{I}Iterable<IClass> descend();
1546

1547
{I}/**
1548
{I} * Accept the {{@code visitor}} to visit this instance
1549
{I} * for double dispatch.
1550
{I} */
1551
{I}void accept(IVisitor visitor);
1552

1553
{I}/**
1554
{I} * Accept the visitor to visit this instance for double dispatch
1555
{I} * with the {{@code context}}.
1556
{I} */
1557
{I}<ContextT> void accept(
1558
{III}IVisitorWithContext<ContextT> visitor,
1559
{III}ContextT context);
1560

1561
{I}/**
1562
{I} * Accept the {{@code transformer}} to transform this instance
1563
{I} * for double dispatch.
1564
{I} */
1565
{I}<T> T transform(ITransformer<T> transformer);
1566

1567
{I}/**
1568
{I} * Accept the {{@code transformer}} to visit this instance
1569
{I} * for double dispatch with the {{@code context}}.
1570
{I} */
1571
{I}<ContextT, T> T transform(
1572
{III}ITransformerWithContext<ContextT, T> transformer,
1573
{III}ContextT context);
1574
}}
1575

1576
{java_common.WARNING}\n"""
1577

1578
    return java_common.JavaFile(file_name, file_content)
4✔
1579

1580

1581
# fmt: off
1582
@ensure(lambda result: (result[0] is not None) ^ (result[1] is not None))
4✔
1583
# fmt: on
1584
def _generate_structure(
4✔
1585
    our_type: intermediate.OurType,
1586
    package: java_common.PackageIdentifier,
1587
    spec_impls: specific_implementations.SpecificImplementations,
1588
) -> Tuple[Optional[List[java_common.JavaFile]], Optional[Error]]:
1589
    """
1590
    Generate the Java code for a single structure.
1591

1592
    The ``package`` defines the root Java package.
1593
    """
1594
    assert isinstance(
4✔
1595
        our_type,
1596
        (
1597
            intermediate.Enumeration,
1598
            intermediate.AbstractClass,
1599
            intermediate.ConcreteClass,
1600
        ),
1601
    )
1602

1603
    files = []  # List[java_common.JavaFile]
4✔
1604

1605
    if isinstance(our_type, intermediate.Class) and our_type.is_implementation_specific:
4✔
1606
        implementation_key = specific_implementations.ImplementationKey(
×
1607
            f"Types/{our_type.name}.java"
1608
        )
1609

1610
        imports = _generate_imports_for_class(cls=our_type, package=package)
×
1611

1612
        code = spec_impls.get(implementation_key, None)
×
1613
        if code is None:
×
1614
            return None, Error(
×
1615
                our_type.parsed.node,
1616
                f"The implementation is missing "
1617
                f"for the implementation-specific class: {implementation_key}",
1618
            )
1619

1620
        structure_name = java_naming.class_name(our_type.name)
×
1621

1622
        package_name = java_common.PackageIdentifier(
×
1623
            f"{package}.types.{java_common.CLASS_PKG}"
1624
        )
1625

1626
        java_source = _generate_java_file(
×
1627
            file_name=structure_name, imports=imports, code=code, package=package_name
1628
        )
1629

1630
        files.append(java_source)
×
1631
    else:
1632
        if isinstance(
4✔
1633
            our_type, (intermediate.AbstractClass, intermediate.ConcreteClass)
1634
        ):
1635
            imports = _generate_imports_for_interface(cls=our_type, package=package)
4✔
1636

1637
            code, error = _generate_interface(cls=our_type, package=package)
4✔
1638
            if error is not None:
4✔
1639
                return None, Error(
×
1640
                    our_type.parsed.node,
1641
                    f"Failed to generate the interface code for "
1642
                    f"the class {our_type.name!r}",
1643
                    [error],
1644
                )
1645

1646
            assert code is not None
4✔
1647

1648
            structure_name = java_naming.interface_name(our_type.name)
4✔
1649

1650
            file_name = java_common.interface_package_path(structure_name)
4✔
1651

1652
            package_name = java_common.PackageIdentifier(
4✔
1653
                f"{package}.types.{java_common.INTERFACE_PKG}"
1654
            )
1655

1656
            java_source = _generate_java_file(
4✔
1657
                file_name=file_name, imports=imports, code=code, package=package_name
1658
            )
1659

1660
            files.append(java_source)
4✔
1661

1662
            if isinstance(our_type, intermediate.ConcreteClass):
4✔
1663
                imports = _generate_imports_for_class(cls=our_type, package=package)
4✔
1664

1665
                code, error = _generate_class(
4✔
1666
                    cls=our_type, spec_impls=spec_impls, package=package
1667
                )
1668
                if error is not None:
4✔
1669
                    return None, Error(
×
1670
                        our_type.parsed.node,
1671
                        f"Failed to generate the class code for "
1672
                        f"the class {our_type.name!r}",
1673
                        [error],
1674
                    )
1675

1676
                assert code is not None
4✔
1677

1678
                structure_name = java_naming.class_name(our_type.name)
4✔
1679

1680
                file_name = java_common.class_package_path(structure_name)
4✔
1681

1682
                package_name = java_common.PackageIdentifier(
4✔
1683
                    f"{package}.types.{java_common.CLASS_PKG}"
1684
                )
1685

1686
                java_source = _generate_java_file(
4✔
1687
                    file_name=file_name,
1688
                    imports=imports,
1689
                    code=code,
1690
                    package=package_name,
1691
                )
1692

1693
                files.append(java_source)
4✔
1694
        elif isinstance(our_type, intermediate.Enumeration):
4✔
1695
            code, error = _generate_enum(enum=our_type, package=package)
4✔
1696
            if error is not None:
4✔
1697
                return None, Error(
×
1698
                    our_type.parsed.node,
1699
                    f"Failed to generate the code for "
1700
                    f"the enumeration {our_type.name!r}",
1701
                    [error],
1702
                )
1703

1704
            assert code is not None
4✔
1705
            structure_name = java_naming.enum_name(our_type.name)
4✔
1706

1707
            file_name = java_common.enum_package_path(structure_name)
4✔
1708

1709
            package_name = java_common.PackageIdentifier(
4✔
1710
                f"{package}.types.{java_common.ENUM_PKG}"
1711
            )
1712

1713
            java_source = _generate_java_file(
4✔
1714
                file_name=file_name, imports=None, code=code, package=package_name
1715
            )
1716

1717
            files.append(java_source)
4✔
1718
        else:
1719
            assert_never(our_type)
×
1720

1721
    return files, None
4✔
1722

1723

1724
# fmt: off
1725
@ensure(lambda result: (result[0] is not None) ^ (result[1] is not None))
4✔
1726
# fmt: on
1727
def generate(
4✔
1728
    symbol_table: VerifiedIntermediateSymbolTable,
1729
    package: java_common.PackageIdentifier,
1730
    spec_impls: specific_implementations.SpecificImplementations,
1731
) -> Tuple[Optional[List[java_common.JavaFile]], Optional[List[Error]]]:
1732
    """
1733
    Generate the Java code of the structures based on the symbol table.
1734

1735
    The ``package`` defines the root Java package.
1736
    """
1737

1738
    files = []  # type: List[java_common.JavaFile]
4✔
1739
    errors = []  # type: List[Error]
4✔
1740

1741
    files.append(_generate_iclass(package))
4✔
1742

1743
    for our_type in symbol_table.our_types:
4✔
1744
        if not isinstance(
4✔
1745
            our_type,
1746
            (
1747
                intermediate.Enumeration,
1748
                intermediate.AbstractClass,
1749
                intermediate.ConcreteClass,
1750
            ),
1751
        ):
1752
            continue
4✔
1753

1754
        new_files, error = _generate_structure(our_type, package, spec_impls)
4✔
1755

1756
        if new_files is not None:
4✔
1757
            files.extend(new_files)
4✔
1758
        elif error is not None:
×
1759
            errors.append(error)
×
1760

1761
    if len(errors) > 0:
4✔
1762
        return None, errors
×
1763

1764
    return files, None
4✔
1765

1766

1767
# endregion
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc