diff --git a/README-CHANGES.xml b/README-CHANGES.xml
index ab563d2d..0524b249 100644
--- a/README-CHANGES.xml
+++ b/README-CHANGES.xml
@@ -6,6 +6,13 @@
2017-01-09
0.6.0
+
+ 2017-01-09
+
+ 29
+ Allow redundant use of the metadata command.
+
+
2017-01-09
diff --git a/io7m-smfj-format-text/src/main/java/com/io7m/smfj/format/text/SMFTV1BodyParser.java b/io7m-smfj-format-text/src/main/java/com/io7m/smfj/format/text/SMFTV1BodyParser.java
index 5f0a4b85..0f7fda3a 100644
--- a/io7m-smfj-format-text/src/main/java/com/io7m/smfj/format/text/SMFTV1BodyParser.java
+++ b/io7m-smfj-format-text/src/main/java/com/io7m/smfj/format/text/SMFTV1BodyParser.java
@@ -51,7 +51,8 @@ final class SMFTV1BodyParser extends SMFTAbstractParser
private Map attributes_ok;
private Map attributes_attempted;
private long parsed_triangles;
- private long parsed_metas;
+ private long metas_parsed_count;
+ private boolean metas_started;
SMFTV1BodyParser(
final SMFTAbstractParser in_parent,
@@ -131,7 +132,7 @@ private void onEOF()
private void failMissedMetadata()
{
- if (this.parsed_metas != this.header.metaCount()) {
+ if (this.metas_parsed_count != this.header.metaCount()) {
this.fail("Too few metadata elements specified", Optional.empty());
}
}
@@ -577,13 +578,16 @@ private void parseMetas()
{
LOG.debug("parsing metadata values");
- if (this.header.metaCount() == 0L) {
- super.fail("No metadata was expected.", Optional.empty());
+ if (this.metas_started) {
+ super.fail(
+ "A metadata command has already been specified.",
+ Optional.empty());
return;
}
- this.parsed_metas = 0L;
- while (this.parsed_metas != this.header.metaCount()) {
+ this.metas_started = true;
+ this.metas_parsed_count = 0L;
+ while (this.metas_parsed_count != this.header.metaCount()) {
if (this.parserHasFailed()) {
return;
}
@@ -614,7 +618,7 @@ private void parseMetas()
}
}
- this.parsed_metas = Math.addExact(this.parsed_metas, 1L);
+ this.metas_parsed_count = Math.addExact(this.metas_parsed_count, 1L);
}
}
diff --git a/io7m-smfj-specification/src/main/resources/com/io7m/smfj/specification/text.sdi b/io7m-smfj-specification/src/main/resources/com/io7m/smfj/specification/text.sdi
index 0c61346c..00e9b784 100644
--- a/io7m-smfj-specification/src/main/resources/com/io7m/smfj/specification/text.sdi
+++ b/io7m-smfj-specification/src/main/resources/com/io7m/smfj/specification/text.sdi
@@ -278,10 +278,9 @@ The command takes no arguments.
[paragraph]
The command may only appear in the file after all
[link [target smft.data.commands.triangles] triangles] and
-[link [target smft.data.commands.attribute] attributes] have been received.
-
-
-
+[link [target smft.data.commands.attribute] attributes] have been received. If
+the file does not have metadata, the command may be omitted. If the file does
+have metadata, the command must be specified exactly once.
[subsection [title Data - meta] [id smft.data.commands.meta]]
[paragraph]
diff --git a/io7m-smfj-tests/src/test/java/com/io7m/smfj/tests/format/text/SMFFormatTextTest.java b/io7m-smfj-tests/src/test/java/com/io7m/smfj/tests/format/text/SMFFormatTextTest.java
index 0d572d2d..8d6c2ffc 100644
--- a/io7m-smfj-tests/src/test/java/com/io7m/smfj/tests/format/text/SMFFormatTextTest.java
+++ b/io7m-smfj-tests/src/test/java/com/io7m/smfj/tests/format/text/SMFFormatTextTest.java
@@ -2896,6 +2896,182 @@ public void testSerializerAttributeNotFinishedSerializingPrevious()
serializer.serializeData(SMFAttributeName.of("y"));
}
+ @Test
+ public void testMetaNoneNotOmitted(
+ final @Mocked SMFParserEventsType events)
+ {
+ final StringBuilder s = new StringBuilder(128);
+ s.append("smf 1 0");
+ s.append(System.lineSeparator());
+ s.append("schema 696F376D A0B0C0D0 1 2");
+ s.append(System.lineSeparator());
+ s.append("coordinates +x +y -z counter-clockwise");
+ s.append(System.lineSeparator());
+ s.append("attribute a integer-unsigned 1 32");
+ s.append(System.lineSeparator());
+ s.append("vertices 1");
+ s.append(System.lineSeparator());
+ s.append("triangles 1 16");
+ s.append(System.lineSeparator());
+ s.append("data");
+ s.append(System.lineSeparator());
+ s.append("attribute a");
+ s.append(System.lineSeparator());
+ s.append("0");
+ s.append(System.lineSeparator());
+ s.append("triangles");
+ s.append(System.lineSeparator());
+ s.append("0 0 0");
+ s.append(System.lineSeparator());
+ s.append("metadata");
+ s.append(System.lineSeparator());
+
+ final SMFAttribute attribute = SMFAttribute.of(
+ SMFAttributeName.of("a"),
+ SMFComponentType.ELEMENT_TYPE_INTEGER_UNSIGNED,
+ 1,
+ 32);
+
+ final SMFHeader.Builder header_b = baseHeader(List.of(attribute));
+ header_b.setTriangleCount(1L);
+ header_b.setVertexCount(1L);
+ final SMFHeader h = header_b.build();
+
+ new StrictExpectations()
+ {{
+ events.onStart();
+ events.onVersionReceived(SMFFormatVersion.of(1, 0));
+ events.onHeaderParsed(h);
+ events.onDataAttributeStart(attribute);
+ events.onDataAttributeValueIntegerUnsigned1(0L);
+ events.onDataAttributeFinish(attribute);
+ events.onDataTrianglesStart();
+ events.onDataTriangle(0L, 0L, 0L);
+ events.onDataTrianglesFinish();
+ events.onFinish();
+ }};
+
+ runForText(events, true, s);
+ }
+
+ @Test
+ public void testMetaNoneOmitted(
+ final @Mocked SMFParserEventsType events)
+ {
+ final StringBuilder s = new StringBuilder(128);
+ s.append("smf 1 0");
+ s.append(System.lineSeparator());
+ s.append("schema 696F376D A0B0C0D0 1 2");
+ s.append(System.lineSeparator());
+ s.append("coordinates +x +y -z counter-clockwise");
+ s.append(System.lineSeparator());
+ s.append("attribute a integer-unsigned 1 32");
+ s.append(System.lineSeparator());
+ s.append("vertices 1");
+ s.append(System.lineSeparator());
+ s.append("triangles 1 16");
+ s.append(System.lineSeparator());
+ s.append("data");
+ s.append(System.lineSeparator());
+ s.append("attribute a");
+ s.append(System.lineSeparator());
+ s.append("0");
+ s.append(System.lineSeparator());
+ s.append("triangles");
+ s.append(System.lineSeparator());
+ s.append("0 0 0");
+ s.append(System.lineSeparator());
+
+ final SMFAttribute attribute = SMFAttribute.of(
+ SMFAttributeName.of("a"),
+ SMFComponentType.ELEMENT_TYPE_INTEGER_UNSIGNED,
+ 1,
+ 32);
+
+ final SMFHeader.Builder header_b = baseHeader(List.of(attribute));
+ header_b.setTriangleCount(1L);
+ header_b.setVertexCount(1L);
+ final SMFHeader h = header_b.build();
+
+ new StrictExpectations()
+ {{
+ events.onStart();
+ events.onVersionReceived(SMFFormatVersion.of(1, 0));
+ events.onHeaderParsed(h);
+ events.onDataAttributeStart(attribute);
+ events.onDataAttributeValueIntegerUnsigned1(0L);
+ events.onDataAttributeFinish(attribute);
+ events.onDataTrianglesStart();
+ events.onDataTriangle(0L, 0L, 0L);
+ events.onDataTrianglesFinish();
+ events.onFinish();
+ }};
+
+ runForText(events, true, s);
+ }
+
+ @Test
+ public void testMetaTwice(
+ final @Mocked SMFParserEventsType events)
+ {
+ final StringBuilder s = new StringBuilder(128);
+ s.append("smf 1 0");
+ s.append(System.lineSeparator());
+ s.append("schema 696F376D A0B0C0D0 1 2");
+ s.append(System.lineSeparator());
+ s.append("coordinates +x +y -z counter-clockwise");
+ s.append(System.lineSeparator());
+ s.append("attribute a integer-unsigned 1 32");
+ s.append(System.lineSeparator());
+ s.append("vertices 1");
+ s.append(System.lineSeparator());
+ s.append("triangles 1 16");
+ s.append(System.lineSeparator());
+ s.append("data");
+ s.append(System.lineSeparator());
+ s.append("attribute a");
+ s.append(System.lineSeparator());
+ s.append("0");
+ s.append(System.lineSeparator());
+ s.append("triangles");
+ s.append(System.lineSeparator());
+ s.append("0 0 0");
+ s.append(System.lineSeparator());
+ s.append("metadata");
+ s.append(System.lineSeparator());
+ s.append("metadata");
+ s.append(System.lineSeparator());
+
+ final SMFAttribute attribute = SMFAttribute.of(
+ SMFAttributeName.of("a"),
+ SMFComponentType.ELEMENT_TYPE_INTEGER_UNSIGNED,
+ 1,
+ 32);
+
+ final SMFHeader.Builder header_b = baseHeader(List.of(attribute));
+ header_b.setTriangleCount(1L);
+ header_b.setVertexCount(1L);
+ final SMFHeader h = header_b.build();
+
+ new StrictExpectations()
+ {{
+ events.onStart();
+ events.onVersionReceived(SMFFormatVersion.of(1, 0));
+ events.onHeaderParsed(h);
+ events.onDataAttributeStart(attribute);
+ events.onDataAttributeValueIntegerUnsigned1(0L);
+ events.onDataAttributeFinish(attribute);
+ events.onDataTrianglesStart();
+ events.onDataTriangle(0L, 0L, 0L);
+ events.onDataTrianglesFinish();
+ events.onError(this.withArgThat(
+ new ParseErrorMessageStartsWith("A metadata command has already been specified.")));
+ events.onFinish();
+ }};
+
+ runForText(events, true, s);
+ }
+
private static class ParseErrorMessageStartsWith extends TypeSafeMatcher
{
private final String message;