Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.beanio.internal.parser.Field Maven / Gradle / Ivy
Go to download
A Java un/marshalling library for CSV, XML, delimited and fixed length stream formats.
/*
* Copyright 2011-2012 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.internal.parser;
import java.util.Set;
import java.util.regex.*;
import org.beanio.*;
import org.beanio.internal.util.*;
import org.beanio.types.*;
/**
* A parser for marshalling and unmarshalling a single field in a record. A field is usually, but
* optionally, bound to a simple property value.
*
* A field component does not have any children.
*
* @author Kevin Seim
* @since 2.0
*/
public class Field extends ParserComponent implements Property {
private static final boolean marshalDefault = "true".equalsIgnoreCase(
Settings.getInstance().getProperty(Settings.DEFAULT_MARSHALLING_ENABLED));
private ParserLocal value = new ParserLocal(Value.MISSING);
private boolean bound;
private boolean identifier;
/* validation settings */
private boolean trim;
private boolean required;
private int minLength = 0;
private int maxLength = Integer.MAX_VALUE;
private String literal = null;
private Pattern regex = null;
private Object defaultValue;
/*
* the property type
* if this field is not a property, then null
* if this field is an array, then the array component type
*/
private Class> propertyType;
private TypeHandler handler;
private PropertyAccessor accessor;
private FieldFormat format;
/**
* Constructs a new Field .
*/
public Field() {
super(0);
}
/*
* (non-Javadoc)
* @see org.beanio.internal.parser.Parser#hasContent()
*/
public boolean hasContent(ParsingContext context) {
if (isBound()) {
return getValue(context) != Value.MISSING;
}
else {
// fields that aren't bound to a property of a bean object are
// always considered to have content during marshalling
return true;
}
}
/*
* (non-Javadoc)
* @see org.beanio.parser2.Property#type()
*/
public int type() {
return Property.SIMPLE;
}
/*
* (non-Javadoc)
* @see org.beanio.parser.Parser#isLazy()
*/
public boolean isLazy() {
return format.isLazy();
}
/*
* (non-Javadoc)
* @see org.beanio.parser2.Property#defines(java.lang.Object)
*/
public boolean defines(Object value) {
if (value == null) {
return false;
}
if (!isIdentifier()) {
return true;
}
if (!TypeUtil.isAssignable(getPropertyType(), value.getClass())) {
return false;
}
return isMatch(formatValue(value));
}
/**
* Tests if the field text in the record matches this field.
* @param context the {@link UnmarshallingContext} containing the record to match
* @return true if the field text is a match or this field is not used
* to identify the record
*/
public boolean matches(UnmarshallingContext context) {
if (isIdentifier()) {
return isMatch(format.extract(context, false));
}
else {
return true;
}
}
/**
* Returns true if the provided field text is a match for this field
* definition based on the configured literal value or regular expression.
* @param text the field text to test
* @return true if the field text matches this field definitions constraints,
* or false if the field text is null or does not match
*/
protected boolean isMatch(String text) {
if (text == null)
return false;
if (text == Value.INVALID)
return false;
if (text == Value.NIL)
return false;
if (literal != null && !literal.equals(text))
return false;
if (regex != null && !regex.matcher(text).matches())
return false;
return true;
}
/*
* (non-Javadoc)
* @see org.beanio.parser2.Marshaller#marshal(org.beanio.parser2.MarshallingContext)
*/
public boolean marshal(MarshallingContext context) {
String text;
if (literal != null) {
text = literal;
}
else {
Object value = getValue(context);
// the default value may be used to override null property values
// if enabled (since 1.2.2)
if (marshalDefault && value == Value.MISSING) {
value = defaultValue;
setValue(context, defaultValue);
}
if (value == Value.MISSING) {
value = null;
setValue(context, null);
}
// allow the format to bypass type conversion
if (format.insertValue(context, value)) {
return true;
}
text = formatValue(value);
}
format.insertField(context, text);
return true;
}
/*
* (non-Javadoc)
* @see org.beanio.parser2.Parser#hasNext(org.beanio.parser2.UnmarshallingContext)
*/
public boolean hasNext(UnmarshallingContext context) {
return format.extract(context, false) != null;
}
/*
* (non-Javadoc)
* @see org.beanio.parser2.Unmarshaller#unmarshal(org.beanio.parser2.UnmarshallingContext)
*/
public boolean unmarshal(UnmarshallingContext context) {
String text = format.extract(context, true);
if (text == null) {
// minOccurs is validated at the segment level
setValue(context, Value.MISSING);
return false;
}
if (text == Value.INVALID) {
this.value.set(context, Value.INVALID);
}
else {
this.value.set(context, parseValue(context, text));
}
return true;
}
/**
* Parses and validates a field property value from the given field text.
* @param context the {@link UnmarshallingContext} to report field errors to
* @param fieldText the field text to parse
* @return the parsed field value, or {@link Value#INVALID} if the field was invalid,
* or {@link Value#MISSING} if the field was not present in the record
*/
protected Object parseValue(UnmarshallingContext context, String fieldText) {
boolean valid = true;
String text = fieldText;
if (text == Value.NIL) {
// validate field is nillable
if (!format.isNillable()) {
context.addFieldError(getName(), null, "nillable");
return Value.INVALID;
}
// collections are not further validated
else if (required) {
context.addFieldError(getName(), null, "required");
return Value.INVALID;
}
// return the default value if set
else if (defaultValue != null) {
return defaultValue;
}
return null;
}
// repeating fields are always optional
if (text == null) {
if (!format.isLazy()) {
context.addFieldError(getName(), null, "minOccurs", 1);
return Value.INVALID;
}
}
// trim before validation if configured
else if (trim) {
text = text.trim();
}
// check if field exists
if (text == null || text.length() == 0) {
// validation for required fields
if (required) {
context.addFieldError(getName(), fieldText, "required");
valid = false;
}
// return the default value if set
else if (defaultValue != null) {
return defaultValue;
}
else if (text == null) {
return Value.MISSING;
}
}
else {
// validate constant fields
if (literal != null && !literal.equals(text)) {
context.addFieldError(getName(), fieldText, "literal", literal);
valid = false;
}
// validate minimum length
if (text.length() < minLength) {
context.addFieldError(getName(), fieldText, "minLength", minLength, maxLength);
valid = false;
}
// validate maximum length
if (text.length() > maxLength) {
context.addFieldError(getName(), fieldText, "maxLength", minLength, maxLength);
valid = false;
}
// validate the regular expression
if (regex != null && !regex.matcher(text).matches()) {
context.addFieldError(getName(), fieldText, "regex", regex.pattern());
valid = false;
}
}
// type conversion is skipped if the text does not pass other validations
if (!valid) {
return Value.INVALID;
}
// perform type conversion and return the result
try {
// if there is no type handler, assume its a String
Object value = (handler == null) ? text : handler.parse(text);
// validate primitive values are not null
if (value == null && propertyType != null && propertyType.isPrimitive()) {
context.addFieldError(getName(), fieldText, "type",
"Primitive property values cannot be null");
return Value.INVALID;
}
return value;
}
catch (TypeConversionException ex) {
context.addFieldError(getName(), fieldText, "type", ex.getMessage());
return Value.INVALID;
}
catch (Exception ex) {
throw new BeanReaderException("Type conversion failed for field '" + getName() +
"' while parsing text '" + fieldText + "'", ex);
}
}
/**
* Formats a field/property value.
* @param value the property value to format
* @return the formatted field text
*/
protected String formatValue(Object value) {
String text = null;
if (handler != null) {
try {
text = handler.format(value);
if (text == TypeHandler.NIL) {
if (format.isNillable()) {
return Value.NIL;
}
text = null;
}
}
catch (Exception ex) {
throw new BeanWriterException("Type conversion failed for field '" +
getName() + "' while formatting value '" + value + "'", ex);
}
}
else if (value != null) {
text = value.toString();
}
return text;
}
/*
* (non-Javadoc)
* @see org.beanio.parser.Parser#clearValue()
*/
public void clearValue(ParsingContext context) {
this.value.set(context, Value.MISSING);
}
/*
* (non-Javadoc)
* @see org.beanio.internal.parser.Property#createValue()
*/
public Object createValue(ParsingContext context) {
return getValue(context);
}
/*
* (non-Javadoc)
* @see org.beanio.parser.Parser#getValue()
*/
public Object getValue(ParsingContext context) {
return value.get(context);
}
/*
* (non-Javadoc)
* @see org.beanio.parser.Parser#setValue(java.lang.Object)
*/
public void setValue(ParsingContext context, Object value) {
this.value.set(context, value == null ? Value.MISSING : value);
}
@Override
protected boolean isSupportedChild(Component child) {
return false;
}
/**
* Returns the regular expression pattern the field text parsed by this field
* definition must match.
* @return the regular expression pattern
*/
public String getRegex() {
return regex == null ? null : regex.pattern();
}
/**
* Sets the regular expression pattern the field text parsed by this field
* definition must match.
* @param pattern the regular expression pattern
* @throws PatternSyntaxException if the pattern is invalid
*/
public void setRegex(String pattern) throws PatternSyntaxException {
if (pattern == null)
this.regex = null;
else
this.regex = Pattern.compile(pattern);
}
/**
* Returns the regular expression the field text parsed by this field
* definition must match.
* @return the regular expression
*/
protected Pattern getRegexPattern() {
return regex;
}
@Override
public void registerLocals(Set> locals) {
if (locals.add(value)) {
super.registerLocals(locals);
}
}
public void setPropertyType(Class> type) {
this.propertyType = type;
}
public Class> getPropertyType() {
return propertyType;
}
public boolean isIdentifier() {
return identifier;
}
public void setIdentifier(boolean recordIdentifier) {
this.identifier = recordIdentifier;
}
public FieldFormat getFormat() {
return format;
}
public void setFormat(FieldFormat format) {
this.format = format;
}
public String getLiteral() {
return literal;
}
public void setLiteral(String literal) {
this.literal = literal;
}
public Class> getType() {
return propertyType;
}
public boolean isTrim() {
return trim;
}
public void setTrim(boolean trim) {
this.trim = trim;
}
public boolean isRequired() {
return required;
}
public void setRequired(boolean required) {
this.required = required;
}
public int getMinLength() {
return minLength;
}
public void setMinLength(int minLength) {
this.minLength = minLength;
}
public int getMaxLength() {
return maxLength;
}
public void setMaxLength(int maxLength) {
this.maxLength = maxLength;
}
public void setRegex(Pattern regex) {
this.regex = regex;
}
public void setType(Class> type) {
this.propertyType = type;
}
public PropertyAccessor getAccessor() {
return accessor;
}
public void setAccessor(PropertyAccessor accessor) {
this.accessor = accessor;
}
/**
* Returns the default value for a field parsed by this field definition
* when the field text is null or the empty string (after trimming).
* @return default value
*/
public Object getDefaultValue() {
return defaultValue;
}
/**
* Sets the default value for a field parsed by this field definition
* when the field text is null or the empty string (after trimming).
* @param defaultValue the default value
*/
public void setDefaultValue(Object defaultValue) {
this.defaultValue = defaultValue;
}
public TypeHandler getHandler() {
return handler;
}
public void setHandler(TypeHandler handler) {
this.handler = handler;
}
protected void toParamString(StringBuilder s) {
super.toParamString(s);
s.append(", type=").append(propertyType != null ? propertyType.getSimpleName() : null);
s.append(", size=").append(Integer.toString(getSize()));
s.append(", format=").append(format);
}
public int getSize() {
return format.getSize();
}
public boolean isBound() {
return bound;
}
public void setBound(boolean property) {
this.bound = property;
}
}