From 2a1d51fbaf34204bf9e4e07eeac28ec484285e5a Mon Sep 17 00:00:00 2001 From: Marc-Christian Schulze Date: Sat, 30 Nov 2024 22:37:33 +0100 Subject: [PATCH 1/8] Implemented default values for arrays --- .../org/structs4java/Structs4JavaDsl.xtext | 34 ++++++++++-- .../Structs4JavaDslFormatter.xtend | 1 - .../generator/StructGenerator.xtend | 53 +++++++++++++------ .../generator/Structs4JavaDslGenerator.xtend | 2 +- .../Structs4JavaDslJvmModelInferrer.xtend | 6 --- .../Structs4JavaDslScopeProvider.xtend | 2 - .../validation/Structs4JavaDslValidator.xtend | 10 ++-- .../src/main/structs/default-values.structs | 9 ++++ .../example/tests/DefaultValueTest.java | 24 +++++++++ .../structs4java/StructsBatchCompiler.java | 22 +++++++- 10 files changed, 125 insertions(+), 38 deletions(-) diff --git a/org.structs4java.parent/org.structs4java/src/org/structs4java/Structs4JavaDsl.xtext b/org.structs4java.parent/org.structs4java/src/org/structs4java/Structs4JavaDsl.xtext index ba49779..dc1adf8 100644 --- a/org.structs4java.parent/org.structs4java/src/org/structs4java/Structs4JavaDsl.xtext +++ b/org.structs4java.parent/org.structs4java/src/org/structs4java/Structs4JavaDsl.xtext @@ -91,8 +91,7 @@ ComplexTypeMember: IntegerMember: (comments += SL_COMMENT)* - typename=INTEGER_TYPE name=ID - (array=ArrayDimension)? + typename=INTEGER_TYPE name=ID ( ('sizeof' '(' sizeof=[Member] ')')? & ('sizeof' '(' sizeofThis?='this' ')')? & @@ -101,15 +100,33 @@ IntegerMember: ('padding' '(' padding=LONG ( ',' 'using' '=' using=LONG )? ')')? ('=' defaultValue=LONG)? ';' + | + (comments += SL_COMMENT)* + typename=INTEGER_TYPE name=ID + array=ArrayDimension + ( + ('sizeof' '(' sizeof=[Member] ')')? & + ('sizeof' '(' sizeofThis?='this' ')')? & + ('countof' '(' countof=[Member] ')')? + ) + ('padding' '(' padding=LONG ( ',' 'using' '=' using=LONG )? ')')? + ('=' defaultValues=IntInitializerList)? + ';' ; FloatMember: (comments += SL_COMMENT)* - typename=FLOAT_TYPE name=ID - (array=ArrayDimension)? + typename=FLOAT_TYPE name=ID ('padding' '(' padding=LONG ( ',' 'using' '=' using=LONG )? ')')? ('=' defaultValue=FLOAT)? ';' + | + (comments += SL_COMMENT)* + typename=FLOAT_TYPE name=ID + array=ArrayDimension + ('padding' '(' padding=LONG ( ',' 'using' '=' using=LONG )? ')')? + ('=' defaultValues=FloatInitializerList)? + ';' ; StringMember: @@ -128,6 +145,15 @@ ArrayDimension: {ArrayDimension} '[' (dimension=LONG)? ']' ; +IntInitializerList: + {IntInitializerList} + '{' items+=LONG (',' items+=LONG)* '}' +; + +FloatInitializerList: + '{' items+=FLOAT (',' items+=FLOAT)* '}' +; + QualifiedName: ID ('.' ID)* ; diff --git a/org.structs4java.parent/org.structs4java/src/org/structs4java/formatting2/Structs4JavaDslFormatter.xtend b/org.structs4java.parent/org.structs4java/src/org/structs4java/formatting2/Structs4JavaDslFormatter.xtend index a0369f0..e8b3439 100644 --- a/org.structs4java.parent/org.structs4java/src/org/structs4java/formatting2/Structs4JavaDslFormatter.xtend +++ b/org.structs4java.parent/org.structs4java/src/org/structs4java/formatting2/Structs4JavaDslFormatter.xtend @@ -13,7 +13,6 @@ import org.structs4java.structs4JavaDsl.Member import org.structs4java.structs4JavaDsl.StructDeclaration import org.structs4java.structs4JavaDsl.Item import org.structs4java.structs4JavaDsl.Structs4JavaDslPackage -import org.eclipse.xtext.formatting2.regionaccess.ILineRegion import org.eclipse.xtext.EcoreUtil2 class Structs4JavaDslFormatter extends AbstractFormatter2 { diff --git a/org.structs4java.parent/org.structs4java/src/org/structs4java/generator/StructGenerator.xtend b/org.structs4java.parent/org.structs4java/src/org/structs4java/generator/StructGenerator.xtend index c50d5c6..b07dabc 100644 --- a/org.structs4java.parent/org.structs4java/src/org/structs4java/generator/StructGenerator.xtend +++ b/org.structs4java.parent/org.structs4java/src/org/structs4java/generator/StructGenerator.xtend @@ -15,6 +15,8 @@ import org.structs4java.structs4JavaDsl.EnumDeclaration import org.structs4java.structs4JavaDsl.BitfieldMember import org.structs4java.structs4JavaDsl.BitfieldEntry +import java.util.stream.Collectors + /** * Generates code from your model files on save. * @@ -558,7 +560,7 @@ class StructGenerator { FloatMember: m.name StringMember: m.name ComplexTypeMember: m.name - BitfieldMember: attributeName(m as BitfieldMember) + BitfieldMember: attributeName(m) default: "" } } @@ -599,7 +601,7 @@ class StructGenerator { IntegerMember: readerMethodForIntegerMember(m) FloatMember: readerMethodForFloatMember(m) StringMember: readerMethodForStringMember(m) - BitfieldMember: readerMethodForBitfieldMember(m as BitfieldMember) + BitfieldMember: readerMethodForBitfieldMember(m) } } @@ -608,12 +610,12 @@ class StructGenerator { } def readerMethodForArrayMember(Member m) ''' - private static java.util.ArrayList<«m.nativeTypeName().native2JavaType().box()»> «m.readerMethodName()»(java.nio.ByteBuffer buf, boolean partialRead«IF findMemberDefiningCountOf(m) != null», long countof«ENDIF») throws java.io.IOException { + private static java.util.ArrayList<«m.nativeTypeName().native2JavaType().box()»> «m.readerMethodName()»(java.nio.ByteBuffer buf, boolean partialRead«IF findMemberDefiningCountOf(m) !== null», long countof«ENDIF») throws java.io.IOException { java.util.ArrayList<«m.nativeTypeName().native2JavaType().box()»> lst = new java.util.ArrayList<«m.nativeTypeName().native2JavaType().box()»>(); try { «IF dimensionOf(m) == 0» - «IF findMemberDefiningCountOf(m) != null» + «IF findMemberDefiningCountOf(m) !== null» for(long i = 0; i < countof; ++i) { lst.add(«readerMethodName(m)»«arrayPostfix(m)»(buf, partialRead)); } @@ -827,7 +829,7 @@ class StructGenerator { } «ENDIF» - «IF m.getNullTerminated() == null» + «IF m.getNullTerminated() === null» return new String(tmp, 0, (int)sizeof, "«encodingOf(m)»"); «ELSE» int terminatingZeros = "\0".getBytes("«encodingOf(m)»").length; @@ -941,7 +943,7 @@ class StructGenerator { «ENDFOR» ''' - def hasSizeOfOrCountOfAttribute(Member m) { + def hasSizeOfOrCountOfAttribute(Member m) { return m.hasSizeOfAttribute() || m.hasCountOfAttribute() } @@ -1365,7 +1367,7 @@ class StructGenerator { «IF findMemberDefiningSizeOf(m) === null» buf.put("\0".getBytes("«encodingOf(m)»")); «ELSE» - «IF m.getNullTerminated() != null» + «IF m.getNullTerminated() !== null» buf.put("\0".getBytes("«encodingOf(m)»")); «ENDIF» «ENDIF» @@ -1411,7 +1413,7 @@ class StructGenerator { def fields(StructDeclaration struct) ''' «FOR m : struct.members» «IF m instanceof BitfieldMember» - «field(m as BitfieldMember)» + «field(m)» «ELSE» «field(m)» «ENDIF» @@ -1421,7 +1423,7 @@ class StructGenerator { def getters(StructDeclaration struct) ''' «FOR m : struct.members» «IF m instanceof BitfieldMember» - «getter(m as BitfieldMember)» + «getter(m)» «ELSE» «getter(m)» «ENDIF» @@ -1431,7 +1433,7 @@ class StructGenerator { def setters(StructDeclaration struct) ''' «FOR m : struct.nonTransientMembers()» «IF m instanceof BitfieldMember» - «setter(m as BitfieldMember)» + «setter(m)» «ELSE» «setter(m)» «ENDIF» @@ -1509,7 +1511,7 @@ class StructGenerator { switch (m) { ComplexTypeMember: { if(m.type instanceof EnumDeclaration) { - if(m.defaultValue == null) { + if(m.defaultValue === null) { return "null" } return attributeJavaType(m) + "." + m.defaultValue.name @@ -1525,8 +1527,17 @@ class StructGenerator { def defaultConstruct(Member m) { if(m.isArray()) { - if(doesAttributeJavaTypeMapToByteBuffer(m)) { - return "java.nio.ByteBuffer.wrap(new byte[]{})" + if(m instanceof IntegerMember) { + if(doesAttributeJavaTypeMapToByteBuffer(m)) { + + if(m.defaultValues === null) { + return "java.nio.ByteBuffer.wrap(new byte[]{})" + } + + val initList = m.defaultValues.items.join(", ") + + return "java.nio.ByteBuffer.wrap(new byte[]{" + initList + "})" + } } if(m instanceof StringMember) { @@ -1535,6 +1546,17 @@ class StructGenerator { val nativeType = nativeTypeName(m) val javaType = native2JavaType(nativeType) + + if(m instanceof IntegerMember) { + if(m.defaultValues !== null) { + return "new java.util.ArrayList<" + box(javaType) + ">(java.util.Arrays.asList(" + m.defaultValues.items.stream().map(v | v + "L").collect(Collectors.joining(", ")) + "))" + } + } else if(m instanceof FloatMember) { + if(m.defaultValues !== null) { + return "new java.util.ArrayList<" + box(javaType) + ">(java.util.Arrays.asList(" + m.defaultValues.items.join(', ') + "))" + } + } + return "new java.util.ArrayList<" + box(javaType) + ">()" } @@ -1552,7 +1574,7 @@ class StructGenerator { if(m instanceof ComplexTypeMember) { if(m.type instanceof EnumDeclaration) { - if(m.defaultValue == null) { + if(m.defaultValue === null) { return "null" } return attributeJavaType(m) + "." + m.defaultValue.name @@ -1582,9 +1604,6 @@ class StructGenerator { } def doesAttributeJavaTypeMapToByteBuffer(Member m) { - val nativeType = nativeTypeName(m) - val javaType = native2JavaType(nativeType) - if (!isArray(m)) { return false } diff --git a/org.structs4java.parent/org.structs4java/src/org/structs4java/generator/Structs4JavaDslGenerator.xtend b/org.structs4java.parent/org.structs4java/src/org/structs4java/generator/Structs4JavaDslGenerator.xtend index 3de63ef..0ba1f96 100644 --- a/org.structs4java.parent/org.structs4java/src/org/structs4java/generator/Structs4JavaDslGenerator.xtend +++ b/org.structs4java.parent/org.structs4java/src/org/structs4java/generator/Structs4JavaDslGenerator.xtend @@ -34,7 +34,7 @@ class Structs4JavaDslGenerator extends AbstractGenerator { def javaType(ComplexTypeDeclaration type) { val pkg = type.eContainer as StructsFile - if (pkg != null && !pkg.name.empty) { + if (pkg !== null && !pkg.name.empty) { return pkg.name + "." + type.name } return type.name diff --git a/org.structs4java.parent/org.structs4java/src/org/structs4java/jvmmodel/Structs4JavaDslJvmModelInferrer.xtend b/org.structs4java.parent/org.structs4java/src/org/structs4java/jvmmodel/Structs4JavaDslJvmModelInferrer.xtend index 2bae136..5be17ce 100644 --- a/org.structs4java.parent/org.structs4java/src/org/structs4java/jvmmodel/Structs4JavaDslJvmModelInferrer.xtend +++ b/org.structs4java.parent/org.structs4java/src/org/structs4java/jvmmodel/Structs4JavaDslJvmModelInferrer.xtend @@ -6,7 +6,6 @@ package org.structs4java.jvmmodel import com.google.inject.Inject import org.eclipse.xtext.xbase.jvmmodel.AbstractModelInferrer import org.eclipse.xtext.xbase.jvmmodel.IJvmDeclaredTypeAcceptor -import org.eclipse.xtext.xbase.jvmmodel.JvmTypesBuilder /** *

Infers a JVM model from the source model.

@@ -16,11 +15,6 @@ import org.eclipse.xtext.xbase.jvmmodel.JvmTypesBuilder */ class Structs4JavaDslJvmModelInferrer extends AbstractModelInferrer { - /** - * convenience API to build and initialize JVM types and their members. - */ - @Inject extension JvmTypesBuilder - /** * The dispatch method {@code infer} is called for each instance of the * given element's type that is contained in a resource. diff --git a/org.structs4java.parent/org.structs4java/src/org/structs4java/scoping/Structs4JavaDslScopeProvider.xtend b/org.structs4java.parent/org.structs4java/src/org/structs4java/scoping/Structs4JavaDslScopeProvider.xtend index 2842ba8..bd18509 100644 --- a/org.structs4java.parent/org.structs4java/src/org/structs4java/scoping/Structs4JavaDslScopeProvider.xtend +++ b/org.structs4java.parent/org.structs4java/src/org/structs4java/scoping/Structs4JavaDslScopeProvider.xtend @@ -5,10 +5,8 @@ package org.structs4java.scoping import org.eclipse.emf.ecore.EObject import org.eclipse.emf.ecore.EReference -import org.eclipse.xtext.scoping.impl.ImportScope import org.structs4java.structs4JavaDsl.Structs4JavaDslPackage import org.structs4java.structs4JavaDsl.StructDeclaration -import org.eclipse.xtext.scoping.Scopes /** * This class contains custom scoping description. diff --git a/org.structs4java.parent/org.structs4java/src/org/structs4java/validation/Structs4JavaDslValidator.xtend b/org.structs4java.parent/org.structs4java/src/org/structs4java/validation/Structs4JavaDslValidator.xtend index a40b7c7..575771a 100644 --- a/org.structs4java.parent/org.structs4java/src/org/structs4java/validation/Structs4JavaDslValidator.xtend +++ b/org.structs4java.parent/org.structs4java/src/org/structs4java/validation/Structs4JavaDslValidator.xtend @@ -19,7 +19,7 @@ class Structs4JavaDslValidator extends AbstractStructs4JavaDslValidator { @Check def checkArrayDimension(Member m) { - if(m.array == null) { + if(m.array === null) { return; } @@ -40,11 +40,11 @@ class Structs4JavaDslValidator extends AbstractStructs4JavaDslValidator { } } else { - if(getCountOfFor(m) != null) { + if(getCountOfFor(m) !== null) { error('Either array dimension or countof must be specified but not both!', m, Structs4JavaDslPackage.Literals.MEMBER__ARRAY) } - if(getSizeOfFor(m) != null) { + if(getSizeOfFor(m) !== null) { error('Either array dimension or sizeof must be specified but not both!', m, Structs4JavaDslPackage.Literals.MEMBER__ARRAY) } } @@ -52,7 +52,7 @@ class Structs4JavaDslValidator extends AbstractStructs4JavaDslValidator { @Check def checkArrayDimensionIsBeforeDynamicElement(Member m) { - if(m.array == null) { + if(m.array === null) { return; } @@ -87,7 +87,7 @@ class Structs4JavaDslValidator extends AbstractStructs4JavaDslValidator { @Check def disable64BitSizeOfAndCountOf(Member m) { if(m instanceof IntegerMember) { - if(m.sizeof != null || m.countof != null || m.sizeofThis) { + if(m.sizeof !== null || m.countof !== null || m.sizeofThis) { if(m.typename.equals("uint64_t") || m.typename.equals("int64_t")) { warning('64 bit types for sizeof/countof members are not fully supported!\nThey throw a RuntimeException in case the value is larger than 2^63!', m, Structs4JavaDslPackage.Literals.INTEGER_MEMBER__TYPENAME) } diff --git a/structs4java-maven-plugin-test/src/main/structs/default-values.structs b/structs4java-maven-plugin-test/src/main/structs/default-values.structs index d9519cf..8be8b28 100644 --- a/structs4java-maven-plugin-test/src/main/structs/default-values.structs +++ b/structs4java-maven-plugin-test/src/main/structs/default-values.structs @@ -16,6 +16,15 @@ struct StructWithStringDefaultValue { char str[] = "default"; } +struct StructWithArrayDefaultValue { + uint8_t int8[3] = { 1, 0x20, 3 }; + uint16_t int16[3] = { 4, 5, 6 }; + uint32_t int32[3] = { 7, 8, 9 }; + uint64_t int64[3] = { 0x10, 0x11, 0x12 }; + float f[3] = { 1.0, 2.0, 3.0 }; + double d[3] = { 4.0, 5.0, 6.0 }; +} + enum Int8Enum : uint8_t { A = 0x01, B = 0x02, diff --git a/structs4java-maven-plugin-test/src/test/java/org/structs4java/example/tests/DefaultValueTest.java b/structs4java-maven-plugin-test/src/test/java/org/structs4java/example/tests/DefaultValueTest.java index c49c93a..3948747 100644 --- a/structs4java-maven-plugin-test/src/test/java/org/structs4java/example/tests/DefaultValueTest.java +++ b/structs4java-maven-plugin-test/src/test/java/org/structs4java/example/tests/DefaultValueTest.java @@ -3,6 +3,9 @@ import org.junit.Test; import org.structs4java.example.tests.defaultvalues.*; +import java.nio.ByteBuffer; +import java.util.List; + import static org.junit.Assert.assertEquals; public class DefaultValueTest { @@ -33,4 +36,25 @@ public void testStructWithEnumDefaultValue() { StructWithEnum struct = new StructWithEnum(); assertEquals(Int8Enum.B, struct.getInt8Enum()); } + + @Test + public void testStructWithArrayDefaultValue() { + StructWithArrayDefaultValue struct = new StructWithArrayDefaultValue(); + assertBufferEquals(struct.getInt8(), 1, 0x20, 3); + assertListEquals(struct.getInt16(), 4, 5, 6); + assertListEquals(struct.getInt32(), 7, 8, 9); + assertListEquals(struct.getInt64(), 0x10, 0x11, 0x12); + } + + private void assertBufferEquals(ByteBuffer buffer, int... expected) { + for(int value : expected) { + assertEquals(value, buffer.get() & 0xFF); + } + } + + private void assertListEquals(List items, int... expected) { + for(int i = 0; i < expected.length; ++i) { + assertEquals(expected[i], items.get(i).longValue()); + } + } } diff --git a/structs4java-maven-plugin/src/main/java/org/structs4java/StructsBatchCompiler.java b/structs4java-maven-plugin/src/main/java/org/structs4java/StructsBatchCompiler.java index 1218ff1..fb91683 100644 --- a/structs4java-maven-plugin/src/main/java/org/structs4java/StructsBatchCompiler.java +++ b/structs4java-maven-plugin/src/main/java/org/structs4java/StructsBatchCompiler.java @@ -453,8 +453,12 @@ private StringBuilder createIssueMessage(Issue issue) { issueBuilder.append(filePath).append(":").append(issue.getLineNumber()).append(" ").append(issue.getMessage()).append("\n"); String sourceLineWithIssue = readLineFromResource(filePath, issue.getLineNumber()); - issueBuilder.append(sourceLineWithIssue).append("\n"); - issueBuilder.append(com.google.common.base.Strings.repeat(" ", issue.getColumn() - 1)).append("^"); + int numTabsBeforeColStart = countTabsTillColumnStart(sourceLineWithIssue, issue.getColumn()); + // 1 \t replaced by 4 spaces = 3 additional spaces / tab + int spacesIntroducedOnTopByReplacingTabsWithSpaces = 3 * numTabsBeforeColStart; + + issueBuilder.append(replaceTabsWith4Spaces(sourceLineWithIssue)).append("\n"); + issueBuilder.append(com.google.common.base.Strings.repeat(" ", issue.getColumn() - 1 + spacesIntroducedOnTopByReplacingTabsWithSpaces)).append("^"); int colLength = issue.getColumnEnd() - issue.getColumn(); if(colLength > 1) { issueBuilder.append(com.google.common.base.Strings.repeat("-", colLength - 1)); @@ -462,6 +466,20 @@ private StringBuilder createIssueMessage(Issue issue) { return issueBuilder; } + private int countTabsTillColumnStart(String str, int colStart) { + int count = 0; + for(int i = 0; i < colStart; ++i) { + if(str.charAt(i) == '\t') { + count++; + } + } + return count; + } + + private String replaceTabsWith4Spaces(String str) { + return str.replaceAll("\t", " "); + } + private String readLineFromResource(String filePath, int line) { JavaIoFileSystemAccess fileSystemAccess = javaIoFileSystemAccessProvider.get(); // if you don't set the output path before using you end up with below exception From f7d7ff9166d771276b3e367c74bf3738d732f466 Mon Sep 17 00:00:00 2001 From: Marc-Christian Schulze Date: Sat, 30 Nov 2024 22:48:59 +0100 Subject: [PATCH 2/8] Fixed some compiler warnings --- .../structs4java/formatting2/Structs4JavaDslFormatter.xtend | 6 +----- .../jvmmodel/Structs4JavaDslJvmModelInferrer.xtend | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/org.structs4java.parent/org.structs4java/src/org/structs4java/formatting2/Structs4JavaDslFormatter.xtend b/org.structs4java.parent/org.structs4java/src/org/structs4java/formatting2/Structs4JavaDslFormatter.xtend index e8b3439..65df5a7 100644 --- a/org.structs4java.parent/org.structs4java/src/org/structs4java/formatting2/Structs4JavaDslFormatter.xtend +++ b/org.structs4java.parent/org.structs4java/src/org/structs4java/formatting2/Structs4JavaDslFormatter.xtend @@ -3,10 +3,8 @@ */ package org.structs4java.formatting2 -import com.google.inject.Inject import org.eclipse.xtext.formatting2.AbstractFormatter2 import org.eclipse.xtext.formatting2.IFormattableDocument -import org.structs4java.services.Structs4JavaDslGrammarAccess import org.structs4java.structs4JavaDsl.EnumDeclaration import org.structs4java.structs4JavaDsl.Import import org.structs4java.structs4JavaDsl.Member @@ -17,8 +15,6 @@ import org.eclipse.xtext.EcoreUtil2 class Structs4JavaDslFormatter extends AbstractFormatter2 { - @Inject extension Structs4JavaDslGrammarAccess - def dispatch void format(org.structs4java.structs4JavaDsl.StructsFile structsFile, extension IFormattableDocument document) { // TODO: format HiddenRegions around keywords, attributes, cross references, etc. @@ -50,7 +46,7 @@ class Structs4JavaDslFormatter extends AbstractFormatter2 { } def dispatch void format(Import imp, extension IFormattableDocument document) { - if ((imp.eContainer as org.structs4java.structs4JavaDsl.StructsFile).getImports().last == imp) { + if ((imp.eContainer as org.structs4java.structs4JavaDsl.StructsFile).getImports().lastOrNull == imp) { imp.regionFor.keyword(";").prepend[noSpace].append[newLines = 2] } else { imp.regionFor.keyword(";").prepend[noSpace].append[newLine] diff --git a/org.structs4java.parent/org.structs4java/src/org/structs4java/jvmmodel/Structs4JavaDslJvmModelInferrer.xtend b/org.structs4java.parent/org.structs4java/src/org/structs4java/jvmmodel/Structs4JavaDslJvmModelInferrer.xtend index 5be17ce..06c4c09 100644 --- a/org.structs4java.parent/org.structs4java/src/org/structs4java/jvmmodel/Structs4JavaDslJvmModelInferrer.xtend +++ b/org.structs4java.parent/org.structs4java/src/org/structs4java/jvmmodel/Structs4JavaDslJvmModelInferrer.xtend @@ -3,7 +3,6 @@ */ package org.structs4java.jvmmodel -import com.google.inject.Inject import org.eclipse.xtext.xbase.jvmmodel.AbstractModelInferrer import org.eclipse.xtext.xbase.jvmmodel.IJvmDeclaredTypeAcceptor From 324cc75c26077207f431e05dc3032ce33af1c824 Mon Sep 17 00:00:00 2001 From: Marc-Christian Schulze Date: Sun, 1 Dec 2024 09:28:58 +0100 Subject: [PATCH 3/8] Added additional parser validations to inform user if default values do not fit into arrays --- README.md | 2 +- .../tests/DefaultValuesParsingTest.xtend | 51 +++++++++++++++ .../validation/Structs4JavaDslValidator.xtend | 65 +++++++++++++++++++ 3 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 org.structs4java.parent/org.structs4java.tests/src/org/structs4java/tests/DefaultValuesParsingTest.xtend diff --git a/README.md b/README.md index d3987c9..c5704a5 100644 --- a/README.md +++ b/README.md @@ -166,7 +166,7 @@ Variable-sized structures contain at least one array field which dimension is de ```C++ struct BString { // no getSizeOf() uint32_t length sizeOf(value); - char value[] encoding("UCS-2") null-terminated; + char value[] encoding("UTF-16LE") null-terminated; } ``` In the given example no explicit dimension of field `value` is provided. Instead the field `length` is marked with the `sizeOf` keyword indicating that it's value will provide the size of the overall array `value` in bytes. You can also use the `countOf` keyword to provide the count of elements an array will contain, e.g. diff --git a/org.structs4java.parent/org.structs4java.tests/src/org/structs4java/tests/DefaultValuesParsingTest.xtend b/org.structs4java.parent/org.structs4java.tests/src/org/structs4java/tests/DefaultValuesParsingTest.xtend new file mode 100644 index 0000000..1d56d7a --- /dev/null +++ b/org.structs4java.parent/org.structs4java.tests/src/org/structs4java/tests/DefaultValuesParsingTest.xtend @@ -0,0 +1,51 @@ +package org.structs4java.tests + +import com.google.inject.Inject +import org.eclipse.xtext.testing.InjectWith +import org.eclipse.xtext.testing.XtextRunner +import org.eclipse.xtext.testing.util.ParseHelper +import org.junit.Test +import org.junit.runner.RunWith +import org.structs4java.structs4JavaDsl.IntegerMember +import org.structs4java.structs4JavaDsl.FloatMember + +import static org.junit.Assert.assertEquals + +@RunWith(XtextRunner) +@InjectWith(Structs4JavaDslInjectorProvider) +class DefaultValuesParsingTest { + + @Inject + ParseHelper parseHelper + + @Test + def void structWithValidPrimitivesWithDefaultValues() { + val pkg = parseHelper.parse(''' + struct S { + int8_t a = 1; + uint8_t b = 2; + int16_t c = 3; + uint16_t d = 4; + int32_t e = 5; + uint32_t f = 6; + int64_t g = 7; + uint64_t h = 8; + float i = 1.0; + double j = 2.0; + } + ''') + val struct = pkg.structs.get(0) + + assertEquals(1L, (struct.members.get(0) as IntegerMember).defaultValue) + assertEquals(2L, (struct.members.get(1) as IntegerMember).defaultValue) + assertEquals(3L, (struct.members.get(2) as IntegerMember).defaultValue) + assertEquals(4L, (struct.members.get(3) as IntegerMember).defaultValue) + assertEquals(5L, (struct.members.get(4) as IntegerMember).defaultValue) + assertEquals(6L, (struct.members.get(5) as IntegerMember).defaultValue) + assertEquals(7L, (struct.members.get(6) as IntegerMember).defaultValue) + assertEquals(8L, (struct.members.get(7) as IntegerMember).defaultValue) + assertEquals(1.0f, (struct.members.get(8) as FloatMember).defaultValue, 0.001f) + assertEquals(2.0f, (struct.members.get(9) as FloatMember).defaultValue, 0.001f) + } + +} diff --git a/org.structs4java.parent/org.structs4java/src/org/structs4java/validation/Structs4JavaDslValidator.xtend b/org.structs4java.parent/org.structs4java/src/org/structs4java/validation/Structs4JavaDslValidator.xtend index 575771a..700d00b 100644 --- a/org.structs4java.parent/org.structs4java/src/org/structs4java/validation/Structs4JavaDslValidator.xtend +++ b/org.structs4java.parent/org.structs4java/src/org/structs4java/validation/Structs4JavaDslValidator.xtend @@ -5,6 +5,7 @@ package org.structs4java.validation import org.eclipse.xtext.validation.Check import org.structs4java.structs4JavaDsl.IntegerMember +import org.structs4java.structs4JavaDsl.FloatMember import org.structs4java.structs4JavaDsl.Member import org.structs4java.structs4JavaDsl.StructDeclaration import org.structs4java.structs4JavaDsl.Structs4JavaDslPackage @@ -94,6 +95,70 @@ class Structs4JavaDslValidator extends AbstractStructs4JavaDslValidator { } } } + + @Check + def defaultValueInitializerListOfIntMemberDoesNotExceedArrayDimension(IntegerMember m) { + if(m.array === null) { + return + } + + if(m.array.dimension == 0) { + return + } + + if(m.defaultValues === null) { + return + } + + if(m.array.dimension < m.defaultValues.items.size) { + error("Can't fit default values (size=" + m.defaultValues.items.size + ") into fixed-sized array(size=" + m.array.dimension + ").", m, Structs4JavaDslPackage.Literals.INTEGER_MEMBER__DEFAULT_VALUES) + } + } + + @Check + def defaultValueInitializerListOfIntMemberDoesNotExceedArrayDimension(FloatMember m) { + if(m.array === null) { + return + } + + if(m.array.dimension == 0) { + return + } + + if(m.defaultValues === null) { + return + } + + if(m.array.dimension < m.defaultValues.items.size) { + error("Can't fit default values (size=" + m.defaultValues.items.size + ") into fixed-sized array(size=" + m.array.dimension + ").", m, Structs4JavaDslPackage.Literals.FLOAT_MEMBER__DEFAULT_VALUES) + } + } + + @Check + def defaultValueInitializerListOfIntMemberDoesNotExceedArrayDimension(StringMember m) { + if(m.array === null) { + return + } + + if(m.array.dimension == 0) { + return + } + + if(m.defaultValue === null) { + return + } + + val encoding = if(m.encoding !== null) m.encoding else "UTF-8" + var lenBytes = m.defaultValue.getBytes(encoding).length + + if(m.nullTerminated !== null) { + lenBytes += "\u0000".getBytes(encoding).length + } + + if(m.array.dimension < lenBytes) { + error("Can't fit default value (size=" + lenBytes + ") into fixed-sized array(size=" + m.array.dimension + ").", m, Structs4JavaDslPackage.Literals.STRING_MEMBER__DEFAULT_VALUE) + } + } def getSizeOfFor(Member m) { val struct = m.eContainer as StructDeclaration; From a3cb9a154e08216fb5f9d7ad54b5740304486fd3 Mon Sep 17 00:00:00 2001 From: Marc-Christian Schulze Date: Sun, 1 Dec 2024 11:47:29 +0100 Subject: [PATCH 4/8] Added support for constants --- .../org/structs4java/Structs4JavaDsl.xtext | 5 + .../generator/StructGenerator.xtend | 113 +++++++++++++-- .../src/main/structs/constants.structs | 31 +++++ .../example/tests/ConstantTest.java | 129 ++++++++++++++++++ 4 files changed, 265 insertions(+), 13 deletions(-) create mode 100644 structs4java-maven-plugin-test/src/main/structs/constants.structs create mode 100644 structs4java-maven-plugin-test/src/test/java/org/structs4java/example/tests/ConstantTest.java diff --git a/org.structs4java.parent/org.structs4java/src/org/structs4java/Structs4JavaDsl.xtext b/org.structs4java.parent/org.structs4java/src/org/structs4java/Structs4JavaDsl.xtext index dc1adf8..c241f6f 100644 --- a/org.structs4java.parent/org.structs4java/src/org/structs4java/Structs4JavaDsl.xtext +++ b/org.structs4java.parent/org.structs4java/src/org/structs4java/Structs4JavaDsl.xtext @@ -98,6 +98,7 @@ IntegerMember: ('countof' '(' countof=[Member] ')')? ) ('padding' '(' padding=LONG ( ',' 'using' '=' using=LONG )? ')')? + (constant='const')? ('=' defaultValue=LONG)? ';' | @@ -110,6 +111,7 @@ IntegerMember: ('countof' '(' countof=[Member] ')')? ) ('padding' '(' padding=LONG ( ',' 'using' '=' using=LONG )? ')')? + (constant='const')? ('=' defaultValues=IntInitializerList)? ';' ; @@ -118,6 +120,7 @@ FloatMember: (comments += SL_COMMENT)* typename=FLOAT_TYPE name=ID ('padding' '(' padding=LONG ( ',' 'using' '=' using=LONG )? ')')? + (constant='const')? ('=' defaultValue=FLOAT)? ';' | @@ -125,6 +128,7 @@ FloatMember: typename=FLOAT_TYPE name=ID array=ArrayDimension ('padding' '(' padding=LONG ( ',' 'using' '=' using=LONG )? ')')? + (constant='const')? ('=' defaultValues=FloatInitializerList)? ';' ; @@ -137,6 +141,7 @@ StringMember: ('padding' '(' padding=LONG ( ',' 'using' '=' using=LONG )? ')')? ('filler' '(' filler=LONG ')')? (nullTerminated='null-terminated')? + (constant='const')? ('=' defaultValue=STRING)? ';' ; diff --git a/org.structs4java.parent/org.structs4java/src/org/structs4java/generator/StructGenerator.xtend b/org.structs4java.parent/org.structs4java/src/org/structs4java/generator/StructGenerator.xtend index b07dabc..247addf 100644 --- a/org.structs4java.parent/org.structs4java/src/org/structs4java/generator/StructGenerator.xtend +++ b/org.structs4java.parent/org.structs4java/src/org/structs4java/generator/StructGenerator.xtend @@ -384,14 +384,27 @@ class StructGenerator { java.nio.ByteBuffer slice = buf.slice(); slice.order(buf.order()); slice.limit((int)«tempVarForMember(findMemberDefiningSizeOf(m))»); + «IF isConst(m)» + «readerMethodName(m)»(slice, true); + «ELSE» obj.«setterName(m)»(«readerMethodName(m)»(slice, true)); + «ENDIF» + buf.position(buf.position() + (int)«tempVarForMember(findMemberDefiningSizeOf(m))»); } «ELSE» - obj.«setterName(m)»(«readerMethodName(m)»(buf, partialRead, (int)«tempVarForMember(findMemberDefiningSizeOf(m))»)); + «IF isConst(m)» + «readerMethodName(m)»(buf, partialRead, (int)«tempVarForMember(findMemberDefiningSizeOf(m))»); + «ELSE» + obj.«setterName(m)»(«readerMethodName(m)»(buf, partialRead, (int)«tempVarForMember(findMemberDefiningSizeOf(m))»)); + «ENDIF» «ENDIF» «ELSEIF findMemberDefiningCountOf(m) !== null» - obj.«setterName(m)»(«readerMethodName(m)»(buf, partialRead, (int)«tempVarForMember(findMemberDefiningCountOf(m))»)); + «IF isConst(m)» + «readerMethodName(m)»(buf, partialRead, (int)«tempVarForMember(findMemberDefiningCountOf(m))»); + «ELSE» + obj.«setterName(m)»(«readerMethodName(m)»(buf, partialRead, (int)«tempVarForMember(findMemberDefiningCountOf(m))»)); + «ENDIF» «ELSE» «IF m.isBitfield()» «IF m.isArray()» @@ -402,9 +415,9 @@ class StructGenerator { long value = «readerMethodName(m)»(buf, partialRead); «FOR entry : (m as BitfieldMember).entries» «IF entry.type !== null» - obj.«setterName(entry)»(«javaType(entry.type)».fromValue((value & «computeBitmaskFor(entry)») >>> «computeBitsToShift(entry)»)); + obj.«setterName(entry)»(«javaType(entry.type)».fromValue((value & «computeBitmaskFor(entry)») >>> «computeBitsToShift(entry)»)); «ELSEIF entry.typename == "boolean"» - obj.«setterName(entry)»(((value & «computeBitmaskFor(entry)») >>> «computeBitsToShift(entry)») != 0); + obj.«setterName(entry)»(((value & «computeBitmaskFor(entry)») >>> «computeBitsToShift(entry)») != 0); «ELSE» obj.«setterName(entry)»((value & «computeBitmaskFor(entry)») >>> «computeBitsToShift(entry)»); «ENDIF» @@ -413,13 +426,25 @@ class StructGenerator { «ENDIF» «ELSEIF m.isArray() && !m.isString() && m.isGreedy()» «IF struct.isSelfSized()» - obj.«setterName(m)»(«readerMethodName(m)»(buf, partialRead, (int)(structEndPosition - buf.position()))); + «IF isConst(m)» + «readerMethodName(m)»(buf, partialRead, (int)(structEndPosition - buf.position())); + «ELSE» + obj.«setterName(m)»(«readerMethodName(m)»(buf, partialRead, (int)(structEndPosition - buf.position()))); + «ENDIF» «ELSE» // greedy member - obj.«setterName(m)»(«readerMethodName(m)»(buf, partialRead, (int)(buf.limit() - buf.position()))); + «IF isConst(m)» + «readerMethodName(m)»(buf, partialRead, (int)(buf.limit() - buf.position())); + «ELSE» + obj.«setterName(m)»(«readerMethodName(m)»(buf, partialRead, (int)(buf.limit() - buf.position()))); + «ENDIF» «ENDIF» «ELSE» - obj.«setterName(m)»(«readerMethodName(m)»(buf, partialRead)); + «IF isConst(m)» + «readerMethodName(m)»(buf, partialRead); + «ELSE» + obj.«setterName(m)»(«readerMethodName(m)»(buf, partialRead)); + «ENDIF» «ENDIF» «ENDIF» «ENDIF» @@ -608,6 +633,12 @@ class StructGenerator { def getDefiningStruct(Member m) { return m.eContainer as StructDeclaration; } + + def getDefaultValues(Member m) { + if(m instanceof IntegerMember) return m.defaultValues.items + if(m instanceof FloatMember) return m.defaultValues.items + return null + } def readerMethodForArrayMember(Member m) ''' private static java.util.ArrayList<«m.nativeTypeName().native2JavaType().box()»> «m.readerMethodName()»(java.nio.ByteBuffer buf, boolean partialRead«IF findMemberDefiningCountOf(m) !== null», long countof«ENDIF») throws java.io.IOException { @@ -635,9 +666,15 @@ class StructGenerator { } } + «IF isConst(m)» + «FOR i : 0 ..< getDefaultValues(m).size» + if(lst.get(«i») != «getDefaultValues(m).get(i)») throw new java.io.IOException("Expected constant '«getDefaultValues(m).get(i)»' but got '" + lst.get(«i») + "'."); + «ENDFOR» + «ENDIF» + return lst; } - + «readerMethodForPrimitive(m)» ''' @@ -660,6 +697,17 @@ class StructGenerator { buf.position(buf.position() + «m.padding» - bytesOverlap); } «ENDIF» + + «IF isConst(m)» + «FOR i : 0 ..< getDefaultValues(m).size» + { + int actual = buffer.get(«i») & 0xFF; + if(actual != «getDefaultValues(m).get(i)») throw new java.io.IOException("Expected constant '«getDefaultValues(m).get(i)»' but got '" + actual + "' at offset «i»."); + } + «ENDFOR» + buffer.position(0); + «ENDIF» + return buffer; } ''' @@ -723,6 +771,11 @@ class StructGenerator { buf.position(buf.position() + «m.padding» - 8); «ENDIF» «ENDIF» + + «IF isConst(m) && !m.isArray()» + if(value != «m.defaultValue») throw new java.io.IOException("Expected constant '«m.defaultValue»' but got '" + value + "'."); + «ENDIF» + return value; } ''' @@ -787,12 +840,18 @@ class StructGenerator { buf.position(buf.position() + «m.padding» - 8); «ENDIF» «ENDIF» + + «IF isConst(m) && !m.isArray()» + if(value != «m.defaultValue») throw new java.io.IOException("Expected constant '«m.defaultValue»' but got '" + value + "'."); + «ENDIF» + return value; } ''' def readerMethodForStringMember(StringMember m) ''' private static String «m.readerMethodName()»(java.nio.ByteBuffer buf, boolean partialRead«IF dimensionOf(m) == 0 && findMemberDefiningSizeOf(m) !== null», «attributeJavaType(findMemberDefiningSizeOrCountOf(m))» sizeof«ENDIF») throws java.io.IOException { + String value = null; «IF dimensionOf(m) == 0 && findMemberDefiningSizeOf(m) === null» java.io.ByteArrayOutputStream tmp = new java.io.ByteArrayOutputStream(); int zerosRead = 0; @@ -816,7 +875,7 @@ class StructGenerator { buf.position(buf.position() + «m.padding» - bytesOverlap); } «ENDIF» - return new String(tmp.toByteArray(), 0, tmp.size() - zerosRead, "«encodingOf(m)»"); + value = new String(tmp.toByteArray(), 0, tmp.size() - zerosRead, "«encodingOf(m)»"); «ELSE» byte[] tmp = new byte[(int)sizeof]; @@ -830,10 +889,10 @@ class StructGenerator { «ENDIF» «IF m.getNullTerminated() === null» - return new String(tmp, 0, (int)sizeof, "«encodingOf(m)»"); + value = new String(tmp, 0, (int)sizeof, "«encodingOf(m)»"); «ELSE» int terminatingZeros = "\0".getBytes("«encodingOf(m)»").length; - return new String(tmp, 0, (int)(sizeof - terminatingZeros), "«encodingOf(m)»"); + value = new String(tmp, 0, (int)(sizeof - terminatingZeros), "«encodingOf(m)»"); «ENDIF» «ENDIF» @@ -859,7 +918,7 @@ class StructGenerator { «IF m.isPadded() && (dimensionOf(m) % m.padding) > 0» buf.position(buf.position() + «m.padding - (dimensionOf(m) % m.padding)»); «ENDIF» - return new String(tmp, 0, len, "«encodingOf(m)»"); + value = new String(tmp, 0, len, "«encodingOf(m)»"); «ENDIF» } catch(java.io.UnsupportedEncodingException e) { throw new java.io.IOException(e); @@ -869,9 +928,15 @@ class StructGenerator { if(!partialRead) { throw e; } - return new String(tmp.toByteArray(), 0, tmp.size() - zerosRead, "«encodingOf(m)»"); + value = new String(tmp.toByteArray(), 0, tmp.size() - zerosRead, "«encodingOf(m)»"); } «ENDIF» + + «IF isConst(m)» + if(!"«m.defaultValue»".equals(value)) throw new java.io.IOException("Expected constant '«m.defaultValue»' but got '" + value + "'."); + «ENDIF» + + return value; } ''' @@ -1432,14 +1497,36 @@ class StructGenerator { def setters(StructDeclaration struct) ''' «FOR m : struct.nonTransientMembers()» + «IF !isConst(m)» «IF m instanceof BitfieldMember» «setter(m)» «ELSE» «setter(m)» «ENDIF» + «ENDIF» «ENDFOR» ''' + def isConst(Member m) { + if(m instanceof ComplexTypeMember) { + return false + } + + if(m instanceof IntegerMember) { + return m.constant !== null + } + + if(m instanceof FloatMember) { + return m.constant !== null + } + + if(m instanceof StringMember) { + return m.constant !== null + } + + return false + } + def field(Member m) ''' «printComments(m)» private «attributeJavaType(m)» «attributeName(m)» = «defaultConstruct(m)»; diff --git a/structs4java-maven-plugin-test/src/main/structs/constants.structs b/structs4java-maven-plugin-test/src/main/structs/constants.structs new file mode 100644 index 0000000..7e18029 --- /dev/null +++ b/structs4java-maven-plugin-test/src/main/structs/constants.structs @@ -0,0 +1,31 @@ +package org.structs4java.example.tests.constants; + +struct StructWithPrimitiveConstants { + int8_t a const = 1; + uint8_t b const = 2; + int16_t c const = 3; + uint16_t d const = 4; + int32_t e const = 5; + uint32_t f const = 6; + int64_t g const = 7; + uint64_t h const = 8; + float i const = 1.0; + double j const = 2.0; +} + +struct StructWithArrayConstants { + int8_t a[1] const = { 1 }; + uint8_t b[1] const = { 2 }; + int16_t c[1] const = { 3 }; + uint16_t d[1] const = { 4 }; + int32_t e[1] const = { 5 }; + uint32_t f[1] const = { 6 }; + int64_t g[1] const = { 7 }; + uint64_t h[1] const = { 8 }; + float i[1] const = { 1.0 }; + double j[1] const = { 2.0 }; +} + +struct StructWithStringConstant { + char str[4] const = "test"; +} diff --git a/structs4java-maven-plugin-test/src/test/java/org/structs4java/example/tests/ConstantTest.java b/structs4java-maven-plugin-test/src/test/java/org/structs4java/example/tests/ConstantTest.java new file mode 100644 index 0000000..5b8f4ff --- /dev/null +++ b/structs4java-maven-plugin-test/src/test/java/org/structs4java/example/tests/ConstantTest.java @@ -0,0 +1,129 @@ +package org.structs4java.example.tests; + +import org.junit.Test; +import org.structs4java.example.tests.constants.StructWithArrayConstants; +import org.structs4java.example.tests.constants.StructWithPrimitiveConstants; +import org.structs4java.example.tests.constants.StructWithStringConstant; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class ConstantTest { + + @Test(expected = IOException.class) + public void testStructWithPrimitiveConstantsFailsOnIntegerMember() throws IOException { + ByteBuffer buffer = ByteBuffer.wrap(new byte[]{ + 0x00, + 0x00, + 0x00, 0x00, + 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); + buffer.order(ByteOrder.LITTLE_ENDIAN); + StructWithPrimitiveConstants.read(buffer); + } + + @Test(expected = IOException.class) + public void testStructWithPrimitiveConstantsFailsOnFloatMember() throws IOException { + ByteBuffer buffer = ByteBuffer.wrap(new byte[]{ + 0x01, + 0x02, + 0x03, 0x00, + 0x04, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, (byte)0x80, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); + buffer.order(ByteOrder.LITTLE_ENDIAN); + StructWithPrimitiveConstants.read(buffer); + } + + @Test(expected = IOException.class) + public void testStructWithArrayConstantsFailsOnIntegerMember() throws IOException { + ByteBuffer buffer = ByteBuffer.wrap(new byte[]{ + 0x00, + 0x00, + 0x00, 0x00, + 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); + buffer.order(ByteOrder.LITTLE_ENDIAN); + StructWithArrayConstants.read(buffer); + } + + @Test(expected = IOException.class) + public void testStructWithArrayConstantsFailsOnFloatMember() throws IOException { + ByteBuffer buffer = ByteBuffer.wrap(new byte[]{ + 0x01, + 0x02, + 0x03, 0x00, + 0x04, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, (byte)0x80, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); + buffer.order(ByteOrder.LITTLE_ENDIAN); + StructWithArrayConstants.read(buffer); + } + + @Test(expected = IOException.class) + public void testStructWithStringConstantFails() throws IOException { + ByteBuffer buffer = ByteBuffer.wrap(new byte[]{0x74, 0x61, 0x73, 0x74}); + buffer.order(ByteOrder.LITTLE_ENDIAN); + StructWithStringConstant.read(buffer); + } + + @Test + public void testStructWithPrimitiveConstantsPasses() throws IOException { + ByteBuffer buffer = ByteBuffer.wrap(new byte[]{ + 0x01, + 0x02, + 0x03, 0x00, + 0x04, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, (byte)0x80, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40 }); + buffer.order(ByteOrder.LITTLE_ENDIAN); + StructWithPrimitiveConstants.read(buffer); + } + + @Test + public void testStructWithArrayConstantsPasses() throws IOException { + ByteBuffer buffer = ByteBuffer.wrap(new byte[]{ + 0x01, + 0x02, + 0x03, 0x00, + 0x04, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, (byte)0x80, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40 }); + buffer.order(ByteOrder.LITTLE_ENDIAN); + StructWithArrayConstants.read(buffer); + } + + @Test + public void testStructWithStringConstantPasses() throws IOException { + ByteBuffer buffer = ByteBuffer.wrap(new byte[]{0x74, 0x65, 0x73, 0x74}); + buffer.order(ByteOrder.LITTLE_ENDIAN); + StructWithStringConstant.read(buffer); + } +} From 436367f68d7dc70a82b8d6f58a5dbaa0f631b9ad Mon Sep 17 00:00:00 2001 From: Marc-Christian Schulze Date: Sun, 1 Dec 2024 12:13:53 +0100 Subject: [PATCH 5/8] Updated readme and example project to make use of new features --- README.md | 62 +++++++++++++++++-- .../src/main/structs/zip.structs | 6 +- .../java/examples/zip/ZipFileWritingTest.java | 3 - 3 files changed, 60 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index c5704a5..aa286ec 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ fileHeader.write(buffer); The following table shows the built-in data types of Structs4Java. They can be used to compose more advanced types. | S4J Typename | Java Mapping | Size (bytes) | Subject to Endianess | Description | -| ------------- | ---------------------- | ------------ | ---------------------| -------------------------------------- | +| ------------- | ---------------------- | ------------ | ---------------------|----------------------------------------| | uint8_t | long | 1 | no | Fixed-size 8bit unsigned integer | | int8_t | long | 1 | no | Fixed-size 8bit signed integer | | uint16_t | long | 2 | yes | Fixed-size 16bit unsigned integer | @@ -131,11 +131,16 @@ The following table shows the built-in data types of Structs4Java. They can be u | int64_t | long | 8 | yes | Fixed-size 64bit signed integer | | float | double | 4 | no | Fixed-size 32bit floating point number | | double | double | 8 | no | Fixed-size 64bit floating point number | -| char[] | String | variable | no | String of characters (max size 2^31) | -| uint8_t[] | java.nio.ByteBuffer | variable | no | Raw ByteBuffer | -| int8_t[] | java.nio.ByteBuffer | variable | no | Raw ByteBuffer | +| char[] | String | variable | no | String of characters (max size 2^31)1 | +| uint8_t[] | java.nio.ByteBuffer | variable | no | Raw ByteBuffer2 | +| int8_t[] | java.nio.ByteBuffer | variable | no | Raw ByteBuffer2 | -There is no primitive type `char` available. If you need to read a single 1-byte character you can use `char[1]` instead. +1 There is no primitive type `char` available. If you need to read a single 1-byte character you can use `char[1]` instead. + +2 When a field is mapped to a `java.nio.ByteBuffer`, the underlying buffer stored in the struct is a +slice of the original buffer passed into the `read(java.nio.ByteBuffer)` method, and NOT a copy. Hence, the fields' buffer stays only +valid until you deallocate the original buffer. This is done for performance reasons to avoid reading potential large amounts of +unstructured/binary data until it is explicitly requested. ## Advanced Data Types (Structs & Enums) @@ -429,6 +434,53 @@ enum SomeEnum : uint8_t { ``` You can NOT define default values for fields within a bitfield, though. +Note that default values are only used during default construction of the structures. A default value is NOT returned if the read operation did not yield any value from the underlying buffer, e.g. +``` +struct SomeStruct { + char str[] = "my default"; +} +``` + +```Java +ByteBuffer buffer = ByteBuffer.wrap(new byte[]{}); +SomeStruct struct = SomeStruct.read(buffer); + +assertEquals("", struct.getStr()); +``` + +```Java +SomeStruct struct = new SomeStruct(); + +assertEquals("my default", struct.getStr()); +``` + +You can also assign default values to arrays of primitives, e.g. +``` +struct SomeStruct { + uint8_t int8[3] = { 1, 0x02, 3 }; + uint16_t int16[3] = { 0x45, 2, 3 }; + uint32_t int32[3] = { 7, 8, 9 }; + uint64_t int64[3] = { 0x20, 0x21, 0x22 }; + float f[3] = { 7.534, 1.4, 3.2 }; + double d[3] = { 9.75142476, 0.0, 7.7777 } +} +``` + +## Constants +Constants are fields that have a default value and are marked as `const`, e.g. +``` +struct SomeStruct { + uint8_t magic[4] const = { 0x50, 0x4b, 0x01, 0x02 }; +} +``` +Once a field is marked as `const` only the corresponding getter method will be generated and you won't be able to +override the fields' value. Furthermore, during the read operation of the structure, the value read from the underlying +buffer will be compared with the expected default value. If the values don't match, an `java.io.IOException` will be thrown. + +Typical use cases for constants are magic or signature fields within structures. + +Constants are NOT supported on structures, enums, bitfields or enum fields (unlike default values). + ## Plugin configuration Below is a full example configuration, including the default values, of the plugin: diff --git a/examples/zip-file-format/src/main/structs/zip.structs b/examples/zip-file-format/src/main/structs/zip.structs index 989d444..9672f6e 100644 --- a/examples/zip-file-format/src/main/structs/zip.structs +++ b/examples/zip-file-format/src/main/structs/zip.structs @@ -1,7 +1,7 @@ package examples.zip; struct LocalFileHeader { - uint8_t signature[4]; + uint8_t signature[4] const = { 0x50, 0x4b, 0x03, 0x04 }; uint16_t version; bitfield uint16_t { uint16_t generalPurposeFlags : 16; @@ -25,7 +25,7 @@ struct ExtraFieldRecord { } struct CentralDirectoryFileHeader { - uint8_t signature[4]; + uint8_t signature[4] const = { 0x50, 0x4b, 0x01, 0x02 }; uint16_t versionMadeBy; uint16_t versionNeededToExtract; bitfield uint16_t { @@ -50,7 +50,7 @@ struct CentralDirectoryFileHeader { } struct EndOfCentralDirectoryRecord { - uint8_t signature[4]; + uint8_t signature[4] const = { 0x50, 0x4b, 0x05, 0x06 }; uint16_t diskNumber; uint16_t diskWhereCentralDirectoryStarts; uint16_t numberOfCentralDirectoryRecordsOnThisDisk; diff --git a/examples/zip-file-format/src/test/java/examples/zip/ZipFileWritingTest.java b/examples/zip-file-format/src/test/java/examples/zip/ZipFileWritingTest.java index af694dd..0169d02 100644 --- a/examples/zip-file-format/src/test/java/examples/zip/ZipFileWritingTest.java +++ b/examples/zip-file-format/src/test/java/examples/zip/ZipFileWritingTest.java @@ -30,7 +30,6 @@ public void testWritingZipFile() throws IOException { // first we create and write the local file header long positionOfLocalFileHeader = buffer.position(); LocalFileHeader fhFile = new LocalFileHeader(); - fhFile.setSignature(ByteBuffer.wrap(new byte[]{ 0x50, 0x4b, 0x03, 0x04 })); fhFile.setFileName("example.txt"); fhFile.setCompressedSizeInBytes(fileData.length); fhFile.setUncompressedSizeInBytes(fileData.length); @@ -42,7 +41,6 @@ public void testWritingZipFile() throws IOException { // now we need to create the central directory header long positionOfFirstCentralDirectoryHeader = buffer.position(); CentralDirectoryFileHeader cdfh = new CentralDirectoryFileHeader(); - cdfh.setSignature(ByteBuffer.wrap(new byte[]{ 0x50, 0x4b, 0x01, 0x02 })); cdfh.setFileName("example.txt"); cdfh.setCrc32OfUncompressedData(crc32); cdfh.setCompressedSizeInBytes(fileData.length); @@ -52,7 +50,6 @@ public void testWritingZipFile() throws IOException { // finally, we need to write the closing end of central directory header EndOfCentralDirectoryRecord eocdr = new EndOfCentralDirectoryRecord(); - eocdr.setSignature(ByteBuffer.wrap(new byte[]{ 0x50, 0x4b, 0x05, 0x06 })); eocdr.setOffsetOfStartOfCentralDirectory(positionOfFirstCentralDirectoryHeader); eocdr.setNumberOfCentralDirectoryRecordsOnThisDisk(1); eocdr.setTotalNumberOfCentralDirectoryRecords(1); From e4281d81ffcfad8460ed9f30d38efb801d9e4722 Mon Sep 17 00:00:00 2001 From: Marc-Christian Schulze Date: Sun, 1 Dec 2024 12:15:34 +0100 Subject: [PATCH 6/8] Updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa286ec..aad5deb 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Define some structures you would like to read/write in a `*.structs` file under package com.mycompany.projectx; struct FileHeader { - uint8_t magic[4]; + uint8_t magic[4] const = { 0x50, 0x4b, 0x01, 0x02 }; uint16_t numberSections countof(sections); FileSection sections[]; } From f027c5e52a7a87bbfb56be5679a0228e75429bee Mon Sep 17 00:00:00 2001 From: Marc-Christian Schulze Date: Sun, 1 Dec 2024 12:16:17 +0100 Subject: [PATCH 7/8] Updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aad5deb..e1e63f2 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ struct FileHeader { struct FileSection { SectionType type; char name[32]; - uint32_t length sizeof(sectionContent); + uint32_t length sizeof(content); uint8_t content[]; } From ccd7fd1d49ce0e0f88b0e8065f64ac4d875f3850 Mon Sep 17 00:00:00 2001 From: Marc-Christian Schulze Date: Sun, 1 Dec 2024 12:16:53 +0100 Subject: [PATCH 8/8] Updated readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e1e63f2..644fb9a 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,7 @@ fileHeader.write(buffer); * bit fields * implement Java interfaces * default values + * constants ## Unsupported * Unions