Java tutorial
/* * Copyright 2012 Michael Bischoff * * 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 * * http://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 de.jpaw.bonaparte.core; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import java.util.UUID; import org.joda.time.DateTimeZone; import org.joda.time.Instant; import org.joda.time.LocalDate; import org.joda.time.LocalDateTime; import org.joda.time.LocalTime; import de.jpaw.bonaparte.pojos.meta.AlphanumericElementaryDataItem; import de.jpaw.bonaparte.pojos.meta.BasicNumericElementaryDataItem; import de.jpaw.bonaparte.pojos.meta.BinaryElementaryDataItem; import de.jpaw.bonaparte.pojos.meta.FieldDefinition; import de.jpaw.bonaparte.pojos.meta.MiscElementaryDataItem; import de.jpaw.bonaparte.pojos.meta.NumericElementaryDataItem; import de.jpaw.bonaparte.pojos.meta.ObjectReference; import de.jpaw.bonaparte.pojos.meta.TemporalElementaryDataItem; import de.jpaw.bonaparte.pojos.meta.XEnumDataItem; import de.jpaw.bonaparte.pojos.meta.XEnumDefinition; import de.jpaw.bonaparte.util.BigDecimalTools; import de.jpaw.enums.AbstractXEnumBase; import de.jpaw.enums.XEnumFactory; import de.jpaw.util.ByteArray; /** * The CompactByteArrayParser class. * * @author Michael Bischoff * @version $Revision$ * * Implementation of the MessageParser for the binary compact protocol, using byte arrays. */ public class CompactByteArrayParser extends Settings implements MessageParser<MessageParserException>, CompactConstants { // private static final Logger LOGGER = LoggerFactory.getLogger(CompactByteArrayParser.class); private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; private static final String EMPTY_STRING = ""; private int parseIndex; private int messageLength; private byte[] inputdata; protected String currentClass; private final boolean useCache = true; private List<BonaPortable> objects; /** Quick conversion utility method, for use by code generators. (null safe) */ public static <T extends BonaPortable> T unmarshal(byte[] x, ObjectReference di, Class<T> expectedClass) throws MessageParserException { if (x == null || x.length == 0) return null; return new CompactByteArrayParser(x, 0, -1).readObject(di, expectedClass); } /** Assigns a new source to subsequent parsing operations. */ public final void setSource(byte[] src, int offset, int length) { inputdata = src; parseIndex = offset; messageLength = length; if (useCache) objects.clear(); } /** Assigns a new source to subsequent parsing operations. */ public final void setSource(byte[] src) { inputdata = src; parseIndex = 0; messageLength = src.length; if (useCache) objects.clear(); } /** Create a processor for parsing a buffer. */ public CompactByteArrayParser(byte[] buffer, int offset, int length) { inputdata = buffer; parseIndex = offset; messageLength = length < 0 ? inputdata.length : length; // -1 means full array size currentClass = "N/A"; if (useCache) objects = new ArrayList<BonaPortable>(60); } /************************************************************************************************** * Deserialization goes here. Code below does not use the ByteBuilder class, * but reads from the byte[] directly **************************************************************************************************/ protected void require(int length) throws MessageParserException { if (parseIndex + length > messageLength) { throw newMPE(MessageParserException.PREMATURE_END, null); } } protected int needToken() throws MessageParserException { if (parseIndex >= messageLength) { throw newMPE(MessageParserException.PREMATURE_END, null); } return inputdata[parseIndex++] & 0xff; } protected void needToken(int c) throws MessageParserException { if (parseIndex >= messageLength) { throw newMPE(MessageParserException.PREMATURE_END, String.format("(expected 0x%02x)", c)); } int d = inputdata[parseIndex++] & 0xff; if (c != d) { throw newMPE(MessageParserException.UNEXPECTED_CHARACTER, String.format("(expected 0x%02x, got 0x%02x)", c, d)); } } // check for Null called for field members inside a class protected boolean checkForNullOrNeedToken(FieldDefinition di, int token) throws MessageParserException { return checkForNullOrNeedToken(di.getName(), di.getIsRequired(), token); } // check for Null called for field members inside a class protected boolean checkForNullOrNeedToken(String fieldname, boolean isRequired, int token) throws MessageParserException { int c = needToken(); if (c == token) return false; if (c == NULL_FIELD) { if (!isRequired) { return true; } else { throw newMPE(MessageParserException.ILLEGAL_EXPLICIT_NULL, fieldname); } } if ((c == PARENT_SEPARATOR) || (c == OBJECT_TERMINATOR)) { if (!isRequired) { // uneat it --parseIndex; return true; } else { throw newMPE(MessageParserException.ILLEGAL_IMPLICIT_NULL, fieldname); } } throw newMPE(MessageParserException.UNEXPECTED_CHARACTER, String.format("(expected 0x%02x, got 0x%02x)", token, c)); } /** Check for Null. Returns true if null has been encountered and was allowed. Throws an exception in case it was not allowed. Returns false * if no null is next. (Called for field members inside a class.) */ protected boolean checkForNull(FieldDefinition di) throws MessageParserException { return checkForNull(di.getName(), di.getIsRequired()); } // check for Null called for field members inside a class protected boolean checkForNull(String fieldname, boolean isRequired) throws MessageParserException { int c = needToken(); if (c == NULL_FIELD) { if (!isRequired) { return true; } else { throw newMPE(MessageParserException.ILLEGAL_EXPLICIT_NULL, fieldname); } } if ((c == PARENT_SEPARATOR) || (c == OBJECT_TERMINATOR)) { if (!isRequired) { // uneat it --parseIndex; return true; } else { throw newMPE(MessageParserException.ILLEGAL_IMPLICIT_NULL, fieldname); } } --parseIndex; return false; } // upon entry, we know that firstByte is not null (0xa0) protected int readInt(int firstByte, String fieldname) throws MessageParserException { if (firstByte < 0xa0) { // 1 positive byte numbers if (firstByte <= 31) return firstByte; if (firstByte >= 0x80) return firstByte - 0x60; // 0x20..0x3f throw newMPE(MessageParserException.ILLEGAL_CHAR_NOT_NUMERIC, fieldname); } if (firstByte <= 0xd0) { if (firstByte <= 0xaa) return 0xa0 - firstByte; // -1 .. -10 if (firstByte < 0xc0) throw newMPE(MessageParserException.ILLEGAL_CHAR_NOT_NUMERIC, fieldname); // 2 byte number 0...2047 return needToken() + ((firstByte & 0x0f) << 8); } switch (firstByte) { case INT_2BYTE: short n = (short) (needToken() << 8); n += needToken(); return n; case INT_3BYTE: require(3); int nn = (inputdata[parseIndex++] & 0xff) << 16; nn += (inputdata[parseIndex++] & 0xff) << 8; nn += inputdata[parseIndex++] & 0xff; if ((nn & 0x800000) != 0) nn |= 0xff << 24; // sign-extend return nn; case INT_4BYTE: return readFixed4ByteInt(); default: throw newMPE(MessageParserException.ILLEGAL_CHAR_NOT_NUMERIC, fieldname); } } protected int readFixed4ByteInt() throws MessageParserException { require(4); int nn = (inputdata[parseIndex++] & 0xff) << 24; nn += (inputdata[parseIndex++] & 0xff) << 16; nn += (inputdata[parseIndex++] & 0xff) << 8; nn += inputdata[parseIndex++] & 0xff; return nn; } protected long readFixed6ByteLong() throws MessageParserException { require(6); int nn1 = inputdata[parseIndex++] << 8; nn1 |= inputdata[parseIndex++] & 0xff; int nn2 = readFixed4ByteInt(); return ((long) nn1 << 32) | (nn2 & 0xffffffffL); } protected long readFixed8ByteLong() throws MessageParserException { int nn1 = readFixed4ByteInt(); int nn2 = readFixed4ByteInt(); return ((long) nn1 << 32) | (nn2 & 0xffffffffL); } protected long readLong(int firstByte, String fieldname) throws MessageParserException { if (firstByte == INT_6BYTE) return readFixed6ByteLong(); if (firstByte == INT_8BYTE) return readFixed8ByteLong(); return readInt(firstByte, fieldname); } protected void skipNulls() { while (parseIndex < messageLength) { int c = inputdata[parseIndex] & 0xff; if (c != NULL_FIELD) { break; } // skip trailing NULL objects ++parseIndex; } } @Override public UUID readUUID(MiscElementaryDataItem di) throws MessageParserException { if (checkForNullOrNeedToken(di, COMPACT_UUID)) return null; long msl = readFixed8ByteLong(); long lsl = readFixed8ByteLong(); return new UUID(msl, lsl); } @Override public MessageParserException enumExceptionConverter(IllegalArgumentException e) { return newMPE(MessageParserException.INVALID_ENUM_TOKEN, e.getMessage()); } @Override public MessageParserException customExceptionConverter(String msg, Exception e) { return newMPE(MessageParserException.CUSTOM_OBJECT_EXCEPTION, e != null ? msg + e.toString() : msg); } protected MessageParserException newMPE(int errorCode, String msg) { return new MessageParserException(errorCode, msg, parseIndex, currentClass); } @Override public void setClassName(String newClassName) { currentClass = newClassName; } @Override public <T extends AbstractXEnumBase<T>> T readXEnum(XEnumDataItem di, XEnumFactory<T> factory) throws MessageParserException { XEnumDefinition spec = di.getBaseXEnum(); String scannedToken = readString(di.getName(), di.getIsRequired() && !spec.getHasNullToken()); if (scannedToken == null) return factory.getNullToken(); T value = factory.getByToken(scannedToken); if (value == null) { throw newMPE(MessageParserException.INVALID_ENUM_TOKEN, scannedToken); } return value; } @Override public BigDecimal readBigDecimal(NumericElementaryDataItem di) throws MessageParserException { if (checkForNull(di)) return null; String fieldname = di.getName(); int scale = 0; int c = needToken(); if (c == 0) return BigDecimal.ZERO; if (c >= COMPACT_BIGDECIMAL && c <= COMPACT_BIGDECIMAL + 9) { // BigDecimal with scale if (c != COMPACT_BIGDECIMAL) { scale = c - COMPACT_BIGDECIMAL; } else { scale = readInt(needToken(), fieldname); } } // now read mantissa. Either length + digits, or an integer BigDecimal r; c = needToken(); if (c == COMPACT_BIGINTEGER) { // length and mantissa int len = readInt(needToken(), fieldname); require(len); byte[] mantissa = new byte[len]; System.arraycopy(inputdata, parseIndex, mantissa, 0, len); parseIndex += len; r = new BigDecimal(new BigInteger(mantissa), scale); } else { c = readInt(c, fieldname); r = BigDecimal.valueOf(c, scale); } return BigDecimalTools.checkAndScale(r, di, parseIndex, currentClass); } @Override public Character readCharacter(MiscElementaryDataItem di) throws MessageParserException { if (checkForNull(di)) return null; return Character.valueOf(readPrimitiveCharacter(di)); } @Override public Boolean readBoolean(MiscElementaryDataItem di) throws MessageParserException { if (checkForNull(di)) return null; int c = needToken(); if (c == 0) return Boolean.FALSE; if (c != 1) throw newMPE(MessageParserException.UNEXPECTED_CHARACTER, String.format("(expected BOOLEAN 0/1, got 0x%02x)", c)); return Boolean.TRUE; } @Override public Double readDouble(BasicNumericElementaryDataItem di) throws MessageParserException { if (checkForNull(di)) return null; return Double.valueOf(readPrimitiveDouble(di)); } @Override public Float readFloat(BasicNumericElementaryDataItem di) throws MessageParserException { if (checkForNull(di)) return null; return Float.valueOf(readPrimitiveFloat(di)); } @Override public Long readLong(BasicNumericElementaryDataItem di) throws MessageParserException { if (checkForNull(di)) return null; return readLong(needToken(), di.getName()); } @Override public Integer readInteger(BasicNumericElementaryDataItem di) throws MessageParserException { if (checkForNull(di)) return null; return readInt(needToken(), di.getName()); } @Override public Short readShort(BasicNumericElementaryDataItem di) throws MessageParserException { if (checkForNull(di)) return null; return (short) readInt(needToken(), di.getName()); } @Override public Byte readByte(BasicNumericElementaryDataItem di) throws MessageParserException { if (checkForNull(di)) return null; return (byte) readInt(needToken(), di.getName()); } @Override public BigInteger readBigInteger(BasicNumericElementaryDataItem di) throws MessageParserException { if (checkForNull(di)) return null; int c = needToken(); if (c == COMPACT_BIGINTEGER) { // length and mantissa int len = readInt(needToken(), di.getName()); require(len); byte[] mantissa = new byte[len]; System.arraycopy(inputdata, parseIndex, mantissa, 0, len); parseIndex += len; return new BigInteger(mantissa); } else { c = readInt(c, di.getName()); return BigInteger.valueOf(c); } } protected String readAscii(int len, String fieldname) throws MessageParserException { require(len); char data[] = new char[len]; for (int i = 0; i < len; ++i) data[i] = (char) (inputdata[parseIndex++] & 0xff); return new String(data); } @Override public String readAscii(AlphanumericElementaryDataItem di) throws MessageParserException { return readString(di.getName(), di.getIsRequired()); } protected String readString(String fieldname, boolean isRequired) throws MessageParserException { if (checkForNull(fieldname, isRequired)) return null; int len; String result; int c = needToken(); if (c >= 0x20 && c < 0x80) return String.valueOf((char) c); // single ASCII byte string if (c >= SHORT_ASCII_STRING && c <= SHORT_ASCII_STRING + 15) { len = c - SHORT_ASCII_STRING + 1; return readAscii(len, fieldname); } try { switch (c) { case EMPTY_FIELD: return EMPTY_STRING; case UNICODE_CHAR: require(2); char cc = (char) (((inputdata[parseIndex] & 0xff) << 8) | (inputdata[parseIndex + 1] & 0xff)); parseIndex += 2; return String.valueOf(cc); // single Unicode char string case ASCII_STRING: len = readInt(needToken(), fieldname); // return readAscii(len, di.getName()); require(len); result = new String(inputdata, parseIndex, len, CHARSET_ASCII); parseIndex += len; return result; case UTF8_STRING: len = readInt(needToken(), fieldname); require(len); result = new String(inputdata, parseIndex, len, CHARSET_UTF8); parseIndex += len; return result; case UTF16_STRING: len = 2 * readInt(needToken(), fieldname); // * 2 because we have 2 byte per character and any code below measures in bytes require(len); result = new String(inputdata, parseIndex, len, CHARSET_UTF16); parseIndex += len; return result; default: throw newMPE(MessageParserException.UNEXPECTED_CHARACTER, String.format("(expected STRING*, got 0x%02x)", c)); } } catch (UnsupportedEncodingException e) { throw newMPE(MessageParserException.ILLEGAL_CHAR_ASCII, String.format("(encoding %02x)", c)); } } @Override public String readString(AlphanumericElementaryDataItem di) throws MessageParserException { return readString(di.getName(), di.getIsRequired()); } @Override public ByteArray readByteArray(BinaryElementaryDataItem di) throws MessageParserException { if (checkForNull(di)) return null; int c = needToken(); switch (c) { case EMPTY_FIELD: return ByteArray.ZERO_BYTE_ARRAY; case COMPACT_BINARY: int len = readInt(needToken(), di.getName()); ByteArray result = new ByteArray(inputdata, parseIndex, len); parseIndex += len; return result; default: throw newMPE(MessageParserException.UNEXPECTED_CHARACTER, String.format("(expected BINARY*, got 0x%02x)", c)); } } @Override public byte[] readRaw(BinaryElementaryDataItem di) throws MessageParserException { if (checkForNull(di)) return null; int c = needToken(); switch (c) { case EMPTY_FIELD: return EMPTY_BYTE_ARRAY; case COMPACT_BINARY: int len = readInt(needToken(), di.getName()); byte[] data = new byte[len]; System.arraycopy(inputdata, parseIndex, data, 0, len); parseIndex += len; return data; default: throw newMPE(MessageParserException.UNEXPECTED_CHARACTER, String.format("(expected BINARY*, got 0x%02x)", c)); } } @Override public LocalDate readDay(TemporalElementaryDataItem di) throws MessageParserException { if (checkForNullOrNeedToken(di, COMPACT_DATE)) return null; String fieldname = di.getName(); int year = readInt(needToken(), fieldname); int month = readInt(needToken(), fieldname); int day = readInt(needToken(), fieldname); return new LocalDate(year, month, day); } @Override public LocalTime readTime(TemporalElementaryDataItem di) throws MessageParserException { if (checkForNull(di)) return null; int c = needToken(); switch (c) { case COMPACT_TIME_MILLIS: return new LocalTime(readInt(needToken(), di.getName()), DateTimeZone.UTC); case COMPACT_TIME: return new LocalTime(readInt(needToken(), di.getName()) * 1000L, DateTimeZone.UTC); default: throw newMPE(MessageParserException.UNEXPECTED_CHARACTER, String.format("(expected COMPACT_TIME_*, got 0x%02x)", c)); } } @Override public LocalDateTime readDayTime(TemporalElementaryDataItem di) throws MessageParserException { if (checkForNull(di)) return null; boolean fractional = false; int c = needToken(); switch (c) { case COMPACT_DATETIME: break; case COMPACT_DATETIME_MILLIS: fractional = true; break; default: throw newMPE(MessageParserException.UNEXPECTED_CHARACTER, String.format("(expected COMPACT_DATETIME_*, got 0x%02x)", c)); } String fieldname = di.getName(); int year = readInt(needToken(), fieldname); int month = readInt(needToken(), fieldname); int day = readInt(needToken(), fieldname); int secondsOfDay = readInt(needToken(), fieldname); int millis = 0; if (fractional) { millis = secondsOfDay % 1000; secondsOfDay /= 1000; } return new LocalDateTime(year, month, day, secondsOfDay / 3600, (secondsOfDay % 3600) / 60, secondsOfDay % 60, millis); } @Override public Instant readInstant(TemporalElementaryDataItem di) throws MessageParserException { if (checkForNull(di)) return null; return new Instant(readLong(needToken(), di.getName())); } @Override public <R extends BonaPortable> R readObject(ObjectReference di, Class<R> type) throws MessageParserException { if (checkForNull(di)) { return null; } boolean allowSubtypes = di.getAllowSubclasses(); String fieldname = di.getName(); int c = needToken(); if (useCache && c == OBJECT_AGAIN) { // we reuse an object int objectIndex = readInt(needToken(), fieldname); if (objectIndex >= objects.size()) throw newMPE(MessageParserException.INVALID_BACKREFERENCE, String.format( "at %s: requested object %d of only %d available", fieldname, objectIndex, objects.size())); BonaPortable newObject = objects.get(objects.size() - 1 - objectIndex); // 0 is the last one put in, 1 the one before last etc... // check if the object is of expected type if (newObject.getClass() != type) { // check if it is a superclass if (!allowSubtypes || !type.isAssignableFrom(newObject.getClass())) { throw newMPE(MessageParserException.BAD_CLASS, String.format("(got %s, expected %s for %s, subclassing = %b)", newObject.getClass().getSimpleName(), type.getSimpleName(), fieldname, allowSubtypes)); } } return type.cast(newObject); } else if (c == OBJECT_BEGIN_PQON || c == OBJECT_BEGIN_ID || c == OBJECT_BEGIN_BASE) { String previousClass = currentClass; BonaPortable newObject; String classname; if (c == OBJECT_BEGIN_ID) { int factoryId = readInt(needToken(), "$factoryId"); int classId = readInt(needToken(), "$classId"); BonaPortableClass<?> bclass = BonaPortableFactoryById.getByIds(factoryId, classId); if (bclass == null) throw newMPE(MessageParserException.BAD_CLASS_IDS, factoryId + "/" + classId); classname = bclass.getPqon(); newObject = bclass.newInstance(); } else { if (c == OBJECT_BEGIN_BASE) { if (di.getLowerBound() == null) throw newMPE(MessageParserException.INVALID_BASE_CLASS_REFERENCE, ""); classname = di.getLowerBound().getName(); } else { classname = readString(fieldname, false); if (classname == null || classname.length() == 0) { if (di.getLowerBound() == null) throw newMPE(MessageParserException.INVALID_BASE_CLASS_REFERENCE, ""); // the base class name is referred to, which is contained in the meta data classname = di.getLowerBound().getName(); } needToken(NULL_FIELD); // version not yet allowed } newObject = BonaPortableFactory.createObject(classname); } // System.out.println("Creating new obj " + classname + " gave me " + newObject); // check if the object is of expected type if (newObject.getClass() != type) { // check if it is a superclass if (!allowSubtypes || !type.isAssignableFrom(newObject.getClass())) { throw newMPE(MessageParserException.BAD_CLASS, String.format("(got %s, expected %s for %s, subclassing = %b)", newObject.getClass().getSimpleName(), type.getSimpleName(), fieldname, allowSubtypes)); } } // all good here. Parse the contents // if we use the cache, make the object known even before the contents has been parsed, because it may be referenced if the structure is cyclic if (useCache) objects.add(newObject); currentClass = classname; newObject.deserialize(this); skipNulls(); needToken(OBJECT_TERMINATOR); currentClass = previousClass; return type.cast(newObject); } else { throw newMPE(MessageParserException.UNEXPECTED_CHARACTER, String.format("(expected OBJECT_START*, got 0x%02x)", c)); } } @Override public int parseMapStart(FieldDefinition di) throws MessageParserException { if (checkForNullOrNeedToken(di.getName(), di.getIsAggregateRequired(), MAP_BEGIN)) return COLLECTION_COUNT_NULL; return readInt(needToken(), di.getName()); } @Override public int parseArrayStart(FieldDefinition di, int sizeOfElement) throws MessageParserException { if (checkForNullOrNeedToken(di.getName(), di.getIsAggregateRequired(), ARRAY_BEGIN)) return COLLECTION_COUNT_NULL; return readInt(needToken(), di.getName()); } @Override public void parseArrayEnd() throws MessageParserException { } @Override public BonaPortable readRecord() throws MessageParserException { // there are no record start/end markers in this format return readObject(StaticMeta.OUTER_BONAPORTABLE, BonaPortable.class); } @Override public List<BonaPortable> readTransmission() throws MessageParserException { List<BonaPortable> results = new ArrayList<BonaPortable>(); while (parseIndex < messageLength) results.add(readRecord()); return results; } @Override public void eatParentSeparator() throws MessageParserException { skipNulls(); // upwards compatibility: skip extra fields if they are blank. needToken(PARENT_SEPARATOR); } @Override public boolean readPrimitiveBoolean(MiscElementaryDataItem di) throws MessageParserException { int c = needToken(); if (c == 0) return false; if (c != 1) throw newMPE(MessageParserException.UNEXPECTED_CHARACTER, String.format("(expected BOOLEAN 0/1, got 0x%02x)", c)); return true; } // default implementations for the next ones... @Override public char readPrimitiveCharacter(MiscElementaryDataItem di) throws MessageParserException { int c = needToken(); if (c >= 0x20 && c < 0x80) return (char) c; // single byte char if (c != UNICODE_CHAR) throw newMPE(MessageParserException.UNEXPECTED_CHARACTER, String.format("(expected UNICODE_CHAR, got 0x%02x)", c)); require(2); char cc = (char) (((inputdata[parseIndex] & 0xff) << 8) | (inputdata[parseIndex + 1] & 0xff)); parseIndex += 2; return cc; } @Override public double readPrimitiveDouble(BasicNumericElementaryDataItem di) throws MessageParserException { int c = needToken(); if (c == COMPACT_DOUBLE) { return Double.longBitsToDouble(readFixed8ByteLong()); } // not a float, try upgrade of int to double (doubles of value 0 or 1 are explicitly written as int) return readInt(c, di.getName()); } @Override public float readPrimitiveFloat(BasicNumericElementaryDataItem di) throws MessageParserException { int c = needToken(); if (c == COMPACT_FLOAT) { return Float.intBitsToFloat(readFixed4ByteInt()); } // not a float, try upgrade of int to float (floats of value 0 or 1 are explicitly written as int) return readInt(c, di.getName()); } @Override public long readPrimitiveLong(BasicNumericElementaryDataItem di) throws MessageParserException { return readLong(needToken(), di.getName()); } @Override public int readPrimitiveInteger(BasicNumericElementaryDataItem di) throws MessageParserException { return readInt(needToken(), di.getName()); } @Override public short readPrimitiveShort(BasicNumericElementaryDataItem di) throws MessageParserException { return (short) readInt(needToken(), di.getName()); } @Override public byte readPrimitiveByte(BasicNumericElementaryDataItem di) throws MessageParserException { return (byte) readInt(needToken(), di.getName()); } }