/*
 * Copyright 2017 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.CsvRequiredFieldEmptyException;
import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.lang3.ArrayUtils;

import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

This class maintains a mapping from header names out of a CSV file to bean fields. Simple entries are matched using string equality. Complex entries are matched using regular expressions.
Author:Andrew Rucker Jones
Type parameters:
  • <T> – Type of the bean being converted
Since:4.2
/** * This class maintains a mapping from header names out of a CSV file to bean * fields. * Simple entries are matched using string equality. Complex entries are matched * using regular expressions. * * @param <T> Type of the bean being converted * @author Andrew Rucker Jones * @since 4.2 */
public class FieldMapByName<T> extends AbstractFieldMap<String, String, RegexToBeanField<T>, T> {
Holds a Comparator to sort columns on writing.
/** Holds a {@link java.util.Comparator} to sort columns on writing. */
private Comparator<String> writeOrder = null;
Initializes this FieldMap.
Params:
  • errorLocale – The locale to be used for error messages
/** * Initializes this {@link FieldMap}. * * @param errorLocale The locale to be used for error messages */
public FieldMapByName(final Locale errorLocale) { super(errorLocale); }
Params:
  • key – A regular expression matching header names
/** * @param key A regular expression matching header names */
// The rest of the Javadoc is inherited @Override public void putComplex(final String key, final BeanField<T, String> value) { complexMapList.add(new RegexToBeanField<>(key, value, errorLocale)); }
Returns a list of required headers that were not present in the input.
Params:
  • headersPresent – An array of all headers present from the input
Returns:A list of name + field for all of the required headers that were not found
/** * Returns a list of required headers that were not present in the input. * * @param headersPresent An array of all headers present from the input * @return A list of name + field for all of the required headers that were * not found */
public List<FieldMapByNameEntry<T>> determineMissingRequiredHeaders(final String[] headersPresent) { // Start with collections of all required headers final List<String> requiredStringList = simpleMap.entrySet().stream() .filter(e -> e.getValue().isRequired()) .map(Map.Entry::getKey) .collect(Collectors.toCollection(LinkedList::new)); final List<ComplexFieldMapEntry<String, String, T>> requiredRegexList = complexMapList.stream() .filter(r -> r.getBeanField().isRequired()) .collect(Collectors.toList()); // Now remove the ones we found for(String h : headersPresent) { if(!requiredStringList.remove(h.toUpperCase())) { final ListIterator<ComplexFieldMapEntry<String, String, T>> requiredRegexListIterator = requiredRegexList.listIterator(); boolean found = false; while(!found && requiredRegexListIterator.hasNext()) { final ComplexFieldMapEntry<String, String, T> r = requiredRegexListIterator.next(); if(r.contains(h)) { found = true; requiredRegexListIterator.remove(); } } } } // Repackage what remains List<FieldMapByNameEntry<T>> missingRequiredHeaders = new LinkedList<>(); for(String s : requiredStringList) { missingRequiredHeaders.add(new FieldMapByNameEntry<T>(s, simpleMap.get(s), false)); } for(ComplexFieldMapEntry<String, String, T> r : requiredRegexList) { missingRequiredHeaders.add(new FieldMapByNameEntry<T>(r.getInitializer(), r.getBeanField(), true)); } return missingRequiredHeaders; }
This method generates a header that can be used for writing beans of the type provided back to a file.

The ordering of the headers is determined by the Comparator passed in to setColumnOrderOnWrite(Comparator), should that method be called, otherwise the natural ordering is used (alphabetically ascending).

This implementation will not write headers discovered in multi-valued bean fields if the headers would not be matched by the bean field on reading. There are two reasons for this:

  1. opencsv always tries to create data that are round-trip equivalent, and that would not be the case if it generated data on writing that it would discard on reading.
  2. As the code is currently written, the header name is used on writing each bean field to determine the appropriate BeanField for information concerning conversions, locales, necessity (whether or not the field is required). Without this information, conversion is impossible, and every value written under the unmatched header is blank, regardless of the contents of the bean.
/** * This method generates a header that can be used for writing beans of the * type provided back to a file. * <p>The ordering of the headers is determined by the * {@link java.util.Comparator} passed in to * {@link #setColumnOrderOnWrite(Comparator)}, should that method be called, * otherwise the natural ordering is used (alphabetically ascending).</p> * <p>This implementation will not write headers discovered in multi-valued * bean fields if the headers would not be matched by the bean field on * reading. There are two reasons for this:</p> * <ol><li>opencsv always tries to create data that are round-trip * equivalent, and that would not be the case if it generated data on * writing that it would discard on reading.</li> * <li>As the code is currently written, the header name is used on writing * each bean field to determine the appropriate {@link BeanField} for * information concerning conversions, locales, necessity (whether or not * the field is required). Without this information, conversion is * impossible, and every value written under the unmatched header is blank, * regardless of the contents of the bean.</li></ol> */
// The rest of the Javadoc is inherited. @Override public String[] generateHeader(final T bean) throws CsvRequiredFieldEmptyException { final List<Field> missingRequiredHeaders = new LinkedList<>(); final List<String> headerList = new ArrayList<>(simpleMap.keySet()); for(ComplexFieldMapEntry<String, String, T> r : complexMapList) { @SuppressWarnings("unchecked") final MultiValuedMap<String,T> m = (MultiValuedMap<String,T>) r.getBeanField().getFieldValue(bean); if(m != null && !m.isEmpty()) { headerList.addAll(m.entries().stream() .map(Map.Entry::getKey) .filter(r::contains) .collect(Collectors.toList())); } else { if(r.getBeanField().isRequired()) { missingRequiredHeaders.add(r.getBeanField().getField()); } } } // Report headers that should have been present if(!missingRequiredHeaders.isEmpty()) { String errorMessage = String.format( ResourceBundle .getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale) .getString("header.required.field.absent"), missingRequiredHeaders.stream() .map(Field::getName) .collect(Collectors.joining(" ")), String.join(" ", headerList)); throw new CsvRequiredFieldEmptyException(bean.getClass(), missingRequiredHeaders, errorMessage); } // Sort and return headerList.sort(writeOrder); return headerList.toArray(ArrayUtils.EMPTY_STRING_ARRAY); }
Sets the Comparator to be used to sort columns when writing beans to a CSV file.
Params:
  • writeOrder – The Comparator to use. May be null, in which case the natural ordering is used.
Since:4.3
/** * Sets the {@link java.util.Comparator} to be used to sort columns when * writing beans to a CSV file. * * @param writeOrder The {@link java.util.Comparator} to use. May be * {@code null}, in which case the natural ordering is used. * @since 4.3 */
public void setColumnOrderOnWrite(Comparator<String> writeOrder) { this.writeOrder = writeOrder; } }