Skip to content

Commit

Permalink
Generate enums as interface + enum + class for unknown values
Browse files Browse the repository at this point in the history
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 enum `enum` would be generated as:

```java
// interface to abstract known and unknown values
interface IEnum extends IKaitaiEnum {
    // Storage for unknown values
    public static class Unknown extends IKaitaiEnum.Unknown implements IEnum {
        Unknown(long id) { super(id); }
        @OverRide
        public String toString() { return "Enum(" + this.id + ")"; }
    }
}
// Storage for known values
class Enum implements IEnum {
    VARIANT_1(1),
    VARIANT_2(2),
    VARIANT_3(3);

    private final long id;

    private static HashMap<Long, IEnum> variants = new HashMap<>(3);
    static {
        for (final Enum e : values()) {
            variants.put(e.id, e);
        }
    }

    public static IEnum byId(final long id) {
        return variants.computeIfAbsent(id, _id -> new IEnum.Unknown(id));
    }

    private Enum(long id) { this.id = id; }
    @OverRide
    public long id() { return this.id; }
}
```
  • Loading branch information
Mingun committed Aug 19, 2024
1 parent 3c855b3 commit 0bd7cd3
Showing 1 changed file with 57 additions and 16 deletions.
73 changes: 57 additions & 16 deletions shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -535,21 +535,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 I$javaEnumName on = ${expression(on)};")
out.puts(s"if (on instanceof $javaEnumName) {")
out.inc
out.puts(s"switch (($javaEnumName)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) =>
Expand All @@ -562,6 +576,7 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
out.puts("}")
}

// Close "on" isolation scope
out.dec
out.puts("}")
} else {
Expand Down Expand Up @@ -700,8 +715,25 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
override def enumDeclaration(curClass: String, enumName: String, enumColl: Seq[(Long, String)]): Unit = {
val enumClass = type2class(enumName)

// Enums are always contained in some other class so we can generate two public items

// public interface I<enum> extends IKaitaiEnum { ... }
out.puts
out.puts(s"public enum $enumClass {")
out.puts(s"public interface I$enumClass extends IKaitaiEnum {")
out.inc
out.puts(s"public static class Unknown extends IKaitaiEnum.Unknown implements I$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("}") // close class
out.dec
out.puts("}") // close interface

// public enum <enum> implements I<enum> { ... }
out.puts(s"public enum $enumClass implements I$enumClass {")
out.inc

if (enumColl.size > 1) {
Expand All @@ -716,23 +748,32 @@ 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<Long, $enumClass> byId = new HashMap<Long, $enumClass>(${enumColl.size});")
out.puts(s"private static final HashMap<Long, I$enumClass> variants = new HashMap<>(${enumColl.size});")
out.puts("static {")
out.inc
out.puts(s"for ($enumClass e : $enumClass.values())")
out.puts(s"for ($enumClass e : values()) {")
out.inc
out.puts(s"byId.put(e.id(), e);")
out.puts(s"variants.put(e.id, e);")
out.dec
out.puts("}")// for
out.dec
out.puts("}")
out.puts(s"public static $enumClass byId(long id) { return byId.get(id); }")
out.puts("}")// static initializer
out.puts
out.puts(s"public static I$enumClass byId(final long id) {")
out.inc
out.puts(s"return variants.computeIfAbsent(id, _id -> new I$enumClass.Unknown(id));")
out.dec
out.puts("}")
out.puts("}")// byId(...)
out.puts
out.puts(s"private $enumClass(long id) { this.id = id; }")
out.puts
out.puts("@Override")
out.puts("public long id() { return id; }")
out.dec
out.puts("}")// enum

importList.add("java.util.Map")
importList.add("java.util.HashMap")
importList.add("io.kaitai.struct.IKaitaiEnum")
}

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

0 comments on commit 0bd7cd3

Please sign in to comment.