Skip to content

Commit 56d66d4

Browse files
committed
Generate enums as interface + enum + class for unknown values
The new generated enum is represented as an interface with two nested classes: - enum class Known with the list of known enumeration values - class Unknown which represents any not-known value The enums `enum` would be generated as: ```java interface Enum extends IKaitaiEnum { public static class Unknown extends IKaitaiEnum.Unknown implements Enum { private Unknown(long id) { super(id); } } public enum Known implements Enum { Variant1(1), Variant2(2), Variant(3); private final long id; static HashMap<Long, Enum> variants = new HashMap<>(3); static { for (final Known e : Known.values()) { variants.put(e.id, e); } } private Known(long id) { this.id = id; } @OverRide public long id() { return this.id; } } public static Enum byId(final long id) { return Known.variants.computeIfAbsent(id, _id -> new Unknown(id)); } } ``` Unfortunately, it is not possible to generate enum what will implement nested interface due to cyclic reference. If that would be possible, we would be protected against name clashes. In the current implementation the names Known and Unknown can clash with enum name itself
1 parent 2191070 commit 56d66d4

File tree

2 files changed

+52
-15
lines changed

2 files changed

+52
-15
lines changed

shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -524,21 +524,35 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
524524
// doing this workaround for enums.
525525

526526
val onType = typeProvider._currentSwitchType.get
527-
val isNullable = onType match {
527+
val isEnum = onType match {
528528
case _: EnumType => true
529529
case _ => false
530530
}
531531

532-
if (isNullable) {
533-
val nameSwitchStr = expression(NAME_SWITCH_ON)
532+
if (isEnum) {
533+
val javaEnumName = kaitaiType2JavaType(onType)
534+
// Open scope for "on" isolation
534535
out.puts("{")
535536
out.inc
536-
out.puts(s"${kaitaiType2JavaType(onType)} $nameSwitchStr = ${expression(on)};")
537-
out.puts(s"if ($nameSwitchStr != null) {")
537+
out.puts(s"final $javaEnumName on = ${expression(on)};")
538+
out.puts(s"if (on instanceof $javaEnumName.Known) {")
539+
out.inc
540+
out.puts(s"switch (($javaEnumName.Known)on) {")
538541
out.inc
539542

540-
super.switchCasesRender(id, on, cases, normalCaseProc, elseCaseProc)
543+
cases.foreach { case (condition, result) =>
544+
condition match {
545+
case SwitchType.ELSE_CONST =>
546+
// skip for now
547+
case _ =>
548+
switchCaseStart(condition)
549+
normalCaseProc(result)
550+
switchCaseEnd()
551+
}
552+
}
541553

554+
out.dec
555+
out.puts("} // switch")
542556
out.dec
543557
cases.get(SwitchType.ELSE_CONST) match {
544558
case Some(result) =>
@@ -551,6 +565,7 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
551565
out.puts("}")
552566
}
553567

568+
// Close "on" isolation scope
554569
out.dec
555570
out.puts("}")
556571
} else {
@@ -690,7 +705,18 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
690705
val enumClass = type2class(enumName)
691706

692707
out.puts
693-
out.puts(s"public enum $enumClass {")
708+
out.puts(s"public interface $enumClass extends IKaitaiEnum {")
709+
out.inc
710+
out.puts(s"public static class Unknown extends IKaitaiEnum.Unknown implements $enumClass {")
711+
out.inc
712+
out.puts("Unknown(long id) { super(id); }")
713+
out.puts
714+
out.puts("@Override");
715+
out.puts(s"public String toString() { return \"${enumClass}(\" + this.id + \")\"; }");
716+
out.dec
717+
out.puts("}")
718+
out.puts
719+
out.puts(s"public enum Known implements $enumClass {")
694720
out.inc
695721

696722
if (enumColl.size > 1) {
@@ -705,23 +731,34 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
705731

706732
out.puts
707733
out.puts("private final long id;")
708-
out.puts(s"$enumClass(long id) { this.id = id; }")
709-
out.puts("public long id() { return id; }")
710-
out.puts(s"private static final Map<Long, $enumClass> byId = new HashMap<Long, $enumClass>(${enumColl.size});")
734+
out.puts(s"static final HashMap<Long, $enumClass> variants = new HashMap<>(${enumColl.size});")
711735
out.puts("static {")
712736
out.inc
713-
out.puts(s"for ($enumClass e : $enumClass.values())")
737+
out.puts(s"for (Known e : Known.values()) {")
714738
out.inc
715-
out.puts(s"byId.put(e.id(), e);")
739+
out.puts(s"variants.put(e.id, e);")
740+
out.dec
741+
out.puts("}")
742+
out.dec
743+
out.puts("}")
744+
out.puts
745+
out.puts("private Known(long id) { this.id = id; }")
746+
out.puts
747+
out.puts("@Override")
748+
out.puts("public long id() { return id; }")
716749
out.dec
750+
out.puts("}")
751+
out.puts
752+
out.puts(s"public static $enumClass byId(final long id) {")
753+
out.inc
754+
out.puts("return Known.variants.computeIfAbsent(id, _id -> new Unknown(id));")
717755
out.dec
718756
out.puts("}")
719-
out.puts(s"public static $enumClass byId(long id) { return byId.get(id); }")
720757
out.dec
721758
out.puts("}")
722759

723-
importList.add("java.util.Map")
724760
importList.add("java.util.HashMap")
761+
importList.add("io.kaitai.struct.IKaitaiEnum")
725762
}
726763

727764
override def debugClassSequence(seq: List[AttrSpec]) = {

shared/src/main/scala/io/kaitai/struct/translators/JavaTranslator.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class JavaTranslator(provider: TypeProvider, importList: ImportList) extends Bas
6767
s"${JavaCompiler.publicMemberName(id)}()"
6868

6969
override def doEnumByLabel(enumSpec: EnumSpec, label: String): String =
70-
s"${enumClass(enumSpec.name)}.${Utils.upperUnderscoreCase(label)}"
70+
s"${enumClass(enumSpec.name)}.Known.${Utils.upperUnderscoreCase(label)}"
7171
override def doEnumById(enumSpec: EnumSpec, id: String): String =
7272
s"${enumClass(enumSpec.name)}.byId($id)"
7373

0 commit comments

Comments
 (0)