/* * Copyright 2013-2020 Real Logic Limited. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package uk.co.real_logic.sbe.xml; import org.junit.jupiter.api.Test; import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import uk.co.real_logic.sbe.PrimitiveType; import uk.co.real_logic.sbe.PrimitiveValue; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.HashMap; import java.util.Map; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static uk.co.real_logic.sbe.PrimitiveValue.parse; public class EncodedDataTypeTest { @Test public void shouldHandleSettingAllAttributes() throws Exception { final String testXmlString = "<types>" + " <type name=\"testType\" presence=\"required\" primitiveType=\"char\" length=\"1\" " + "variableLength=\"false\"/>" + "</types>"; final Map<String, Type> map = parseTestXmlWithMap("/types/type", testXmlString); // assert that testType is in map and name of Type is correct final Type t = map.get("testType"); assertThat(t.name(), is("testType")); assertThat(t.presence(), is(Presence.REQUIRED)); final EncodedDataType d = (EncodedDataType)t; assertThat(d.primitiveType(), is(PrimitiveType.CHAR)); assertThat(d.length(), is(1)); assertThat(d.isVariableLength(), is(false)); } @Test public void shouldHandleMultipleTypes() throws Exception { final String testXmlString = "<types>" + " <type name=\"testType1\" presence=\"required\" primitiveType=\"char\" length=\"1\" " + "variableLength=\"false\"/>" + " <type name=\"testType2\" presence=\"required\" primitiveType=\"int8\" length=\"1\" " + "variableLength=\"false\"/>" + "</types>"; final Map<String, Type> map = parseTestXmlWithMap("/types/type", testXmlString); // assert that testType is in map and name of Type is correct assertThat(map.size(), is(2)); assertThat(map.get("testType1").name(), is("testType1")); assertThat(map.get("testType2").name(), is("testType2")); } @Test public void shouldSetAppropriateDefaultsWhenNoneSpecified() throws Exception { final String testXmlString = "<types>" + " <type name=\"testType\" primitiveType=\"char\"/>" + "</types>"; final Map<String, Type> map = parseTestXmlWithMap("/types/type", testXmlString); // assert that testType is in map and name of Type is correct assertThat(map.get("testType").name(), is("testType")); // assert defaults for length, variableLength and presence final Type t = map.get("testType"); assertThat(t.presence(), is(Presence.REQUIRED)); final EncodedDataType d = (EncodedDataType)t; assertThat(d.length(), is(1)); assertThat(d.isVariableLength(), is(false)); } @Test public void shouldUseAppropriatePresence() throws Exception { final String testXmlString = "<types>" + " <type name=\"testTypeDefault\" primitiveType=\"char\"/>" + " <type name=\"testTypeRequired\" presence=\"required\" primitiveType=\"char\"/>" + " <type name=\"testTypeOptional\" presence=\"optional\" primitiveType=\"char\"/>" + " <type name=\"testTypeConstant\" presence=\"constant\" primitiveType=\"char\">A</type>" + "</types>"; final Map<String, Type> map = parseTestXmlWithMap("/types/type", testXmlString); assertThat(map.get("testTypeDefault").presence(), is(Presence.REQUIRED)); assertThat(map.get("testTypeRequired").presence(), is(Presence.REQUIRED)); assertThat(map.get("testTypeOptional").presence(), is(Presence.OPTIONAL)); assertThat(map.get("testTypeConstant").presence(), is(Presence.CONSTANT)); } @Test public void shouldThrowExceptionWhenUnknownPresenceSpecified() { final String testXmlString = "<types>" + " <type name=\"testTyeUnknown\" presence=\"XXXXX\" primitiveType=\"char\"/>" + "</types>"; assertThrows(IllegalArgumentException.class, () -> parseTestXmlWithMap("/types/type", testXmlString)); } @Test public void shouldThrowExceptionWhenNoPrimitiveTypeSpecified() { final String testXmlString = "<types>" + " <type name=\"testType\"/>" + "</types>"; assertThrows(IllegalStateException.class, () -> parseTestXmlWithMap("/types/type", testXmlString)); } @Test public void shouldThrowExceptionWhenNoNameSpecified() { final String testXmlString = "<types>" + " <type primitiveType=\"char\"/>" + "</types>"; assertThrows(IllegalStateException.class, () -> parseTestXmlWithMap("/types/type", testXmlString)); } @Test public void shouldUseAppropriatePrimitiveType() throws Exception { final String testXmlString = "<types>" + " <type name=\"testTypeChar\" primitiveType=\"char\"/>" + " <type name=\"testTypeInt8\" primitiveType=\"int8\"/>" + " <type name=\"testTypeInt16\" primitiveType=\"int16\"/>" + " <type name=\"testTypeInt32\" primitiveType=\"int32\"/>" + " <type name=\"testTypeInt64\" primitiveType=\"int64\"/>" + " <type name=\"testTypeUInt8\" primitiveType=\"uint8\"/>" + " <type name=\"testTypeUInt16\" primitiveType=\"uint16\"/>" + " <type name=\"testTypeUInt32\" primitiveType=\"uint32\"/>" + " <type name=\"testTypeUInt64\" primitiveType=\"uint64\"/>" + " <type name=\"testTypeFloat\" primitiveType=\"float\"/>" + " <type name=\"testTypeDouble\" primitiveType=\"double\"/>" + "</types>"; final Map<String, Type> map = parseTestXmlWithMap("/types/type", testXmlString); assertThat(((EncodedDataType)map.get("testTypeChar")).primitiveType(), is(PrimitiveType.CHAR)); assertThat(((EncodedDataType)map.get("testTypeInt8")).primitiveType(), is(PrimitiveType.INT8)); assertThat(((EncodedDataType)map.get("testTypeInt16")).primitiveType(), is(PrimitiveType.INT16)); assertThat(((EncodedDataType)map.get("testTypeInt32")).primitiveType(), is(PrimitiveType.INT32)); assertThat(((EncodedDataType)map.get("testTypeInt64")).primitiveType(), is(PrimitiveType.INT64)); assertThat(((EncodedDataType)map.get("testTypeUInt8")).primitiveType(), is(PrimitiveType.UINT8)); assertThat(((EncodedDataType)map.get("testTypeUInt16")).primitiveType(), is(PrimitiveType.UINT16)); assertThat(((EncodedDataType)map.get("testTypeUInt32")).primitiveType(), is(PrimitiveType.UINT32)); assertThat(((EncodedDataType)map.get("testTypeUInt64")).primitiveType(), is(PrimitiveType.UINT64)); assertThat(((EncodedDataType)map.get("testTypeFloat")).primitiveType(), is(PrimitiveType.FLOAT)); assertThat(((EncodedDataType)map.get("testTypeDouble")).primitiveType(), is(PrimitiveType.DOUBLE)); } @Test public void shouldThrowExceptionWhenUnknownPrimitiveTypeSpecified() { final String testXmlString = "<types>" + " <type name=\"testTypeUnknown\" primitiveType=\"XXXX\"/>" + "</types>"; assertThrows(IllegalArgumentException.class, () -> parseTestXmlWithMap("/types/type", testXmlString)); } @Test public void shouldReturnCorrectSizeForPrimitiveTypes() throws Exception { final String testXmlString = "<types>" + " <type name=\"testTypeChar\" primitiveType=\"char\"/>" + " <type name=\"testTypeInt8\" primitiveType=\"int8\"/>" + " <type name=\"testTypeInt16\" primitiveType=\"int16\"/>" + " <type name=\"testTypeInt32\" primitiveType=\"int32\"/>" + " <type name=\"testTypeInt64\" primitiveType=\"int64\"/>" + " <type name=\"testTypeUInt8\" primitiveType=\"uint8\"/>" + " <type name=\"testTypeUInt16\" primitiveType=\"uint16\"/>" + " <type name=\"testTypeUInt32\" primitiveType=\"uint32\"/>" + " <type name=\"testTypeUInt64\" primitiveType=\"uint64\"/>" + " <type name=\"testTypeFloat\" primitiveType=\"float\"/>" + " <type name=\"testTypeDouble\" primitiveType=\"double\"/>" + "</types>"; final Map<String, Type> map = parseTestXmlWithMap("/types/type", testXmlString); assertThat(map.get("testTypeChar").encodedLength(), is(1)); assertThat(map.get("testTypeInt8").encodedLength(), is(1)); assertThat(map.get("testTypeInt32").encodedLength(), is(4)); assertThat(map.get("testTypeInt64").encodedLength(), is(8)); assertThat(map.get("testTypeUInt8").encodedLength(), is(1)); assertThat(map.get("testTypeUInt16").encodedLength(), is(2)); assertThat(map.get("testTypeUInt32").encodedLength(), is(4)); assertThat(map.get("testTypeUInt64").encodedLength(), is(8)); assertThat(map.get("testTypeFloat").encodedLength(), is(4)); assertThat(map.get("testTypeDouble").encodedLength(), is(8)); } @Test public void shouldReturnCorrectDescriptionForType() throws Exception { final String desc = "basic description attribute of a type element"; final String testXmlString = "<types>" + " <type name=\"testTypeDescription\" primitiveType=\"char\" description=\"" + desc + "\"/>" + "</types>"; final Map<String, Type> map = parseTestXmlWithMap("/types/type", testXmlString); assertThat(map.get("testTypeDescription").description(), is(desc)); } @Test public void shouldReturnNullOnNoDescriptionSet() throws Exception { final String testXmlString = "<types>" + " <type name=\"testTypeNoDescription\" primitiveType=\"char\"/>" + "</types>"; final Map<String, Type> map = parseTestXmlWithMap("/types/type", testXmlString); final String description = map.get("testTypeNoDescription").description(); assertNull(description); } @Test public void shouldReturnCorrectSemanticTypeForType() throws Exception { final String semanticType = "char"; final String testXmlString = "<types>" + " <type name=\"testType\" primitiveType=\"char\" semanticType=\"" + semanticType + "\"/>" + "</types>"; final Map<String, Type> map = parseTestXmlWithMap("/types/type", testXmlString); assertThat(map.get("testType").semanticType(), is(semanticType)); } @Test public void shouldReturnNullWhenSemanticTypeNotSpecified() throws Exception { final String testXmlString = "<types>" + " <type name=\"testType\" primitiveType=\"char\"/>" + "</types>"; final Map<String, Type> map = parseTestXmlWithMap("/types/type", testXmlString); assertNull(map.get("testType").semanticType()); } @Test public void shouldThrowExceptionWhenConstantPresenceButNoDataSpecified() { final String testXmlString = "<types>" + " <type name=\"testTypePresenceConst\" primitiveType=\"char\" presence=\"constant\"></type>" + "</types>"; assertThrows(IllegalArgumentException.class, () -> parseTestXmlWithMap("/types/type", testXmlString)); } @Test public void shouldReturnCorrectPresenceConstantWhenSpecified() throws Exception { final String testXmlString = "<types>" + " <type name=\"testTypePresenceConst\" primitiveType=\"char\" presence=\"constant\">F</type>" + "</types>"; final Map<String, Type> map = parseTestXmlWithMap("/types/type", testXmlString); final String expectedString = "F"; final PrimitiveValue expectedValue = parse(expectedString, PrimitiveType.CHAR); assertThat((((EncodedDataType)map.get("testTypePresenceConst")).constVal()), is(expectedValue)); } @Test public void shouldReturnCorrectConstantStringWhenSpecified() throws Exception { final String strConst = "string constant"; final String testXmlString = "<types>" + " <type name=\"testTypeConstString\" primitiveType=\"char\" presence=\"constant\" " + "length=\"" + strConst.length() + "\"" + ">" + strConst + "</type>" + "</types>"; final Map<String, Type> map = parseTestXmlWithMap("/types/type", testXmlString); assertThat((((EncodedDataType)map.get("testTypeConstString")).constVal()), is(parse(strConst, strConst.length(), "US-ASCII"))); } @Test public void shouldReturnDefaultMinValueWhenSpecified() throws Exception { final String testXmlString = "<types>" + " <type name=\"testTypeDefaultCharMinValue\" primitiveType=\"char\"/>" + "</types>"; final Map<String, Type> map = parseTestXmlWithMap("/types/type", testXmlString); assertNull(((EncodedDataType)map.get("testTypeDefaultCharMinValue")).minValue()); } @Test public void shouldReturnDefaultMaxValueWhenSpecified() throws Exception { final String testXmlString = "<types>" + " <type name=\"testTypeDefaultCharMaxValue\" primitiveType=\"char\"/>" + "</types>"; final Map<String, Type> map = parseTestXmlWithMap("/types/type", testXmlString); assertNull(((EncodedDataType)map.get("testTypeDefaultCharMaxValue")).maxValue()); } @Test public void shouldReturnDefaultNullValueWhenSpecified() throws Exception { final String testXmlString = "<types>" + " <type name=\"testTypeDefaultCharNullValue\" primitiveType=\"char\"/>" + "</types>"; final Map<String, Type> map = parseTestXmlWithMap("/types/type", testXmlString); assertNull(((EncodedDataType)map.get("testTypeDefaultCharNullValue")).nullValue()); } @Test public void shouldReturnCorrectMinValueWhenSpecified() throws Exception { final String minVal = "10"; final String testXmlString = "<types>" + " <type name=\"testTypeInt8MinValue\" primitiveType=\"int8\" minValue=\"" + minVal + "\"/>" + "</types>"; final Map<String, Type> map = parseTestXmlWithMap("/types/type", testXmlString); assertThat((((EncodedDataType)map.get("testTypeInt8MinValue")).minValue()), is(parse(minVal, PrimitiveType.INT8))); } @Test public void shouldReturnCorrectMaxValueWhenSpecified() throws Exception { final String maxVal = "10"; final String testXmlString = "<types>" + " <type name=\"testTypeInt8MaxValue\" primitiveType=\"int8\" maxValue=\"" + maxVal + "\"/>" + "</types>"; final Map<String, Type> map = parseTestXmlWithMap("/types/type", testXmlString); assertThat((((EncodedDataType)map.get("testTypeInt8MaxValue")).maxValue()), is(parse(maxVal, PrimitiveType.INT8))); } @Test public void shouldReturnCorrectNullValueWhenSpecified() throws Exception { final String nullVal = "10"; final String testXmlString = "<types>" + " <type name=\"testTypeInt8NullValue\" primitiveType=\"int8\" presence=\"optional\" nullValue=\"" + nullVal + "\"/>" + "</types>"; final Map<String, Type> map = parseTestXmlWithMap("/types/type", testXmlString); assertThat((((EncodedDataType)map.get("testTypeInt8NullValue")).nullValue()), is(parse(nullVal, PrimitiveType.INT8))); } private static Map<String, Type> parseTestXmlWithMap(final String xPathExpr, final String xml) throws ParserConfigurationException, XPathExpressionException, IOException, SAXException { final Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse( new ByteArrayInputStream(xml.getBytes())); final XPath xPath = XPathFactory.newInstance().newXPath(); final NodeList list = (NodeList)xPath.compile(xPathExpr).evaluate(document, XPathConstants.NODESET); final Map<String, Type> map = new HashMap<>(); final ParserOptions options = ParserOptions.builder().stopOnError(true).suppressOutput(true).build(); document.setUserData(XmlSchemaParser.ERROR_HANDLER_KEY, new ErrorHandler(options), null); for (int i = 0, size = list.getLength(); i < size; i++) { final Type t = new EncodedDataType(list.item(i)); map.put(t.name(), t); } return map; } }