com.opencsv.bean.ConverterNumber Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of opencsv Show documentation
Show all versions of opencsv Show documentation
A simple library for reading and writing CSV in Java
/*
* Copyright 2018 Andrew Rucker Jones.
*
* 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 com.opencsv.bean;
import com.opencsv.ICSVParser;
import com.opencsv.exceptions.CsvBadConverterException;
import com.opencsv.exceptions.CsvDataTypeMismatchException;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.function.UnaryOperator;
/**
* This converter class is used in combination with {@link CsvNumber}, that is,
* when number inputs and outputs should be formatted.
*
* @author Andrew Rucker Jones
* @since 4.2
*/
public class ConverterNumber extends AbstractCsvConverter {
private final DecimalFormat readFormatter, writeFormatter;
private final UnaryOperator readConversionFunction;
/**
* @param type The class of the type of the data being processed
* @param locale If not null or empty, specifies the locale used for
* converting locale-specific data types for reading
* @param writeLocale If not null or empty, specifies the locale used for
* converting locale-specific data types for writing
* @param errorLocale The locale to use for error messages
* @param readFormat The string to use for parsing the number.
* @param writeFormat The string to use for formatting the number.
* @throws CsvBadConverterException If the information given to initialize the converter are inconsistent (e.g.
* the annotation {@link com.opencsv.bean.CsvNumber} has been applied to a non-{@link java.lang.Number} type.
* @see com.opencsv.bean.CsvNumber#value()
*/
public ConverterNumber(Class> type, String locale, String writeLocale, Locale errorLocale, String readFormat, String writeFormat)
throws CsvBadConverterException {
super(type, locale, writeLocale, errorLocale);
// Check that the bean member is of an applicable type
if(!Number.class.isAssignableFrom(
this.type.isPrimitive()
? ClassUtils.primitiveToWrapper(this.type)
: this.type)) {
throw new CsvBadConverterException(
ConverterNumber.class,
ResourceBundle.getBundle(
ICSVParser.DEFAULT_BUNDLE_NAME,
this.errorLocale)
.getString("csvnumber.not.number"));
}
// Set up the read formatter
readFormatter = createDecimalFormat(readFormat, this.locale);
// Account for BigDecimal and BigInteger, which require special
// processing
if(this.type == BigInteger.class || this.type == BigDecimal.class) {
readFormatter.setParseBigDecimal(true);
}
// Save the read conversion function for later
if(this.type == Byte.class || this.type == Byte.TYPE) {
readConversionFunction = Number::byteValue;
}
else if(this.type == Short.class || this.type == Short.TYPE) {
readConversionFunction = Number::shortValue;
}
else if(this.type == Integer.class || this.type == Integer.TYPE) {
readConversionFunction = Number::intValue;
}
else if(this.type == Long.class || this.type == Long.TYPE) {
readConversionFunction = Number::longValue;
}
else if(this.type == Float.class || this.type == Float.TYPE) {
readConversionFunction = Number::floatValue;
}
else if(this.type == Double.class || this.type == Double.TYPE) {
readConversionFunction = Number::doubleValue;
}
else if(this.type == BigInteger.class) {
readConversionFunction = n -> ((BigDecimal) n).toBigInteger();
}
else {
// Either it's already a BigDecimal and nothing need be done,
// or it's some derivative of java.lang.Number that we couldn't be
// expected to know and accommodate for. In the latter case, a class
// cast exception will be thrown later on assignment.
readConversionFunction = n -> n;
}
// Set up the write formatter
writeFormatter = createDecimalFormat(writeFormat, this.writeLocale);
}
private DecimalFormat createDecimalFormat(String format, Locale locale) {
NumberFormat nf = NumberFormat.getInstance(ObjectUtils.defaultIfNull(locale, Locale.getDefault(Locale.Category.FORMAT)));
if (!(nf instanceof DecimalFormat)) {
throw new CsvBadConverterException(
ConverterNumber.class,
ResourceBundle.getBundle(
ICSVParser.DEFAULT_BUNDLE_NAME,
this.errorLocale)
.getString("numberformat.not.decimalformat"));
}
DecimalFormat formatter = (DecimalFormat) nf;
try {
formatter.applyLocalizedPattern(format);
} catch (IllegalArgumentException e) {
CsvBadConverterException csve = new CsvBadConverterException(
ConverterNumber.class,
String.format(ResourceBundle.getBundle(
ICSVParser.DEFAULT_BUNDLE_NAME,
this.errorLocale)
.getString("invalid.number.pattern"),
format));
csve.initCause(e);
throw csve;
}
return formatter;
}
@Override
public Object convertToRead(String value) throws CsvDataTypeMismatchException {
Number n = null;
if(StringUtils.isNotEmpty(value)) {
try {
synchronized (readFormatter) {
n = readFormatter.parse(value);
}
}
catch(ParseException e) {
CsvDataTypeMismatchException csve = new CsvDataTypeMismatchException(
value, type,
String.format(ResourceBundle.getBundle(
ICSVParser.DEFAULT_BUNDLE_NAME,
errorLocale)
.getString("unparsable.number"), value, readFormatter.toPattern()));
csve.initCause(e);
throw csve;
}
n = readConversionFunction.apply(n);
}
return n;
}
/**
* Formats the number in question according to the pattern that has been
* provided.
*/
// The rest of the Javadoc is inherited.
@Override
public String convertToWrite(Object value) {
synchronized (writeFormatter) {
return value != null ? writeFormatter.format(value) : null;
}
}
}