
org.beanio.config.flat.FlatStreamDefinitionFactory Maven / Gradle / Ivy
/*
* Copyright 2011 Kevin Seim
*
* 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 org.beanio.config.flat;
import java.util.*;
import org.beanio.BeanIOConfigurationException;
import org.beanio.config.*;
import org.beanio.parser.*;
import org.beanio.parser.flat.*;
/**
* Base class for stream definition factories for flat formatted streams (i.e. delimited
* or fixed length).
*
* @author Kevin Seim
* @since 1.0
*/
public abstract class FlatStreamDefinitionFactory extends StreamDefinitionFactory {
/* Comparator for sorting bean properties */
private static final Comparator propertyComparator = new PropertyDefinitionComparator();
@Override
protected void compileFieldDefinitions(RecordConfig recordConfig, RecordDefinition recordDefinition) {
super.compileFieldDefinitions(recordConfig, recordDefinition);
BeanDefinition beanDefinition = recordDefinition.getBeanDefinition();
FlatRecordDefinition definition = (FlatRecordDefinition) recordDefinition;
// validate a position is set for all fields or none of them
Boolean set = null;
List fieldList = beanDefinition.getAllFields();
for (FieldDefinition field : fieldList) {
if (set == null) {
set = field.getPosition() >= 0;
}
else if (set ^ (field.getPosition() >= 0)) {
throw new BeanIOConfigurationException("position must be declared for all the fields " +
"in a record, or none of them (in which case, all fields must be configured in the " +
"order they will appear in the stream)");
}
}
// if positions are not set, assign default positions to each field
if (set == null || !set) {
assignDefaultFieldPositions(recordConfig, definition);
}
else {
// sort bean properties for more efficient formatting...
sortBeanProperties(beanDefinition);
}
//printStack(beanDefinition, 0);
// validate there are no field gaps for descendants of collections
validateCollections(beanDefinition, false);
// validate min and max occurs will not confuse the parser
validateOccurrences(beanDefinition);
// set bean lengths and calculate the default record min & max length
calculateRecordLength(beanDefinition);
// calculate default record minimum and maximum length
int minLength = 0;
int maxLength = 0;
for (FieldDefinition field : fieldList) {
minLength = Math.max(minLength, getRecordMinLength(field));
maxLength = Math.max(maxLength, getRecordMaxLength(field));
}
if (maxLength == Integer.MAX_VALUE) {
maxLength = -1;
}
// validate and set the minimum length of the record
if (recordConfig.getMinLength() == null) {
// do nothing, minLength already set
}
else {
if (recordConfig.getMinLength() < 0) {
throw new BeanIOConfigurationException(
"minLength must be at least 0 on record '" + recordConfig.getName() + "'");
}
minLength = recordConfig.getMinLength();
}
definition.setMinLength(minLength);
// validate and set the maximum length of the record
if (recordConfig.getMaxLength() == null) {
maxLength = maxLength < 0 ? maxLength : Math.max(minLength, maxLength);
}
// handle unbounded
else if (recordConfig.getMaxLength() < 0) {
maxLength = -1;
}
else if (recordConfig.getMaxLength() > 0 && recordConfig.getMaxLength() < minLength) {
if (recordConfig.getMinLength() == null) {
throw new BeanIOConfigurationException("maxLength must be at least " + minLength);
}
else {
throw new BeanIOConfigurationException("maxLength must be greater than or " +
"equal to minLength on record '" + recordConfig.getName() + "'");
}
}
else {
maxLength = recordConfig.getMaxLength();
}
definition.setMaxLength(maxLength);
// perform the last validations after all fields have been constructed and ordered
for (FieldDefinition field : fieldList) {
// set lazy to true for any field with a position greater than the minimum length of the record
field.setLazy(!(field.getPosition() < minLength));
}
}
/**
* Assigns a default position to all record fields.
* @param recordConfig the record configuration
* @param recordDefinition the record definition
*/
protected abstract void assignDefaultFieldPositions(RecordConfig recordConfig, FlatRecordDefinition recordDefinition);
/**
* Recursively sorts all bean properties.
* @param the bean definition to sort
*/
private void sortBeanProperties(BeanDefinition bean) {
List list = bean.getPropertyList();
Collections.sort(list, propertyComparator);
// not necessary, but safer in case the reference to the internal list is not returned
bean.setPropertyList(list);
for (PropertyDefinition prop : list) {
if (prop.isBean()) {
sortBeanProperties((BeanDefinition) prop);
}
}
}
/**
* Validates there are no field gaps in any descendant of a collection.
* @param beanDefinition the bean definition to validate
* @param isCollection true if any ancestor of beanDefinition is a collection
*/
protected void validateCollections(BeanDefinition beanDefinition, boolean isCollection) {
isCollection = isCollection || beanDefinition.isCollection();
int min = Integer.MAX_VALUE;
int max = 0;
int length = 0;
PropertyDefinition previous = null;
for (PropertyDefinition property : beanDefinition.getPropertyList()) {
if (property.isBean()) {
FlatBeanDefinition child = (FlatBeanDefinition)property;
validateCollections(child, isCollection);
}
else if (property.isField()) {
if (previous != null) {
if (previous.isCollection()) {
// assume min and max occurs are the same (which is validated elsewhere)
length += previous.getMaxOccurs() * previous.getLength();
}
else {
length += previous.getLength();
}
}
FieldDefinition field = (FieldDefinition) property;
min = Math.min(field.getPosition(), min);
max = Math.max(field.getPosition(), max);
previous = property;
}
}
if (isCollection && (max - min) != length) {
throw new BeanIOConfigurationException("Invalid '" + beanDefinition.getName() +
"' bean configuration: field gaps not allowed for children of collections");
}
}
/**
* Recursively checks bean definitions to validate that any variable occurrence is at the end
* of the record.
* @param beanDefinition the bean definition to check recursively
* @return true if the bean definition is of variable occurrence
*/
protected boolean validateOccurrences(BeanDefinition beanDefinition) {
boolean indeterminate = beanDefinition.getMinOccurs() != beanDefinition.getMaxOccurs();
int index = 0;
int size = beanDefinition.getPropertyList().size();
for (PropertyDefinition property : beanDefinition.getPropertyList()) {
++index;
if (property.isBean()) {
FlatBeanDefinition child = (FlatBeanDefinition)property;
boolean b = validateOccurrences(child);
if (b) {
if (indeterminate) {
throw new BeanIOConfigurationException(
"A bean definition of variable occurrence cannot hold another " +
"bean definition of variable occurrence");
}
else if (index < size) {
throw new BeanIOConfigurationException("A bean definition " +
"of variable occurence is only allowed at the the end of the record");
}
}
}
else if (property.isField()) {
if (property.getMinOccurs() != property.getMaxOccurs()) {
if (indeterminate) {
throw new BeanIOConfigurationException(
"A bean definition of variable occurrence cannot hold a " +
"field definition of variable occurrence");
}
else if (index < size) {
throw new BeanIOConfigurationException("Field '" + property.getName() +
"' with variable occurence is only allowed at the the end of the record");
}
return true;
}
}
}
return indeterminate;
}
/**
* Returns the minimum length of the record based solely on the given field.
* Collection type bean definitions must have a valid length setting prior
* to calling this method.
* @param field the field to use to determine the minimum record length
* @return the minimum record length
*/
private int getRecordMinLength(FieldDefinition field) {
if (field.getMinOccurs() == 0) {
return 0;
}
int n = field.getPosition() + field.getLength() * (field.getMinOccurs() - 1);
PropertyDefinition property = field;
while ((property = property.getParent()) != null) {
if (property.getMinOccurs() == 0) {
return 0;
}
n += property.getLength() * (property.getMinOccurs() - 1);
}
return n + field.getLength();
}
/**
* Returns the maximum length of the record based solely on the given field.
* Collection type bean definitions must have a valid length setting prior
* to calling this method.
* @param field the field to use to determine the maximum record length
* @return the maximum record length
*/
private int getRecordMaxLength(FieldDefinition field) {
if (field.getMaxOccurs() < 0) {
return Integer.MAX_VALUE;
}
int n = field.getPosition() + field.getLength() * (field.getMaxOccurs() - 1);
PropertyDefinition property = field;
while ((property = property.getParent()) != null) {
if (property.getMaxOccurs() < 0) {
return Integer.MAX_VALUE;
}
n += property.getLength() * (property.getMaxOccurs() - 1);
}
return n + field.getLength();
}
/**
* Calculate and set bean lengths. And determine the default min and max record length.
* @param beanDefinition the record level bean definition
* @return the record block containing the min and max length
*/
private Block calculateRecordLength(BeanDefinition beanDefinition) {
Block block = new Block();
for (PropertyDefinition property : beanDefinition.getPropertyList()) {
if (property.isBean()) {
BeanDefinition bean = (BeanDefinition) property;
Block beanSize = calculateRecordLength(bean);
bean.setLength(beanSize.getLength());
block.update(bean, beanSize);
}
else if (property.isField()) {
block.update((FieldDefinition) property);
}
}
return block;
}
/* this implementation assumes the same field is not reused */
private static class Block {
private int max = 0;
private FieldDefinition firstField;
private FieldDefinition lastField;
public int getLength() {
if (max < 0) {
return -1;
}
else if (lastField == null) {
return max;
}
else if (lastField.getMaxOccurs() < 0) {
return -1;
}
else {
return max + lastField.getPosition() +
lastField.getLength() * lastField.getMaxOccurs() - firstField.getPosition();
}
}
public void update(FieldDefinition property) {
if (firstField == null) {
firstField = property;
}
else if (property.getPosition() < firstField.getPosition()) {
firstField = property;
}
if (lastField == null) {
lastField = property;
}
else if (property.getPosition() > lastField.getPosition()) {
lastField = property;
}
}
public void update(BeanDefinition bean, Block size) {
if (bean.getMaxOccurs() < 0)
this.max = -1;
else
this.max += size.getLength() * bean.getMaxOccurs();
}
}
/**
* Comparator for sorting property definitions.
*/
private static class PropertyDefinitionComparator implements Comparator {
/*
* (non-Javadoc)
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
public int compare(PropertyDefinition o1, PropertyDefinition o2) {
return new Integer(getPosition(o1)).compareTo(new Integer(getPosition(o2)));
}
private int getPosition(PropertyDefinition pd) {
if (pd.isBean()) {
int min = Integer.MAX_VALUE;
BeanDefinition bean = (BeanDefinition) pd;
for (PropertyDefinition prop : bean.getPropertyList()) {
min = Math.min(min, getPosition(prop));
}
return min;
}
else if (pd.isField()) {
return ((FieldDefinition)pd).getPosition();
}
else {
return -1;
}
}
}
/*
private void printStack(BeanDefinition bean, int level) {
for (int i=0; i
© 2015 - 2025 Weber Informatics LLC | Privacy Policy