diff --git a/shared/src/main/scala/io/kaitai/struct/ClassCompiler.scala b/shared/src/main/scala/io/kaitai/struct/ClassCompiler.scala index f7aa87f56..c73269eb4 100644 --- a/shared/src/main/scala/io/kaitai/struct/ClassCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/ClassCompiler.scala @@ -47,6 +47,7 @@ class ClassCompiler( if (!lang.innerDocstrings) compileClassDoc(curClass) + lang.classAnnotation(curClass) lang.classHeader(curClass.name) if (lang.innerDocstrings) compileClassDoc(curClass) @@ -92,14 +93,17 @@ class ClassCompiler( // Attributes declarations and readers val allAttrs: List[MemberSpec] = - curClass.seq ++ - curClass.params ++ List( AttrSpec(List(), RootIdentifier, CalcUserType(topClassName, None)), AttrSpec(List(), ParentIdentifier, curClass.parentType) ) ++ ExtraAttrs.forClassSpec(curClass, lang) + compileIndexedAttrDeclarations(curClass.seq) + compileIndexedAttrDeclarations(curClass.params) compileAttrDeclarations(allAttrs) + + compileIndexedAttrReaders(curClass.seq) + compileIndexedAttrReaders(curClass.params) compileAttrReaders(allAttrs) curClass.toStringExpr.foreach(expr => lang.classToString(expr)) @@ -192,12 +196,7 @@ class ClassCompiler( */ def compileAttrDeclarations(attrs: List[MemberSpec]): Unit = { attrs.foreach { (attr) => - val isNullable = if (lang.switchBytesOnlyAsRaw) { - attr.isNullableSwitchRaw - } else { - attr.isNullable - } - lang.attributeDeclaration(attr.id, attr.dataTypeComposite, isNullable) + lang.attributeDeclaration(attr.id, attr.dataTypeComposite, isNullable(attr)) } } @@ -211,12 +210,45 @@ class ClassCompiler( // FIXME: Python should have some form of attribute docs too if (!attr.doc.isEmpty && !lang.innerDocstrings) lang.attributeDoc(attr.id, attr.doc) - val isNullable = if (lang.switchBytesOnlyAsRaw) { - attr.isNullableSwitchRaw - } else { - attr.isNullable - } - lang.attributeReader(attr.id, attr.dataTypeComposite, isNullable) + lang.attributeReader(attr.id, attr.dataTypeComposite, isNullable(attr)) + } + } + + /** + * Iterates over a given list of attributes and generates attribute + * declarations for each of them, additionally annotate each field. + * + * @param attrs attribute list to traverse + */ + def compileIndexedAttrDeclarations(attrs: List[MemberSpec]): Unit = { + attrs.zipWithIndex.foreach { case (attr, index) => + lang.attributeAnnotation(index, attr) + lang.attributeDeclaration(attr.id, attr.dataTypeComposite, isNullable(attr)) + } + } + + /** + * Iterates over a given list of attributes and generates attribute + * readers (AKA getters) for each of them, additionally annotate each + * getter. + * + * @param attrs attribute list to traverse + */ + def compileIndexedAttrReaders(attrs: List[MemberSpec]): Unit = { + attrs.zipWithIndex.foreach { case (attr, index) => + // FIXME: Python should have some form of attribute docs too + if (!attr.doc.isEmpty && !lang.innerDocstrings) + lang.attributeDoc(attr.id, attr.doc) + lang.attributeAnnotation(index, attr) + lang.attributeReader(attr.id, attr.dataTypeComposite, isNullable(attr)) + } + } + + def isNullable(attr: MemberSpec): Boolean = { + if (lang.switchBytesOnlyAsRaw) { + attr.isNullableSwitchRaw + } else { + attr.isNullable } } @@ -326,6 +358,7 @@ class ClassCompiler( if (!lang.innerDocstrings) compileInstanceDoc(instName, instSpec) + lang.instanceAnnotation(instName, instSpec, true) lang.instanceHeader(className, instName, dataType, instSpec.isNullable) if (lang.innerDocstrings) compileInstanceDoc(instName, instSpec) @@ -354,6 +387,7 @@ class ClassCompiler( } else { instSpec.isNullable } + lang.instanceAnnotation(instName, instSpec, false) lang.instanceDeclaration(instName, instSpec.dataTypeComposite, isNullable) } 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 bc9c1699d..9e01c7820 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala @@ -65,6 +65,21 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) out.puts } + override def classAnnotation(spec: ClassSpec): Unit = { + if (!spec.isTopLevel) return; + + importList.add("io.kaitai.struct.annotations.Generated") + + out.puts("@Generated(") + out.inc + out.puts(s"""id = "${spec.name.last}",""") + out.puts(s"""version = "${KSVersion.current}",""") + out.puts(s"""posInfo = ${config.readStoresPos},""") + out.puts(s"""autoRead = ${config.autoRead}""") + out.dec + out.puts(")") + } + override def classHeader(name: String): Unit = { val staticStr = if (out.indentLevel > 0) { "static " @@ -197,6 +212,32 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) override def readFooter(): Unit = universalFooter + override def attributeAnnotation(index: Int, attr: MemberSpec): Unit = { + attr match { + case param: ParamDefSpec => { + importList.add("io.kaitai.struct.annotations.Parameter") + + out.puts(s"""@Parameter(index = ${index}, id = "${param.id.humanReadable}")""") + } + case field: AttrSpec => { + field.id match { + case NamedIdentifier(name) => { + importList.add("io.kaitai.struct.annotations.SeqItem") + + out.puts(s"""@SeqItem(index = ${index}, id = "${name}")""") + } + case NumberedIdentifier(_) => { + importList.add("io.kaitai.struct.annotations.SeqItem") + + out.puts(s"""@SeqItem(index = ${index})""") + } + case _ => + } + } + case _ => + } + } + override def attributeDeclaration(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit = { out.puts(s"private ${kaitaiType2JavaType(attrType, isNullable)} ${idToStr(attrName)};") } @@ -656,6 +697,12 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) // + override def instanceAnnotation(instName: InstanceIdentifier, spec: InstanceSpec, getter: Boolean): Unit = { + importList.add("io.kaitai.struct.annotations.Instance") + + out.puts(s"""@Instance(id = "${instName.name}")""") + } + override def instanceDeclaration(instName: InstanceIdentifier, attrType: DataType, isNullable: Boolean): Unit = { out.puts(s"private ${kaitaiType2JavaTypeBoxed(attrType)} ${idToStr(instName)};") } diff --git a/shared/src/main/scala/io/kaitai/struct/languages/components/LanguageCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/components/LanguageCompiler.scala index c2af6439e..7906a2b74 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/components/LanguageCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/components/LanguageCompiler.scala @@ -84,6 +84,14 @@ abstract class LanguageCompiler( def opaqueClassDeclaration(classSpec: ClassSpec): Unit = {} def classDoc(name: List[String], doc: DocSpec): Unit = {} + /** + * Generates additional meta-information for attribute, such as annotations in Java + * or attributes in C#. That annotations appears between documentation and main body + * of class. + * + * @param spec Specification of type + */ + def classAnnotation(spec: ClassSpec): Unit = {} def classHeader(name: List[String]): Unit def classFooter(name: List[String]): Unit def classForwardDeclaration(name: List[String]): Unit = {} @@ -114,6 +122,15 @@ abstract class LanguageCompiler( */ def readFooter(): Unit + /** + * Generates additional meta-information for an attribute or type parameter, + * such as annotations in Java or attributes in C#. + * + * @param index Sequential index of `seq` attribute/parameter in type, started from 0. + * Attributes and parameters have independent indexes + * @param attr Specification of attribute + */ + def attributeAnnotation(index: Int, attr: MemberSpec): Unit = {} def attributeDeclaration(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit def attributeReader(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit def attributeDoc(id: Identifier, doc: DocSpec): Unit = {} @@ -151,6 +168,15 @@ abstract class LanguageCompiler( def popPos(io: String): Unit def alignToByte(io: String): Unit + /** + * Generates additional meta-information for instance, such as annotations in Java + * or attributes in C#. + * + * @param instName Name of instance + * @param spec Specification of instance + * @param getter If `true`, method called for annotate value getter, otherwise for annotate storage variable + */ + def instanceAnnotation(instName: InstanceIdentifier, spec: InstanceSpec, getter: Boolean): Unit = {} def instanceDeclHeader(className: List[String]): Unit = {} def instanceClear(instName: InstanceIdentifier): Unit = {} def instanceSetCalculated(instName: InstanceIdentifier): Unit = {}