Skip to content

Commit 3e592df

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 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 + ")"; } @OverRide public int hashCode() { // Analogues to this code, but without boxing // return Objects.hash("Enum", this.id); final int result = 31 + "Enum".hashCode(); return 31 * result + Long.hashCode(this.id); } @OverRide public boolean equals(Object other) { return other instanceof Enum && this.id == ((Enum)other).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; } } ```
1 parent 52dfec7 commit 3e592df

File tree

1 file changed

+99
-40
lines changed

1 file changed

+99
-40
lines changed

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

Lines changed: 99 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -535,37 +535,48 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
535535
// doing this workaround for enums.
536536

537537
val onType = typeProvider._currentSwitchType.get
538-
val isNullable = onType match {
539-
case _: EnumType => true
540-
case _ => false
541-
}
542-
543-
if (isNullable) {
544-
val nameSwitchStr = expression(NAME_SWITCH_ON)
545-
out.puts("{")
546-
out.inc
547-
out.puts(s"${kaitaiType2JavaType(onType)} $nameSwitchStr = ${expression(on)};")
548-
out.puts(s"if ($nameSwitchStr != null) {")
549-
out.inc
538+
onType match {
539+
case EnumType(name, owner, _) => {
540+
val enumName = types2class(owner :+ name)
541+
// Open scope for "on" isolation
542+
out.puts("{")
543+
out.inc
544+
out.puts(s"final ${enum2iface(name, owner)} on = ${expression(on)};")
545+
out.puts(s"if (on instanceof $enumName) {")
546+
out.inc
547+
out.puts(s"switch (($enumName)on) {")
548+
out.inc
549+
550+
cases.foreach { case (condition, result) =>
551+
condition match {
552+
case SwitchType.ELSE_CONST =>
553+
// skip for now
554+
case _ =>
555+
switchCaseStart(condition)
556+
normalCaseProc(result)
557+
switchCaseEnd()
558+
}
559+
}
550560

551-
super.switchCasesRender(id, on, cases, normalCaseProc, elseCaseProc)
561+
out.dec
562+
out.puts("} // switch")
563+
out.dec
564+
cases.get(SwitchType.ELSE_CONST) match {
565+
case Some(result) =>
566+
out.puts("} else {")
567+
out.inc
568+
elseCaseProc(result)
569+
out.dec
570+
out.puts("}")
571+
case None =>
572+
out.puts("}")
573+
}
552574

553-
out.dec
554-
cases.get(SwitchType.ELSE_CONST) match {
555-
case Some(result) =>
556-
out.puts("} else {")
557-
out.inc
558-
elseCaseProc(result)
559-
out.dec
560-
out.puts("}")
561-
case None =>
562-
out.puts("}")
575+
// Close "on" isolation scope
576+
out.dec
577+
out.puts("}")
563578
}
564-
565-
out.dec
566-
out.puts("}")
567-
} else {
568-
super.switchCasesRender(id, on, cases, normalCaseProc, elseCaseProc)
579+
case _ => super.switchCasesRender(id, on, cases, normalCaseProc, elseCaseProc)
569580
}
570581
}
571582

@@ -699,9 +710,44 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
699710

700711
override def enumDeclaration(curClass: String, enumName: String, enumColl: Seq[(Long, String)]): Unit = {
701712
val enumClass = type2class(enumName)
713+
val enumIface = enum2iface(enumName)
714+
715+
// Enums are always contained in some other class so we can generate two public items
702716

717+
// public interface I<enum> extends IKaitaiEnum { ... }
718+
out.puts
719+
out.puts(s"public interface $enumIface extends IKaitaiEnum {")
720+
out.inc
721+
out.puts(s"public static final class Unknown extends IKaitaiEnum.Unknown implements $enumIface {")
722+
out.inc
723+
out.puts("Unknown(long id) { super(id); }")
724+
out.puts
725+
out.puts("@Override");
726+
out.puts(s"public String toString() { return \"${enumClass}(\" + this.id + \")\"; }");
703727
out.puts
704-
out.puts(s"public enum $enumClass {")
728+
out.puts("@Override");
729+
out.puts(s"public int hashCode() {")
730+
out.inc
731+
// Use the hashCode implementation that is generated by Eclipse and VSCode Java plugin.
732+
// It uses prime 31 and this is how Arrays.hashCode() is implemented
733+
out.puts(s"final int result = 31 + \"${enumClass}\".hashCode();")
734+
out.puts("return 31 * result + Long.hashCode(this.id);")
735+
out.dec
736+
out.puts("}");
737+
out.puts
738+
out.puts("@Override");
739+
out.puts(s"public boolean equals(Object other) {");
740+
out.inc
741+
out.puts("return other instanceof Unknown && this.id == ((Unknown)other).id;")
742+
out.dec
743+
out.puts("}");
744+
out.dec
745+
out.puts("}") // close class
746+
out.dec
747+
out.puts("}") // close interface
748+
749+
// public enum <enum> implements I<enum> { ... }
750+
out.puts(s"public enum $enumClass implements $enumIface {")
705751
out.inc
706752

707753
if (enumColl.size > 1) {
@@ -716,23 +762,32 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
716762

717763
out.puts
718764
out.puts("private final long id;")
719-
out.puts(s"$enumClass(long id) { this.id = id; }")
720-
out.puts("public long id() { return id; }")
721-
out.puts(s"private static final Map<Long, $enumClass> byId = new HashMap<Long, $enumClass>(${enumColl.size});")
765+
out.puts(s"private static final HashMap<Long, $enumIface> variants = new HashMap<>(${enumColl.size});")
722766
out.puts("static {")
723767
out.inc
724-
out.puts(s"for ($enumClass e : $enumClass.values())")
768+
out.puts(s"for ($enumClass e : values()) {")
725769
out.inc
726-
out.puts(s"byId.put(e.id(), e);")
770+
out.puts(s"variants.put(e.id, e);")
727771
out.dec
772+
out.puts("}")// for
728773
out.dec
729-
out.puts("}")
730-
out.puts(s"public static $enumClass byId(long id) { return byId.get(id); }")
774+
out.puts("}")// static initializer
775+
out.puts
776+
out.puts(s"public static $enumIface byId(final long id) {")
777+
out.inc
778+
out.puts(s"return variants.computeIfAbsent(id, _id -> new $enumIface.Unknown(id));")
731779
out.dec
732-
out.puts("}")
780+
out.puts("}")// byId(...)
781+
out.puts
782+
out.puts(s"private $enumClass(long id) { this.id = id; }")
783+
out.puts
784+
out.puts("@Override")
785+
out.puts("public long id() { return id; }")
786+
out.dec
787+
out.puts("}")// enum
733788

734-
importList.add("java.util.Map")
735789
importList.add("java.util.HashMap")
790+
importList.add("io.kaitai.struct.IKaitaiEnum")
736791
}
737792

738793
override def debugClassSequence(seq: List[AttrSpec]) = {
@@ -874,7 +929,7 @@ object JavaCompiler extends LanguageCompilerStatic
874929
case KaitaiStructType | CalcKaitaiStructType(_) => kstructName
875930

876931
case t: UserType => types2class(t.name)
877-
case EnumType(name, owner, _) => types2class(owner :+ name)
932+
case EnumType(name, owner, _) => enum2iface(name, owner)
878933

879934
case _: ArrayType => kaitaiType2JavaTypeBoxed(attrType, importList)
880935

@@ -918,7 +973,7 @@ object JavaCompiler extends LanguageCompilerStatic
918973
case KaitaiStructType | CalcKaitaiStructType(_) => kstructName
919974

920975
case t: UserType => types2class(t.name)
921-
case EnumType(name, owner, _) => types2class(owner :+ name)
976+
case EnumType(name, owner, _) => enum2iface(name, owner)
922977

923978
case at: ArrayType => {
924979
importList.add("java.util.ArrayList")
@@ -931,6 +986,10 @@ object JavaCompiler extends LanguageCompilerStatic
931986

932987
def types2class(names: Iterable[String]) = names.map(x => type2class(x)).mkString(".")
933988

989+
def enum2iface(name: String) = s"I${type2class(name)}"
990+
def enum2iface(name: String, owner: List[String]): String =
991+
(owner.map(type2class) :+ enum2iface(name)).mkString(".")
992+
934993
override def kstreamName: String = "KaitaiStream"
935994
override def kstructName: String = "KaitaiStruct"
936995
}

0 commit comments

Comments
 (0)