Skip to content

Commit 727281a

Browse files
committed
[Java] Check sinceVersion on elements are valid in the context of message schema version. Issue #902.
1 parent b9c60ae commit 727281a

33 files changed

+447
-183
lines changed

sbe-tool/src/main/java/uk/co/real_logic/sbe/xml/CompositeType.java

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public class CompositeType extends Type
5858
/**
5959
* Construct a new compositeType from XML Schema.
6060
*
61-
* @param node from the XML Schema Parsing
61+
* @param node from the XML Schema parsing.
6262
* @param givenName for this node.
6363
* @param referencedName of the type when created from a ref in a composite.
6464
* @param compositesPath with the path of composites that represents the levels of composition.
@@ -88,20 +88,20 @@ public class CompositeType extends Type
8888
}
8989

9090
/**
91-
* Return the EncodedDataType within this composite with the given name
91+
* Return the EncodedDataType within this composite with the given name.
9292
*
93-
* @param name of the EncodedDataType to return
94-
* @return type requested
93+
* @param name of the type to return.
94+
* @return type requested.
9595
*/
9696
public Type getType(final String name)
9797
{
9898
return containedTypeByNameMap.get(name);
9999
}
100100

101101
/**
102-
* The encodedLength (in octets) of the list of EncodedDataTypes
102+
* The encodedLength (in octets) of the list of encoded types.
103103
*
104-
* @return encodedLength of the compositeType
104+
* @return encodedLength of the compositeType.
105105
*/
106106
public int encodedLength()
107107
{
@@ -129,9 +129,9 @@ public int encodedLength()
129129
}
130130

131131
/**
132-
* Return list of the {@link Type}s that compose this composite
132+
* Return list of the {@link Type}s that compose this composite.
133133
*
134-
* @return {@link List} that holds the {@link Type}s in this composite
134+
* @return {@link List} that holds the {@link Type}s in this composite.
135135
*/
136136
public List<Type> getTypeList()
137137
{
@@ -140,7 +140,7 @@ public List<Type> getTypeList()
140140

141141
/**
142142
* Make this composite type, if it has a varData member, variable length
143-
* by making the EncodedDataType with the name "varData" be variable length.
143+
* by making the type with the name "varData" be variable length.
144144
*/
145145
public void makeDataFieldCompositeType()
146146
{
@@ -155,7 +155,7 @@ public void makeDataFieldCompositeType()
155155
* Check the composite for being a well-formed group encodedLength encoding. This means
156156
* that there are the fields "blockLength" and "numInGroup" present.
157157
*
158-
* @param node of the XML for this composite
158+
* @param node of the XML for this composite.
159159
*/
160160
public void checkForWellFormedGroupSizeEncoding(final Node node)
161161
{
@@ -433,10 +433,7 @@ private Type processType(
433433
case "ref":
434434
{
435435
final XPath xPath = XPathFactory.newInstance().newXPath();
436-
437-
final String refName = XmlSchemaParser.getAttributeValue(subTypeNode, "name");
438436
final String refTypeName = XmlSchemaParser.getAttributeValue(subTypeNode, "type");
439-
final int refOffset = Integer.parseInt(XmlSchemaParser.getAttributeValue(subTypeNode, "offset", "-1"));
440437
final Node refTypeNode = (Node)xPath.compile(
441438
"/*[local-name() = 'messageSchema']/types/*[@name='" + refTypeName + "']")
442439
.evaluate(subTypeNode.getOwnerDocument(), XPathConstants.NODE);
@@ -453,11 +450,33 @@ private Type processType(
453450
throw new IllegalStateException("ref types cannot create circular dependencies");
454451
}
455452

453+
final String refName = XmlSchemaParser.getAttributeValue(subTypeNode, "name");
456454
type = processType(refTypeNode, refName, refName, refTypeName);
457455

458-
if (-1 != refOffset)
456+
final String refOffset = XmlSchemaParser.getAttributeValueOrNull(subTypeNode, "offset");
457+
if (null != refOffset)
458+
{
459+
try
460+
{
461+
type.offsetAttribute(Integer.parseInt(refOffset));
462+
}
463+
catch (final NumberFormatException ex)
464+
{
465+
XmlSchemaParser.handleError(subTypeNode, "invalid number type: " + refOffset);
466+
}
467+
}
468+
469+
final String refVersion = XmlSchemaParser.getAttributeValueOrNull(subTypeNode, "sinceVersion");
470+
if (null != refVersion)
459471
{
460-
type.offsetAttribute(refOffset);
472+
try
473+
{
474+
type.sinceVersion(Integer.parseInt(refVersion));
475+
}
476+
catch (final NumberFormatException ex)
477+
{
478+
XmlSchemaParser.handleError(subTypeNode, "invalid number type: " + refVersion);
479+
}
461480
}
462481
}
463482

sbe-tool/src/main/java/uk/co/real_logic/sbe/xml/ErrorHandler.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,11 @@ public void checkIfShouldExit()
104104
{
105105
if (errors > 0)
106106
{
107-
throw new IllegalStateException("had " + errors + " errors");
107+
throw new IllegalStateException("had " + errors + (errors > 1 ? " errors" : " error"));
108108
}
109109
else if (warnings > 0 && warningsFatal)
110110
{
111-
throw new IllegalStateException("had " + warnings + " warnings");
111+
throw new IllegalStateException("had " + warnings + (warnings > 1 ? " warnings" : " warning"));
112112
}
113113
}
114114

sbe-tool/src/main/java/uk/co/real_logic/sbe/xml/MessageSchema.java

Lines changed: 159 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@
1919
import org.agrona.Verify;
2020

2121
import java.nio.ByteOrder;
22-
import java.util.Collection;
23-
import java.util.Map;
22+
import java.util.*;
2423

2524
import static uk.co.real_logic.sbe.xml.XmlSchemaParser.*;
2625

@@ -164,4 +163,162 @@ public ByteOrder byteOrder()
164163
{
165164
return byteOrder;
166165
}
166+
167+
/**
168+
* Validate the message schema and delegate warnings and errors to the supplied {@link ErrorHandler}.
169+
*
170+
* @param errorHandler for delegating warnings and errors.
171+
*/
172+
public void validate(final ErrorHandler errorHandler)
173+
{
174+
final Deque<String> path = new ArrayDeque<>();
175+
176+
for (final Type type : typeByNameMap.values())
177+
{
178+
validateType(errorHandler, path, type);
179+
}
180+
181+
for (final Message message : messageByIdMap.values())
182+
{
183+
if (message.sinceVersion() > version)
184+
{
185+
errorHandler.error(message.name() + ".sinceVersion=" + message.sinceVersion() +
186+
" > messageSchema.version=" + version);
187+
}
188+
189+
path.addLast(message.name());
190+
191+
for (final Field field : message.fields())
192+
{
193+
validateField(errorHandler, path, field);
194+
}
195+
196+
path.removeLast();
197+
}
198+
}
199+
200+
private void validateType(final ErrorHandler errorHandler, final Deque<String> path, final Type type)
201+
{
202+
if (type instanceof EncodedDataType)
203+
{
204+
validateEncodedType(errorHandler, path, (EncodedDataType)type);
205+
}
206+
else if (type instanceof EnumType)
207+
{
208+
validateEnumType(errorHandler, path, (EnumType)type);
209+
}
210+
else if (type instanceof SetType)
211+
{
212+
validateSetType(errorHandler, path, (SetType)type);
213+
}
214+
else if (type instanceof CompositeType)
215+
{
216+
validateCompositeType(errorHandler, path, (CompositeType)type);
217+
}
218+
}
219+
220+
private void validateEncodedType(
221+
final ErrorHandler errorHandler, final Deque<String> path, final EncodedDataType type)
222+
{
223+
if (type.sinceVersion() > version)
224+
{
225+
reportError(errorHandler, path, type.name(), type.sinceVersion());
226+
}
227+
}
228+
229+
private void validateEnumType(final ErrorHandler errorHandler, final Deque<String> path, final EnumType type)
230+
{
231+
if (type.sinceVersion() > version)
232+
{
233+
reportError(errorHandler, path, type.name(), type.sinceVersion());
234+
}
235+
236+
path.addLast(type.name());
237+
238+
for (final EnumType.ValidValue validValue : type.validValues())
239+
{
240+
if (validValue.sinceVersion() > version)
241+
{
242+
reportError(errorHandler, path, validValue.name(), validValue.sinceVersion());
243+
}
244+
}
245+
246+
path.removeLast();
247+
}
248+
249+
private void validateSetType(final ErrorHandler errorHandler, final Deque<String> path, final SetType type)
250+
{
251+
if (type.sinceVersion() > version)
252+
{
253+
reportError(errorHandler, path, type.name(), type.sinceVersion());
254+
}
255+
256+
path.addLast(type.name());
257+
258+
for (final SetType.Choice choice : type.choices())
259+
{
260+
if (choice.sinceVersion() > version)
261+
{
262+
reportError(errorHandler, path, choice.name(), choice.sinceVersion());
263+
}
264+
}
265+
266+
path.removeLast();
267+
}
268+
269+
private void validateCompositeType(
270+
final ErrorHandler errorHandler, final Deque<String> path, final CompositeType type)
271+
{
272+
if (type.sinceVersion() > version)
273+
{
274+
reportError(errorHandler, path, type.name(), type.sinceVersion());
275+
}
276+
277+
path.addLast(type.name());
278+
279+
for (final Type subType : type.getTypeList())
280+
{
281+
validateType(errorHandler, path, subType);
282+
}
283+
284+
path.removeLast();
285+
}
286+
287+
private void validateField(final ErrorHandler errorHandler, final Deque<String> path, final Field field)
288+
{
289+
if (field.sinceVersion() > version)
290+
{
291+
reportError(errorHandler, path, field.name(), field.sinceVersion());
292+
}
293+
294+
final List<Field> groupFields = field.groupFields();
295+
if (null != groupFields)
296+
{
297+
path.addLast(field.name());
298+
299+
for (final Field groupField : groupFields)
300+
{
301+
validateField(errorHandler, path, groupField);
302+
}
303+
304+
path.removeLast();
305+
}
306+
}
307+
308+
private void reportError(
309+
final ErrorHandler errorHandler, final Deque<String> path, final String name, final int sinceVersion)
310+
{
311+
final StringBuilder sb = new StringBuilder();
312+
313+
for (final String step : path)
314+
{
315+
sb.append(step).append('.');
316+
}
317+
318+
sb.append(name)
319+
.append(".sinceVersion=").append(sinceVersion)
320+
.append(" > messageSchema.version=").append(version);
321+
322+
errorHandler.error(sb.toString());
323+
}
167324
}

sbe-tool/src/main/java/uk/co/real_logic/sbe/xml/Type.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ public abstract class Type
2828
private final String name;
2929
private final Presence presence;
3030
private final String description;
31-
private final int sinceVersion;
3231
private final int deprecated;
3332
private final String semanticType;
3433
private final String referencedName;
3534

3635
private int offsetAttribute;
36+
private int sinceVersion;
3737

3838
/**
3939
* Construct a new Type from XML Schema. Called by subclasses to mostly set common fields
@@ -150,6 +150,16 @@ public int sinceVersion()
150150
return sinceVersion;
151151
}
152152

153+
/**
154+
* Set the sinceVersion for the type.
155+
*
156+
* @param version to be set.
157+
*/
158+
public void sinceVersion(final int version)
159+
{
160+
sinceVersion = version;
161+
}
162+
153163
/**
154164
* Version in which type was deprecated. Only valid if greater than zero.
155165
*

sbe-tool/src/main/java/uk/co/real_logic/sbe/xml/XmlSchemaParser.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ public static MessageSchema parse(final InputSource is, final ParserOptions opti
146146
}
147147

148148
final MessageSchema messageSchema = new MessageSchema(schemaNode, typeByNameMap, messageByIdMap);
149+
150+
messageSchema.validate(errorHandler);
149151
errorHandler.checkIfShouldExit();
150152

151153
return messageSchema;

sbe-tool/src/test/java/uk/co/real_logic/sbe/TargetCodeGeneratorTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,17 @@
2323
import static org.mockito.Mockito.mock;
2424
import static org.mockito.Mockito.verify;
2525

26-
public class TargetCodeGeneratorTest
26+
class TargetCodeGeneratorTest
2727
{
2828
@Test
29-
public void shouldThrowOnNoTargetLanguage()
29+
void shouldThrowOnNoTargetLanguage()
3030
{
3131
assertThrows(IllegalArgumentException.class, () ->
3232
SbeTool.generate(mock(Ir.class), ".", "none"));
3333
}
3434

3535
@Test
36-
public void shouldLoadAndInstantiateNonStandardTargetLanguage() throws Exception
36+
void shouldLoadAndInstantiateNonStandardTargetLanguage() throws Exception
3737
{
3838
final Ir ir = mock(Ir.class);
3939
final String outputDir = ".";

0 commit comments

Comments
 (0)