/* * 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.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; import java.text.DecimalFormat; import java.text.NumberFormat; import java.text.ParseException; import java.util.List; import java.util.Map; import java.util.UUID; import org.joda.time.Instant; import org.joda.time.LocalDate; import org.joda.time.LocalDateTime; import org.joda.time.LocalTime; import org.joda.time.format.DateTimeFormatter; 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.Base64; import de.jpaw.util.ByteArray; import de.jpaw.util.CharTestsASCII; import de.jpaw.util.IntegralLimits; /** * The StringCSVParser class. * * @author Michael Bischoff * @version $Revision$ * * Implements the deserialization of fixed width an quote-less CSV formats. * Right now, only limited subsets are implemented. Especially date / time parsing is very limited. */ // TODO: should we convert "work" from String to CharSequence to make it more general? public final class StringCSVParser extends AbstractPartialJsonStringParser implements MessageParser<MessageParserException>, StringBuilderConstants { protected final CSVConfiguration cfg; private final boolean fixedLength; private final int lengthOfBoolean; private String work; // for parser. No longer final, as reusing the parser object makes sense due to the high number of datetime formatters constructed private int parseIndex; // for parser private int messageLength; // for parser private String currentClass; protected final DateTimeFormatter dayFormat; // day without time (Joda) protected final DateTimeFormatter timeFormat; // time on second precision (Joda) protected final DateTimeFormatter time3Format; // time on millisecond precision (Joda) protected final DateTimeFormatter timestampFormat; // day and time on second precision (Joda) protected final DateTimeFormatter timestamp3Format; // day and time on millisecond precision (Joda) protected final int dayFormatLength; // day without time (Joda) protected final int timeFormatLength; // time on second precision (Joda) protected final int time3FormatLength; // time on millisecond precision (Joda) protected final int timestampFormatLength; // day and time on second precision (Joda) protected final int timestamp3FormatLength; // day and time on millisecond precision (Joda) protected CSVObjectTypeDetector objectTypeDetector = null; protected NumberFormat localBigDecimalFormat = null; // if set (via setNationalBigDecimal) protected NumberFormat localFloatFormat = null; // if set (via setNationalFloat) public void setNationalBigDecimal() { localBigDecimalFormat = NumberFormat.getInstance(cfg.locale); if (localBigDecimalFormat instanceof DecimalFormat) { ((DecimalFormat)localBigDecimalFormat).setParseBigDecimal(true); } } public void setNationalFloat() { localFloatFormat = NumberFormat.getInstance(cfg.locale); } /** Define the method to guess the type of the record by inspecting its contents. */ public static interface CSVObjectTypeDetector { Class<? extends BonaPortable> typeByContents(String msg) throws MessageParserException; } public static abstract class AbstractCSVObjectTypeDetector implements CSVObjectTypeDetector { protected final Map<String, Class<? extends BonaPortable>> recordMap; public AbstractCSVObjectTypeDetector(Map<String, Class<? extends BonaPortable>> recordMap) { this.recordMap = recordMap; } } /** Determines the object type based on the contents of the first field, using a delimiter. */ public static class DelimiterBasedObjectTypeDetector extends AbstractCSVObjectTypeDetector { protected final String delimiter; public DelimiterBasedObjectTypeDetector(Map<String, Class<? extends BonaPortable>> recordMap, String delimiter) { super(recordMap); this.delimiter = delimiter; } @Override public Class<? extends BonaPortable> typeByContents(String msg) throws MessageParserException { int pos = msg.indexOf(delimiter); String key = pos < 0 ? msg : msg.substring(0, pos); // if pos < 0: record types such as "EOF" etc... these are valid return recordMap.get(key.trim()); } } /** Determines the object type based on the contents of the first n characters. */ public static class FixedWidthObjectTypeDetector extends AbstractCSVObjectTypeDetector { protected final int widthOfFirstField; public FixedWidthObjectTypeDetector(Map<String, Class<? extends BonaPortable>> recordMap, int widthOfFirstField) { super(recordMap); this.widthOfFirstField = widthOfFirstField; } @Override public Class<? extends BonaPortable> typeByContents(String msg) throws MessageParserException { String key = msg.length() < widthOfFirstField ? msg : msg.substring(0, widthOfFirstField); return recordMap.get(key.trim()); } } /** Defines the portion of src from offset (inclusive) to length (exclusive) as parsing source, i.e. length - offset characters. */ public final void setSource(String src, int offset, int length) { // auto-truncate CR/LF, if it exists if (length > 0 && src.charAt(length-1) == '\n') { --length; } if (length > 0 && src.charAt(length-1) == '\r') { --length; } if (length < src.length()) { // some truncation done: remove it from the buffer! work = src.substring(offset, length); parseIndex = 0; messageLength = work.length(); } else { // a copy is not needed work = src; parseIndex = offset; messageLength = length; } } /** Defines src as parsing source. */ public final void setSource(String src) { setSource(src, 0, src.length()); } public StringCSVParser(CSVConfiguration cfg, String work) { // strip CR/LF from input, if existing setSource(work); this.cfg = cfg; this.lengthOfBoolean = cfg.booleanFalse.length() > cfg.booleanTrue.length() ? cfg.booleanFalse.length() : cfg.booleanTrue.length(); this.dayFormat = cfg.determineDayFormatter().withLocale(cfg.locale).withZoneUTC(); this.timeFormat = cfg.determineTimeFormatter().withLocale(cfg.locale).withZoneUTC(); this.time3Format = cfg.determineTime3Formatter().withLocale(cfg.locale).withZoneUTC(); this.timestampFormat = cfg.determineTimestampFormatter().withLocale(cfg.locale).withZoneUTC(); this.timestamp3Format = cfg.determineTimestamp3Formatter().withLocale(cfg.locale).withZoneUTC(); this.dayFormatLength = cfg.customDayFormat == null ? 8 : cfg.customDayFormat.length(); this.timeFormatLength = cfg.customTimeFormat == null ? 6 : cfg.customTimeFormat.length(); this.time3FormatLength = cfg.customTimeWithMsFormat == null ? 9 : cfg.customTimeWithMsFormat.length(); this.timestampFormatLength = cfg.customTimestampFormat == null ? 14 : cfg.customTimestampFormat.length(); this.timestamp3FormatLength = cfg.customTimestampWithMsFormat == null ? 17 : cfg.customTimestampWithMsFormat.length(); fixedLength = cfg.separator.length() == 0; currentClass = "N/A"; } public StringCSVParser(CSVConfiguration cfg, String work, CSVObjectTypeDetector objectTypeDetector) { this(cfg, work); this.objectTypeDetector = objectTypeDetector; } // setting this allows to use readRecord in subsequent calls public void setMapping(CSVObjectTypeDetector objectTypeDetector) { this.objectTypeDetector = objectTypeDetector; } @Override protected MessageParserException newMPE(int errorCode, FieldDefinition di, String msg) { return new MessageParserException(errorCode, di.getName(), parseIndex, currentClass, msg); } /************************************************************************************************** * Deserialization goes here **************************************************************************************************/ protected String processTrailingSigns(String token) { token = token.trim(); // check for trailing sign int l = token.length(); if (l > 0 && token.charAt(l-1) == '-') token = "-" + token.substring(0, l-1); // move sign to the start of the string return token; } private String getField(String fieldname, boolean isRequired, int length) throws MessageParserException { // System.out.println("parsing " + fieldname + " for length " + length); String result = null; if (fixedLength) { if (parseIndex == messageLength) { // implicit null at field boundary: fall through length = 0; result = ""; // not required, but avoid warnings below... } else if (parseIndex + length <= messageLength) { // have sufficient length result = work.substring(parseIndex, parseIndex+length); parseIndex += length; } else { // record ends within a field! // insufficient length: throw an exception if incomplete field, or maybe allow an implicit null // commented out, allow for that and fill with blanks // throw new MessageParserException(MessageParserException.PREMATURE_END, // String.format("(remaining length %d, expected %d)", messageLength - parseIndex, length), parseIndex, currentClass); // adjust length instead length = messageLength - parseIndex; result = work.substring(parseIndex, messageLength); parseIndex = messageLength; } // implicitly strip trailing spaces while (length > 0 && result.charAt(length - 1) == ' ') { --length; } result = length == 0 ? null : result.substring(0, length); // ending here, implicit end // fall through with null } else { int index = work.indexOf(cfg.separator, parseIndex); if (index < 0) { result = work.substring(parseIndex); parseIndex = messageLength; } else { result = work.substring(parseIndex, index); parseIndex = index + 1; // skip field and separator } if (result.length() == 0) result = null; } if (result == null && isRequired) throw new MessageParserException(MessageParserException.ILLEGAL_EXPLICIT_NULL, fieldname, parseIndex, currentClass); return result; } @Override public Boolean readBoolean(MiscElementaryDataItem di) throws MessageParserException { String token = getField(di.getName(), di.getIsRequired(), lengthOfBoolean); if (token == null) return null; if (token.equals(cfg.booleanTrue)) return Boolean.TRUE; if (token.equals(cfg.booleanFalse)) return Boolean.FALSE; throw new MessageParserException(MessageParserException.ILLEGAL_BOOLEAN, String.format("(%s, expected %s or %s for %s)", token, cfg.booleanTrue, cfg.booleanFalse, di.getName()), parseIndex, currentClass); } @Override public String readAscii(AlphanumericElementaryDataItem di) throws MessageParserException { return readString(di.getName(), di.getIsRequired(), di.getLength(), di.getDoTrim(), di.getDoTruncate(), di.getAllowControlCharacters(), false); } // readString does the job for Unicode as well as ASCII @Override public String readString(AlphanumericElementaryDataItem di) throws MessageParserException { return readString(di.getName(), di.getIsRequired(), di.getLength(), di.getDoTrim(), di.getDoTruncate(), di.getAllowControlCharacters(), true); } protected String readString(String fieldname, boolean isRequired, int length, boolean doTrim, boolean doTruncate, boolean allowCtrls, boolean allowUnicode) throws MessageParserException { String token = getField(fieldname, isRequired, length); if (token == null) return null; if (doTrim) { token = token.trim(); // CSV formats trim by default } for (int i = 0; i < token.length(); ++i) { char c = token.charAt(i); if (allowUnicode) { // checks for Unicode characters if (c < ' ') { if (c != '\t' && !allowCtrls) { // special control character, not TAB, and not allowed throw new MessageParserException(MessageParserException.ILLEGAL_CHAR_CTRL, fieldname, parseIndex, currentClass); } } } else { if (!CharTestsASCII.isAsciiPrintable(c)) { throw new MessageParserException(MessageParserException.ILLEGAL_CHAR_ASCII, String.format("(found 0x%02x for %s)", (int)c, fieldname), parseIndex, currentClass); } } } if (length > 0) { // have limits on max size if (token.length() > length) { if (doTruncate) { token = token.substring(0, length); } else { throw new MessageParserException(MessageParserException.STRING_TOO_LONG, String.format("(exceeds length %d for %s, got so far %s)", length, fieldname, token), parseIndex, currentClass); } } } return token; } @Override public BigDecimal readBigDecimal(NumericElementaryDataItem di) throws MessageParserException { int extra = fixedLength ? (di.getDecimalDigits() > 0 && !cfg.removePoint4BD ? 1 : 0) + (di.getIsSigned() ? 1 : 0) : 0; String token = getField(di.getName(), di.getIsRequired(), di.getTotalDigits() + extra); if (token == null) return null; BigDecimal r; if (localBigDecimalFormat != null) { try { r = (BigDecimal) localBigDecimalFormat.parse(processTrailingSigns(token)); } catch (ParseException e) { throw new MessageParserException(MessageParserException.NUMBER_PARSING_ERROR, di.getName(), parseIndex, currentClass); } } else { try { r = new BigDecimal(processTrailingSigns(token)); } catch (NumberFormatException e) { throw new MessageParserException(MessageParserException.NUMBER_PARSING_ERROR, di.getName(), parseIndex, currentClass); } } return BigDecimalTools.checkAndScale(r, di, parseIndex, currentClass); } @Override public Character readCharacter(MiscElementaryDataItem di) throws MessageParserException { String tmp = readString(di.getName(), di.getIsRequired(), 1, false, false, true, true); if (tmp == null) { return null; } if (tmp.length() == 0) { throw new MessageParserException(MessageParserException.EMPTY_CHAR, di.getName(), parseIndex, currentClass); } return tmp.charAt(0); } @Override public ByteArray readByteArray(BinaryElementaryDataItem di) throws MessageParserException { byte [] tmp = readRaw(di); if (tmp == null) { return null; } return new ByteArray(tmp); // TODO: this call does an unnecessary copy } @Override public byte[] readRaw(BinaryElementaryDataItem di) throws MessageParserException { String token = getField(di.getName(), di.getIsRequired(), di.getLength()); if (token == null) return null; try { byte [] btmp = token.getBytes(); return Base64.decode(btmp, 0, btmp.length); } catch (IllegalArgumentException e) { throw new MessageParserException(MessageParserException.BASE64_PARSING_ERROR, di.getName(), parseIndex, currentClass); } // return DatatypeConverter.parseHexBinary(tmp); } @Override public LocalDateTime readDayTime(TemporalElementaryDataItem di) throws MessageParserException { String token = getField(di.getName(), di.getIsRequired(), di.getFractionalSeconds() > 0 ? timestamp3FormatLength : timestampFormatLength); if (token == null) return null; try { if (di.getFractionalSeconds() > 0) return timestamp3Format.parseLocalDateTime(token); else return timestampFormat.parseLocalDateTime(token); } catch (Exception e) { throw new MessageParserException(MessageParserException.ILLEGAL_CALENDAR_VALUE, di.getName(), parseIndex, currentClass); } } @Override public LocalDate readDay(TemporalElementaryDataItem di) throws MessageParserException { String token = getField(di.getName(), di.getIsRequired(), dayFormatLength); if (token == null) return null; try { return dayFormat.parseLocalDate(token); } catch (Exception e) { throw new MessageParserException(MessageParserException.ILLEGAL_CALENDAR_VALUE, di.getName(), parseIndex, currentClass); } } @Override public LocalTime readTime(TemporalElementaryDataItem di) throws MessageParserException { String token = getField(di.getName(), di.getIsRequired(), di.getFractionalSeconds() > 0 ? timestamp3FormatLength : timestampFormatLength); if (token == null) return null; try { if (di.getFractionalSeconds() > 0) return time3Format.parseLocalTime(token); else return timeFormat.parseLocalTime(token); } catch (Exception e) { throw new MessageParserException(MessageParserException.ILLEGAL_CALENDAR_VALUE, di.getName(), parseIndex, currentClass); } } @Override public Instant readInstant(TemporalElementaryDataItem di) throws MessageParserException { String tmp = getField(di.getName(), di.getIsRequired(), 19); if (tmp == null) return null; int millis = 0; long seconds = 0; int dpoint; if ((dpoint = tmp.indexOf('.')) < 0) { seconds = Long.parseLong(tmp); // only seconds } else { // seconds and millis seconds seconds = Long.parseLong(tmp.substring(0, dpoint)); millis = Integer.parseInt(tmp.substring(dpoint + 1)); switch (tmp.length() - dpoint - 1) { // i.e. number of fractional digits case 2: millis *= 10; break; case 1: millis *= 100; break; case 3: break; // maximum resolution (milliseconds) default: // something weird throw new MessageParserException( MessageParserException.BAD_TIMESTAMP_FRACTIONALS, String.format("(found %d for %s)", tmp.length() - dpoint - 1, di.getName()), parseIndex, currentClass); } } return new Instant(1000L * seconds + millis); } @Override public int parseMapStart(FieldDefinition di) throws MessageParserException { throw new MessageParserException(MessageParserException.UNSUPPORTED_DATA_TYPE, di.getName(), parseIndex, currentClass); } @Override public int parseArrayStart(FieldDefinition di, int sizeOfChild) throws MessageParserException { String token = getField(di.getName(), false, 9); if (token == null) return -1; token = token.trim(); if (token == null || token.length() == 0) { if (di.getIsAggregateRequired()) throw new MessageParserException(MessageParserException.NULL_COLLECTION_NOT_ALLOWED, di.getName(), parseIndex, currentClass); return -1; } int n = Integer.parseInt(token); if ((n < 0) || (n > 1000000000)) { throw new MessageParserException(MessageParserException.ARRAY_SIZE_OUT_OF_BOUNDS, String.format("(got %d entries (0x%x) for %s)", n, n, di.getName()), parseIndex, currentClass); } return n; } @Override public void parseArrayEnd() throws MessageParserException { } @Override public BonaPortable readRecord() throws MessageParserException { if (objectTypeDetector == null) { // parsing an arbitrary object is not possible here because we have no type information throw new MessageParserException(MessageParserException.UNSUPPORTED_DATA_TYPE, "readRecord()", parseIndex, currentClass); } Class<? extends BonaPortable> mappedClass = objectTypeDetector.typeByContents(work); if (mappedClass == null) throw new MessageParserException(MessageParserException.UNKNOW_RECORD_TYPE, work, parseIndex, currentClass); return readObject(StaticMeta.OUTER_BONAPORTABLE_FOR_CSV, mappedClass); } private String readBufferForInteger(BasicNumericElementaryDataItem di) throws MessageParserException { String tmp = getField(di.getName(), di.getIsRequired(), di.getTotalDigits() + (di.getIsSigned() ? 1 : 0) + (!cfg.removePoint4BD && di.getDecimalDigits() > 0 ? 1 : 0)); if (tmp != null) { tmp = tmp.trim(); if (tmp.length() == 0) return null; } return tmp; } private long postProcessForImplicitDecimals(BasicNumericElementaryDataItem di, String token) throws MessageParserException { token = processTrailingSigns(token); int decimals = di.getDecimalDigits(); // run it through a BigDecimal for now... BigDecimal tmp = new BigDecimal(token); if (tmp.signum() == 0) return 0L; // always valid // set the scale BigDecimal vv = tmp.setScale(decimals, di.getRounding() ? RoundingMode.HALF_EVEN : RoundingMode.UNNECESSARY); long ltmp = vv.unscaledValue().longValue(); // verify that we do not exceed bounds... if (ltmp < 0L && !di.getIsSigned()) throw new MessageParserException(MessageParserException.SUPERFLUOUS_SIGN, di.getName(), parseIndex, currentClass); // make sure that the parsed value does not exceed the configured number of digits if (ltmp < IntegralLimits.LONG_MIN_VALUES[di.getTotalDigits()] || ltmp > IntegralLimits.LONG_MAX_VALUES[di.getTotalDigits()]) throw new MessageParserException(MessageParserException.NUMERIC_TOO_LONG, di.getName(), parseIndex, currentClass); return ltmp; } @Override public Byte readByte(BasicNumericElementaryDataItem di) throws MessageParserException { String token = readBufferForInteger(di); if (token == null) return null; if (cfg.removePoint4BD || di.getDecimalDigits() == 0) return Byte.valueOf(processTrailingSigns(token)); return Byte.valueOf((byte) postProcessForImplicitDecimals(di, token)); } @Override public Short readShort(BasicNumericElementaryDataItem di) throws MessageParserException { String token = readBufferForInteger(di); if (token == null) return null; if (cfg.removePoint4BD || di.getDecimalDigits() == 0) return Short.valueOf(processTrailingSigns(token)); return Short.valueOf((short) postProcessForImplicitDecimals(di, token)); } @Override public Integer readInteger(BasicNumericElementaryDataItem di) throws MessageParserException { String token = readBufferForInteger(di); if (token == null) return null; if (cfg.removePoint4BD || di.getDecimalDigits() == 0) return Integer.valueOf(processTrailingSigns(token)); return Integer.valueOf((int) postProcessForImplicitDecimals(di, token)); } @Override public Long readLong(BasicNumericElementaryDataItem di) throws MessageParserException { String token = readBufferForInteger(di); if (token == null) return null; if (cfg.removePoint4BD || di.getDecimalDigits() == 0) return Long.valueOf(processTrailingSigns(token)); return Long.valueOf(postProcessForImplicitDecimals(di, token)); } @Override public Float readFloat(BasicNumericElementaryDataItem di) throws MessageParserException { String token = getField(di.getName(), di.getIsRequired(), di.getTotalDigits()+(di.getIsSigned() ? 1 : 0)); if (token == null) return null; return parseFloat(token.trim(), di); } @Override public Double readDouble(BasicNumericElementaryDataItem di) throws MessageParserException { String token = getField(di.getName(), di.getIsRequired(), di.getTotalDigits()+(di.getIsSigned() ? 1 : 0)); if (token == null) return null; return Double.valueOf(token.trim()); } protected Float parseFloat(String s, BasicNumericElementaryDataItem di) throws MessageParserException { if (localFloatFormat != null) { try { return (Float) localFloatFormat.parse(processTrailingSigns(s)); } catch (ParseException e) { throw new MessageParserException(MessageParserException.NUMBER_PARSING_ERROR, di.getName(), parseIndex, currentClass); } } else { return Float.valueOf(s); } } protected Double parseDouble(String s, BasicNumericElementaryDataItem di) throws MessageParserException { if (localFloatFormat != null) { try { return (Double) localFloatFormat.parse(processTrailingSigns(s)); } catch (ParseException e) { throw new MessageParserException(MessageParserException.NUMBER_PARSING_ERROR, di.getName(), parseIndex, currentClass); } } else { return Double.valueOf(s); } } @Override public void eatParentSeparator() throws MessageParserException { } @Override public BigInteger readBigInteger(BasicNumericElementaryDataItem di) throws MessageParserException { String token = getField(di.getName(), di.getIsRequired(), di.getTotalDigits()+(di.getIsSigned() ? 1 : 0)); if (token == null) return null; return new BigInteger(processTrailingSigns(token)); } @Override public <R extends BonaPortable> R readObject (ObjectReference di, Class<R> type) throws MessageParserException { if (di.getAllowSubclasses()) throw new MessageParserException(MessageParserException.UNSUPPORTED_DATA_TYPE, "readObject(subtypes = true)", parseIndex, currentClass); R newObject; try { newObject = type.newInstance(); } catch (InstantiationException e) { throw new MessageParserException(MessageParserException.CLASS_NOT_FOUND, "Instantiation exc on " + type.getCanonicalName(), parseIndex, currentClass); } catch (IllegalAccessException e) { throw new MessageParserException(MessageParserException.CLASS_NOT_FOUND, "Access exc on " + type.getCanonicalName(), parseIndex, currentClass); } String previousClass = currentClass; currentClass = newObject.ret$PQON(); newObject.deserialize(this); currentClass = previousClass; return newObject; } @Override public List<BonaPortable> readTransmission() throws MessageParserException { throw new MessageParserException(MessageParserException.UNSUPPORTED_DATA_TYPE, "readTransmission()", parseIndex, currentClass); } @Override public UUID readUUID(MiscElementaryDataItem di) throws MessageParserException { String tmp = readString(di.getName(), di.getIsRequired(), 36, false, false, false, false); if (tmp == null) { return null; } try { return UUID.fromString(tmp); } catch (IllegalArgumentException e) { throw new MessageParserException(MessageParserException.BAD_UUID_FORMAT, tmp, parseIndex, currentClass); } } @Override public MessageParserException enumExceptionConverter(IllegalArgumentException e) { return new MessageParserException(MessageParserException.INVALID_ENUM_TOKEN, e.getMessage(), parseIndex, currentClass); } @Override public MessageParserException customExceptionConverter(String msg, Exception e) { return new MessageParserException(MessageParserException.CUSTOM_OBJECT_EXCEPTION, e != null ? msg + e.toString() : 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(), spec.getMaxTokenLength(), true, false, false, true); if (scannedToken == null) return factory.getNullToken(); T value = factory.getByToken(scannedToken); if (value == null) { throw new MessageParserException(MessageParserException.INVALID_ENUM_TOKEN, scannedToken, parseIndex, currentClass + "." + di.getName()); } return value; } @Override public boolean readPrimitiveBoolean(MiscElementaryDataItem di) throws MessageParserException { String token = getField(di.getName(), true, lengthOfBoolean); if (token.equals(cfg.booleanTrue)) return true; if (token.equals(cfg.booleanFalse)) return false; throw new MessageParserException(MessageParserException.ILLEGAL_BOOLEAN, String.format("(%s, expected %s or %s for %s)", token, cfg.booleanTrue, cfg.booleanFalse, di.getName()), parseIndex, currentClass); } @Override public char readPrimitiveCharacter(MiscElementaryDataItem di) throws MessageParserException { String tmp = readString(di.getName(), true, 1, false, false, true, true); if (tmp.length() == 0) { throw new MessageParserException(MessageParserException.EMPTY_CHAR, di.getName(), parseIndex, currentClass); } return tmp.charAt(0); } @Override public double readPrimitiveDouble(BasicNumericElementaryDataItem di) throws MessageParserException { String token = getField(di.getName(), di.getIsRequired(), di.getTotalDigits()+(di.getIsSigned() ? 1 : 0)); return parseDouble(token.trim(), di); } @Override public float readPrimitiveFloat(BasicNumericElementaryDataItem di) throws MessageParserException { String token = getField(di.getName(), di.getIsRequired(), di.getTotalDigits()+(di.getIsSigned() ? 1 : 0)); return parseFloat(token.trim(), di); } @Override public long readPrimitiveLong(BasicNumericElementaryDataItem di) throws MessageParserException { String token = readBufferForInteger(di); if (cfg.removePoint4BD || di.getDecimalDigits() == 0) return Long.parseLong(processTrailingSigns(token)); return postProcessForImplicitDecimals(di, token); } @Override public int readPrimitiveInteger(BasicNumericElementaryDataItem di) throws MessageParserException { String token = readBufferForInteger(di); if (cfg.removePoint4BD || di.getDecimalDigits() == 0) return Integer.parseInt(processTrailingSigns(token)); return (int) postProcessForImplicitDecimals(di, token); } @Override public short readPrimitiveShort(BasicNumericElementaryDataItem di) throws MessageParserException { String token = readBufferForInteger(di); if (cfg.removePoint4BD || di.getDecimalDigits() == 0) return Short.parseShort(processTrailingSigns(token)); return (short) postProcessForImplicitDecimals(di, token); } @Override public byte readPrimitiveByte(BasicNumericElementaryDataItem di) throws MessageParserException { String token = readBufferForInteger(di); if (cfg.removePoint4BD || di.getDecimalDigits() == 0) return Byte.parseByte(processTrailingSigns(token)); return (byte) postProcessForImplicitDecimals(di, token); } @Override protected String getString(FieldDefinition di) throws MessageParserException { return readString(di.getName(), di.getIsRequired(), Integer.MAX_VALUE, true, false, true, true); } }