/* * Milyn - Copyright (C) 2006 - 2010 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License (version 2.1) as published by the Free Software * Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * See the GNU Lesser General Public License for more details: * http://www.gnu.org/licenses/lgpl.txt */ package org.milyn.flatfile.variablefield; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.milyn.flatfile.Field; import org.milyn.flatfile.FieldMetaData; import org.milyn.flatfile.Record; import org.milyn.flatfile.RecordMetaData; import org.milyn.flatfile.RecordParser; import org.milyn.function.StringFunctionExecutor; /** * Abstract variable field record parser. * * @author <a href="mailto:tom.fennelly@gmail.com">tom.fennelly@gmail.com</a> */ public abstract class VariableFieldRecordParser<T extends VariableFieldRecordParserFactory> implements RecordParser<T> { private Log logger = LogFactory.getLog(getClass()); private T factory; private int lineNumber = 0; private int recordCount = 0; private RecordMetaData inMessageRecordMetaData; /** * Parse the next record from the flat file input stream and produce the set * of record field values. * * @return The next records field values. * @throws IOException Error reading message stream. */ public abstract List<String> nextRecordFieldValues() throws IOException; /** * {@inheritDoc} */ public final void setRecordParserFactory(T factory) { this.factory = factory; } /** * {@inheritDoc} */ public void initialize() throws IOException { int skipLines = factory.getSkipLines(); // Move past the lines to be skipped ... while (lineNumber < skipLines) { _nextRecordFieldValues(); } // If the fields are defined in the message... read the next record if (factory.fieldsInMessage() || factory.validateHeader()) { List<String> fields = _nextRecordFieldValues(); if (factory.validateHeader()) { validateHeader(fields); } if (factory.fieldsInMessage()) { // In message field definitions do not support variable field definitions... just one record type supported... inMessageRecordMetaData = VariableFieldRecordMetaData.buildRecordMetaData(factory.getRecordElementName(), fields); } } } /** * {@inheritDoc} */ public void uninitialize() { } /** * Get the parser factory instance that created this parser instance. * * @return The parser factory instance that created this parser instance. */ public T getFactory() { return factory; } /** * Get the number of records read so far by this parser instance. * * @return The number of records read so far by this parser instance. */ public int getRecordCount() { return recordCount; } private List<String> _nextRecordFieldValues() throws IOException { lineNumber++; return nextRecordFieldValues(); } /** * {@inheritDoc} */ public final Record nextRecord() throws IOException { List<String> fieldValues = _nextRecordFieldValues(); if (fieldValues == null || fieldValues.isEmpty()) { return null; } RecordMetaData recordMetaData; if (inMessageRecordMetaData != null) { recordMetaData = inMessageRecordMetaData; } else { recordMetaData = factory.getRecordMetaData(fieldValues); } List<FieldMetaData> fieldsMetaData = recordMetaData.getFields(); if (factory.strict() && fieldValues.size() < getUnignoredFieldCount(recordMetaData)) { logger.debug("[CORRUPT] Record #" + recordCount + " invalid [" + fieldValues + "]. The record should contain " + fieldsMetaData.size() + " fields [" + recordMetaData.getFieldNames() + "], but contains " + fieldValues.size() + " fields. Ignoring!!"); return nextRecord(); } List<Field> fields = new ArrayList<Field>(); try { if (recordMetaData == VariableFieldRecordMetaData.UNKNOWN_RECORD_TYPE) { fields.add(new Field(recordMetaData.getFields().get(0).getName(), fieldValues.get(0))); return new Record(recordMetaData.getName(), fields, recordMetaData); } else { int fieldValueOffset = 0; // In message field definitions do not support variable field definitions... just one record type supported... if (inMessageRecordMetaData == null && factory.isMultiTypeRecordSet()) { // Skip the first field value because it's the field name... fieldValueOffset = +1; } for (int i = 0; i < fieldValues.size(); i++) { int fieldValueIndex = i + fieldValueOffset; if (fieldValueIndex > fieldValues.size() - 1) { break; } if (!recordMetaData.isWildCardRecord() && i > fieldsMetaData.size() - 1) { // We're done... ignore the rest of the fields... break; } Field field; String value = fieldValues.get(fieldValueIndex); if (recordMetaData.isWildCardRecord() || i > fieldsMetaData.size() - 1) { field = new Field("field_" + i, value); } else { FieldMetaData fieldMetaData = fieldsMetaData.get(i); if (fieldMetaData.ignore()) { i += fieldMetaData.getIgnoreCount() - 1; if (i < 0) { // An overflow has resulted... i = Integer.MAX_VALUE - 1; } continue; } StringFunctionExecutor stringFunction = fieldMetaData.getStringFunctionExecutor(); if (stringFunction != null) { value = stringFunction.execute(value); } field = new Field(fieldMetaData.getName(), value); field.setMetaData(fieldMetaData); } fields.add(field); } } } finally { recordCount++; } return new Record(recordMetaData.getName(), fields, recordMetaData); } /** * Get the unignored field count for the specified record. * * @param recordMetaData The record metadata. * @return The unignored field count. */ public int getUnignoredFieldCount(RecordMetaData recordMetaData) { if (factory.isMultiTypeRecordSet()) { // Need to account for the leading identifier field on each // record... return recordMetaData.getUnignoredFieldCount() + 1; } else { return recordMetaData.getUnignoredFieldCount(); } } protected void validateHeader(List<String> headers) throws IOException { if (factory.isMultiTypeRecordSet()) { throw new IOException("Cannot validate the 'header' field of a Multi-Type Record Set. Reader fields definition defines multiple record definitions."); } RecordMetaData recordMetaData = factory.getRecordMetaData(); if (headers == null) { throw new IOException("Null header."); } if (validateHeader(headers, recordMetaData.getFields())) { return; } throw new IOException("Invalid header."); } private boolean validateHeader(List<String> headers, final List<FieldMetaData> fieldsMetaData) { if (fieldsMetaData.size() != headers.size()) { return false; } int n = 0; for (FieldMetaData field : fieldsMetaData) { if (!field.ignore()) { if (headers.size() <= n) { return false; } String header = headers.get(n); if (header == null) { header = ""; } String name = field.getName(); if (name == null) { name = ""; } if (!name.equals(header)) { return false; } } n++; } return true; } }