From 240999c58eb4b95cd07810c205bc2896d4d6e455 Mon Sep 17 00:00:00 2001 From: Mingun Date: Mon, 25 Mar 2024 23:21:31 +0500 Subject: [PATCH] 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 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 --- .../struct/languages/JavaCompiler.scala | 65 +++++++++++++++---- .../struct/translators/JavaTranslator.scala | 2 +- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala index f2a22fa34..c15c626af 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala @@ -524,21 +524,35 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) // doing this workaround for enums. val onType = typeProvider._currentSwitchType.get - val isNullable = onType match { + val isEnum = onType match { case _: EnumType => true case _ => false } - if (isNullable) { - val nameSwitchStr = expression(NAME_SWITCH_ON) + if (isEnum) { + val javaEnumName = kaitaiType2JavaType(onType) + // Open scope for "on" isolation out.puts("{") out.inc - out.puts(s"${kaitaiType2JavaType(onType)} $nameSwitchStr = ${expression(on)};") - out.puts(s"if ($nameSwitchStr != null) {") + out.puts(s"final $javaEnumName on = ${expression(on)};") + out.puts(s"if (on instanceof $javaEnumName.Known) {") + out.inc + out.puts(s"switch (($javaEnumName.Known)on) {") out.inc - super.switchCasesRender(id, on, cases, normalCaseProc, elseCaseProc) + cases.foreach { case (condition, result) => + condition match { + case SwitchType.ELSE_CONST => + // skip for now + case _ => + switchCaseStart(condition) + normalCaseProc(result) + switchCaseEnd() + } + } + out.dec + out.puts("} // switch") out.dec cases.get(SwitchType.ELSE_CONST) match { case Some(result) => @@ -551,6 +565,7 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) out.puts("}") } + // Close "on" isolation scope out.dec out.puts("}") } else { @@ -690,7 +705,18 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) val enumClass = type2class(enumName) out.puts - out.puts(s"public enum $enumClass {") + out.puts(s"public interface $enumClass extends IKaitaiEnum {") + out.inc + out.puts(s"public static class Unknown extends IKaitaiEnum.Unknown implements $enumClass {") + out.inc + out.puts("Unknown(long id) { super(id); }") + out.puts + out.puts("@Override"); + out.puts(s"public String toString() { return \"${enumClass}(\" + this.id + \")\"; }"); + out.dec + out.puts("}") + out.puts + out.puts(s"public enum Known implements $enumClass {") out.inc if (enumColl.size > 1) { @@ -705,23 +731,34 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) out.puts out.puts("private final long id;") - out.puts(s"$enumClass(long id) { this.id = id; }") - out.puts("public long id() { return id; }") - out.puts(s"private static final Map byId = new HashMap(${enumColl.size});") + out.puts(s"static final HashMap variants = new HashMap<>(${enumColl.size});") out.puts("static {") out.inc - out.puts(s"for ($enumClass e : $enumClass.values())") + out.puts(s"for (Known e : Known.values()) {") out.inc - out.puts(s"byId.put(e.id(), e);") + out.puts(s"variants.put(e.id, e);") + out.dec + out.puts("}") + out.dec + out.puts("}") + out.puts + out.puts("private Known(long id) { this.id = id; }") + out.puts + out.puts("@Override") + out.puts("public long id() { return id; }") out.dec + out.puts("}") + out.puts + out.puts(s"public static $enumClass byId(final long id) {") + out.inc + out.puts("return Known.variants.computeIfAbsent(id, _id -> new Unknown(id));") out.dec out.puts("}") - out.puts(s"public static $enumClass byId(long id) { return byId.get(id); }") out.dec out.puts("}") - importList.add("java.util.Map") importList.add("java.util.HashMap") + importList.add("io.kaitai.struct.IKaitaiEnum") } override def debugClassSequence(seq: List[AttrSpec]) = { diff --git a/shared/src/main/scala/io/kaitai/struct/translators/JavaTranslator.scala b/shared/src/main/scala/io/kaitai/struct/translators/JavaTranslator.scala index 3eec364db..9268b6b41 100644 --- a/shared/src/main/scala/io/kaitai/struct/translators/JavaTranslator.scala +++ b/shared/src/main/scala/io/kaitai/struct/translators/JavaTranslator.scala @@ -67,7 +67,7 @@ class JavaTranslator(provider: TypeProvider, importList: ImportList) extends Bas s"${JavaCompiler.publicMemberName(id)}()" override def doEnumByLabel(enumSpec: EnumSpec, label: String): String = - s"${enumClass(enumSpec.name)}.${Utils.upperUnderscoreCase(label)}" + s"${enumClass(enumSpec.name)}.Known.${Utils.upperUnderscoreCase(label)}" override def doEnumById(enumSpec: EnumSpec, id: String): String = s"${enumClass(enumSpec.name)}.byId($id)"