/*
 * 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 CsvNumber, that is, when number inputs and outputs should be formatted.
Author:Andrew Rucker Jones
Since:4.2
/** * 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<Number> readConversionFunction;
Params:
  • type – The class of the type of the data being processed
  • locale – If not null or empty, specifies the locale used for converting locale-specific data types for reading
  • writeLocale – If not null or empty, specifies the locale used for converting locale-specific data types for writing
  • errorLocale – The locale to use for error messages
  • readFormat – The string to use for parsing the number.
  • writeFormat – The string to use for formatting the number.
Throws:
See Also:
/** * @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.
/** * 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; } } }