OpencsvUtils.java
/*
* Copyright 2016 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.util;
import com.opencsv.ICSVParser;
import com.opencsv.bean.*;
import com.opencsv.bean.exceptionhandler.CsvExceptionHandler;
import com.opencsv.exceptions.CsvBadConverterException;
import com.opencsv.exceptions.CsvChainedException;
import com.opencsv.exceptions.CsvException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Stream;
/**
* This class is meant to be a collection of general purpose static methods
* useful in internal processing for opencsv.
*
* @author Andrew Rucker Jones
* @since 3.9
*/
public final class OpencsvUtils {
/** This class can't be instantiated. */
private OpencsvUtils() {}
/**
* Determines which mapping strategy is appropriate for this bean.
* The algorithm is:<ol>
* <li>If annotations {@link CsvBindByPosition},
* {@link CsvCustomBindByPosition}, {@link CsvBindAndSplitByPosition} or
* {@link CsvBindAndJoinByPosition} are present,
* {@link ColumnPositionMappingStrategy} is chosen.</li>
* <li>Otherwise, {@link HeaderColumnNameMappingStrategy} is chosen. If
* annotations are present, they will be used, otherwise the field names
* will be used as the column names.</li></ol>
*
* @param <T> The type of the bean for which the mapping strategy is sought
* @param type The class of the bean for which the mapping strategy is sought
* @param errorLocale The locale to use for all error messages. If null, the
* default locale is used.
* @param profile The profile to use when configuring bean fields
* @return A functional mapping strategy for the bean in question
*/
public static <T> MappingStrategy<T> determineMappingStrategy(
Class<? extends T> type, Locale errorLocale, String profile) {
// Check for annotations
boolean positionAnnotationsPresent = Stream.of(FieldUtils.getAllFields(type)).anyMatch(
f -> f.isAnnotationPresent(CsvBindByPosition.class)
|| f.isAnnotationPresent(CsvBindAndSplitByPosition.class)
|| f.isAnnotationPresent(CsvBindAndJoinByPosition.class)
|| f.isAnnotationPresent(CsvCustomBindByPosition.class));
// Set the mapping strategy according to what we've found.
MappingStrategy<T> mappingStrategy = positionAnnotationsPresent ?
new ColumnPositionMappingStrategy<>() :
new HeaderColumnNameMappingStrategy<>();
mappingStrategy.setErrorLocale(errorLocale);
mappingStrategy.setProfile(profile);
mappingStrategy.setType(type);
return mappingStrategy;
}
/**
* I find it annoying that when I want to queue something in a blocking
* queue, the thread might be interrupted and I have to try again; this
* method fixes that.
* @param <E> The type of the object to be queued
* @param queue The queue the object should be added to
* @param object The object to be queued
* @since 4.0
*/
public static <E> void queueRefuseToAcceptDefeat(BlockingQueue<E> queue, E object) {
boolean interrupted = true;
while(interrupted) {
try {
queue.put(object);
interrupted = false;
}
catch(InterruptedException ie) {/* Do nothing. */}
}
}
/**
* A function to consolidate code common to handling exceptions thrown
* during reading or writing of CSV files.
* The proper line number is set for the exception, the exception handler
* is run, and the exception is queued or thrown as necessary.
*
* @param e The exception originally thrown
* @param lineNumber The line or record number that caused the exception
* @param exceptionHandler The exception handler
* @param queue The queue for captured exceptions
* @since 5.2
*/
public static synchronized void handleException(
CsvException e, long lineNumber,
CsvExceptionHandler exceptionHandler, BlockingQueue<OrderedObject<CsvException>> queue) {
e.setLineNumber(lineNumber);
CsvException capturedException = null;
List<CsvException> exceptionList = e instanceof CsvChainedException ?
Collections.<CsvException>unmodifiableList(((CsvChainedException)e).getExceptionChain()) :
Collections.singletonList(e);
for (CsvException iteratedException : exceptionList) {
try {
capturedException = exceptionHandler.handleException(iteratedException);
} catch (CsvException csve) {
capturedException = csve;
throw new RuntimeException(csve);
} finally {
if (capturedException != null) {
queueRefuseToAcceptDefeat(queue,
new OrderedObject<>(lineNumber, capturedException));
}
}
}
}
/**
* Compiles a regular expression into a {@link java.util.regex.Pattern},
* throwing an exception that is proper in the context of opencsv if the
* regular expression is not valid, or if it does not have at least one
* capturing group.
*
* @param regex The regular expression to be compiled. May be {@code null}
* or an empty string, in which case {@code null} is returned.
* Must have at least one capturing group if not {@code null}
* or empty.
* @param regexFlags Flags for compiling the regular expression, as in
* {@link java.util.regex.Pattern#compile(String, int)}.
* @param callingClass The class from which this method is being called.
* Used for generating helpful exceptions.
* @param errorLocale The locale to be used for error messages. If
* {@code null}, the default locale is used.
* @return A compiled pattern, or {@code null} if the input was null or
* empty
* @throws CsvBadConverterException If the regular expression is not empty
* but invalid or valid but does not have at least one capturing group
* @since 4.3
*/
public static Pattern compilePatternAtLeastOneGroup(String regex, int regexFlags, Class<?> callingClass, Locale errorLocale)
throws CsvBadConverterException {
Pattern tempPattern = compilePattern(regex, regexFlags, callingClass, errorLocale);
Locale exceptionLocale = errorLocale == null ? Locale.getDefault() : errorLocale;
// Verify that the pattern has at least one capture group. This does
// not appear to be possible without matching a string first.
if(tempPattern != null) {
Matcher m = tempPattern.matcher(StringUtils.EMPTY);
if(m.groupCount() < 1) {
throw new CsvBadConverterException(callingClass,
String.format(ResourceBundle.getBundle(
ICSVParser.DEFAULT_BUNDLE_NAME,
exceptionLocale).getString("regex.without.capture.group"), regex));
}
}
return tempPattern;
}
/**
* Compiles a regular expression into a {@link java.util.regex.Pattern},
* throwing an exception that is proper in the context of opencsv if the
* regular expression is not valid.
* This method may be used by custom converters if they are required to
* compile regular expressions that are unknown at compile time.
*
* @param regex The regular expression to be compiled. May be {@code null}
* or an empty string, in which case {@code null} is returned.
* @param regexFlags Flags for compiling the regular expression, as in
* {@link java.util.regex.Pattern#compile(String, int)}.
* @param callingClass The class from which this method is being called.
* Used for generating helpful exceptions.
* @param errorLocale The locale to be used for error messages. If
* {@code null}, the default locale is used.
* @return A compiled pattern, or {@code null} if the input was null or
* empty
* @throws CsvBadConverterException If the regular expression is not empty
* but invalid
* @since 4.3
*/
public static Pattern compilePattern(String regex, int regexFlags, Class<?> callingClass, Locale errorLocale)
throws CsvBadConverterException {
Pattern tempPattern = null;
Locale exceptionLocale = errorLocale == null ? Locale.getDefault() : errorLocale;
// Set up the regular expression for extraction of the value to be
// converted
if(StringUtils.isNotEmpty(regex)) {
try {
tempPattern = Pattern.compile(regex, regexFlags);
}
catch(PatternSyntaxException e) {
CsvBadConverterException csve = new CsvBadConverterException(
callingClass,
String.format(ResourceBundle.getBundle(
ICSVParser.DEFAULT_BUNDLE_NAME,
exceptionLocale).getString("invalid.regex"), regex));
csve.initCause(e);
throw csve;
}
}
return tempPattern;
}
/**
* Verifies that the given format string works with one string parameter.
*
* @param format A format string for {@link java.lang.String#format(String, Object...)}
* @param callingClass The class from which this method is being called.
* Used for generating helpful exceptions.
* @param errorLocale The locale to be used for error messages. If
* {@code null}, the default locale is used.
*/
public static void verifyFormatString(String format, Class<?> callingClass, Locale errorLocale) {
Locale exceptionLocale = errorLocale == null ? Locale.getDefault() : errorLocale;
try {
if(StringUtils.isNotEmpty(format)) {
String okayToIgnore = String.format(format, StringUtils.SPACE);
}
}
catch(IllegalFormatException e) {
CsvBadConverterException csve = new CsvBadConverterException(
callingClass,
String.format(ResourceBundle.getBundle(
ICSVParser.DEFAULT_BUNDLE_NAME,
exceptionLocale).getString("invalid.one.parameter.format.string"), format));
csve.initCause(e);
throw csve;
}
}
}