View Javadoc
1   package com.opencsv;
2   
3   import com.opencsv.exceptions.CsvValidationException;
4   import com.opencsv.processor.RowProcessor;
5   import com.opencsv.validators.LineValidatorAggregator;
6   import com.opencsv.validators.RowValidatorAggregator;
7   
8   import java.io.IOException;
9   import java.io.Reader;
10  import java.util.HashMap;
11  import java.util.Locale;
12  import java.util.Map;
13  import java.util.ResourceBundle;
14  
15  /**
16   * Handy reader when there's insufficient motivation to use the bean binding but
17   * the header mapping is still desired.
18   *
19   * @author Andre Rosot
20   * @since 4.2
21   */
22  public class CSVReaderHeaderAware extends CSVReader {
23  
24      private final Map<String, Integer> headerIndex = new HashMap<>();
25  
26      /**
27       * Constructor with supplied reader.
28       *
29       * @param reader The reader to an underlying CSV source
30       * @throws IOException If there is an error when reading the header
31       */
32      public CSVReaderHeaderAware(Reader reader) throws IOException {
33          super(reader);
34          initializeHeader();
35      }
36  
37      /**
38       * Supports non-deprecated constructor from the parent class.
39       * Like the CSVReader this constructor is package scope so only the builder can use it.
40       *
41       * @param reader         The reader to an underlying CSV source
42       * @param skipLines      The number of lines to skip before reading
43       * @param parser         The parser to use to parse input
44       * @param keepCR         True to keep carriage returns in data read, false otherwise
45       * @param verifyReader   True to verify reader before each read, false otherwise
46       * @param multilineLimit Allow the user to define the limit to the number of lines in a multiline record. Less than one means no limit.
47       * @param errorLocale    Set the locale for error messages. If null, the default locale is used.
48       * @param lineValidatorAggregator contains all the custom defined line validators.
49       * @param rowValidatorAggregator  contains all the custom defined row validators.
50       * @param rowProcessor            Custom row processor to run on all columns on a csv record.
51       * @throws IOException   If bad things happen while initializing the header
52       */
53      CSVReaderHeaderAware(Reader reader, int skipLines, ICSVParser parser, boolean keepCR, boolean verifyReader,
54                           int multilineLimit, Locale errorLocale, LineValidatorAggregator lineValidatorAggregator,
55                           RowValidatorAggregator rowValidatorAggregator, RowProcessor rowProcessor) throws IOException {
56          super(reader, skipLines, parser, keepCR, verifyReader, multilineLimit, errorLocale, lineValidatorAggregator, rowValidatorAggregator, rowProcessor);
57          initializeHeader();
58      }
59  
60      /**
61       * Retrieves a specific data element from a line based on the value of the header.
62       *
63       * @param headerNames Name of the header element whose data we are trying to find
64       * @return The data element whose position matches that of the header whose value is passed in. Will return null when there are no more data elements.
65       * @throws IOException              An error occured during the read or there is a mismatch in the number of data items in a row
66       *                                  and the number of header items
67       * @throws IllegalArgumentException If headerName does not exist
68       * @throws CsvValidationException If a custom defined validator fails.
69       */
70      public String[] readNext(String... headerNames) throws IOException, CsvValidationException {
71          if (headerNames == null) {
72              return super.readNextSilently();
73          }
74  
75          String[] strings = readNext();
76          if (strings == null) {
77              return null;
78          }
79  
80          if (strings.length != headerIndex.size()) {
81              throw new IOException(String.format(
82                      ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
83                              .getString("header.data.mismatch.with.line.number"),
84                      getRecordsRead(), headerIndex.size(), strings.length));
85          }
86  
87          String[] response = new String[headerNames.length];
88  
89          for (int i = 0; i < headerNames.length; i++) {
90              String headerName = headerNames[i];
91  
92              Integer index = headerIndex.get(headerName);
93              if (index == null) {
94                  throw new IllegalArgumentException(String.format(
95                          ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
96                                  .getString("header.nonexistant"),
97                          headerName));
98              }
99  
100             response[i] = strings[index];
101         }
102         return response;
103     }
104 
105     /**
106      * Reads the next line and returns a map of header values and data values.
107      *
108      * @return A map whose key is the header row of the data file and the values is the data values. Or null if the line is blank.
109      * @throws IOException An error occurred during the read or there is a mismatch in the number of data items in a row
110      *                     and the number of header items.
111      * @throws CsvValidationException If a custom defined validator fails.
112      */
113     public Map<String, String> readMap() throws IOException, CsvValidationException {
114         String[] strings = readNext();
115         if (strings == null) {
116             return null;
117         }
118         if (strings.length != headerIndex.size()) {
119             throw new IOException(String.format(
120                     ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
121                             .getString("header.data.mismatch.with.line.number"),
122                     getRecordsRead(), headerIndex.size(), strings.length));
123         }
124 
125         // This code cannot be done with a stream and Collectors.toMap()
126         // because Map.merge() does not play well with null values. Some
127         // implementations throw a NullPointerException, others simply remove
128         // the key from the map.
129         Map<String, String> resultMap = new HashMap<>(headerIndex.size()*2);
130         for(Map.Entry<String, Integer> entry : headerIndex.entrySet()) {
131             if(entry.getValue() < strings.length) {
132                 resultMap.put(entry.getKey(), strings[entry.getValue()]);
133             }
134         }
135         return resultMap;
136     }
137 
138     private void initializeHeader() throws IOException {
139         String[] headers = super.readNextSilently();
140         for (int i = 0; i < headers.length; i++) {
141             headerIndex.put(headers[i], i);
142         }
143     }
144 
145 }