View Javadoc
1   /*
2    * Copyright 2018 Andrew Rucker Jones.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.opencsv.bean;
17  
18  import java.util.Arrays;
19  import java.util.Collection;
20  import org.apache.commons.collections4.MultiValuedMap;
21  import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
22  import org.apache.commons.lang3.ArrayUtils;
23  
24  /**
25   * A bi-directional mapping between column positions and header names.
26   * A simple {@link org.apache.commons.collections4.BidiMap} will not do the
27   * trick, because header names (or null in place of a header name) may appear
28   * more than once.
29   * 
30   * @author Andrew Rucker Jones
31   * @since 4.2
32   */
33  public class HeaderIndex {
34      
35      /** The uni-directional map from column position to header name. */
36      private String[] positionToHeader = ArrayUtils.EMPTY_STRING_ARRAY;
37      
38      /**
39       * The uni-directional map from header name to (possibly multiple) column
40       * positions.
41       */
42      private MultiValuedMap<String, Integer> headerToPosition = new ArrayListValuedHashMap<>();
43      
44      /** Useless but explicit nullary constructor to make the style checker happy. */
45      public HeaderIndex(){}
46      
47      /**
48       * Empties the entire mapping.
49       */
50      public void clear() {
51          positionToHeader = ArrayUtils.EMPTY_STRING_ARRAY;
52          headerToPosition.clear();
53      }
54      
55      /**
56       * Finds and returns the highest index in this mapping.
57       * @return The maximum index that is mapped and will return a header name
58       *   (or null if specifically mapped that way). If there are no columns in
59       *   the mapping, returns -1.
60       */
61      public int findMaxIndex() {
62          return positionToHeader.length-1;
63      }
64      
65      /**
66       * Initializes the index with a list of header names in proper encounter
67       * order.
68       * "Proper encounter order" means the order in which they are expected to be
69       * found in the input CSV. Header names may be listed more than once if the
70       * destination field is annotated with {@link CsvBindAndJoinByPosition} or
71       * {@link CsvBindAndJoinByName}. Values of {@code null} indicate the column
72       * from the input should not be mapped to a bean field.
73       * 
74       * @param header A list of header names in the order in which they are
75       *   expected in the CSV input
76       */
77      public void initializeHeaderIndex(String[] header) {
78          positionToHeader = header != null ? ArrayUtils.clone(header): ArrayUtils.EMPTY_STRING_ARRAY;
79          headerToPosition.clear();
80          int i = 0;
81          while(i < positionToHeader.length) {
82              headerToPosition.put(header[i], i);
83              i++;
84          }
85      }
86      
87      /** @return Whether or not the mapping is empty */
88      public boolean isEmpty() {
89          return positionToHeader.length == 0;
90      }
91      
92      /**
93       * Retrieves the column position(s) associated with the given header name.
94       * 
95       * @param headerName The header name for which the associated column
96       *   positions should be returned
97       * @return The column positions associated with {@code headerName}
98       */
99      public int[] getByName(String headerName) {
100         Collection<Integer> positions = headerToPosition.get(headerName);
101         if(positions != null) {
102             return ArrayUtils.toPrimitive(positions.toArray(ArrayUtils.EMPTY_INTEGER_OBJECT_ARRAY));
103         }
104         return ArrayUtils.EMPTY_INT_ARRAY;
105     }
106     
107     /**
108      * Retrieves the header associated with the given column position.
109      * 
110      * @param i The column position for which the header name is to be retrieved
111      * @return The header name mapped by position {@code i}
112      */
113     public String getByPosition(int i) {
114         if(i < positionToHeader.length) {
115             return positionToHeader[i];
116         }
117         return null;
118     }
119     
120     /**
121      * @return The current list of headers mapped by this index in the proper
122      *   order
123      */
124     public String[] getHeaderIndex() {
125         return ArrayUtils.clone(positionToHeader);
126     }
127     
128     /**
129      * @return The length of the current mapping, including all fields unmapped
130      */
131     public int getHeaderIndexLength() {return positionToHeader.length;}
132     
133     /**
134      * Adds a new mapping between a column position and a header.
135      * The header may already be present, in which case the column position is
136      * added to the list of column positions mapped to the header.
137      * 
138      * @param k The column position for the mapping
139      * @param v The header to be associated with the column position
140      */
141     public void put(int k, String v) {
142         if(k >= positionToHeader.length) {
143             positionToHeader = Arrays.copyOf(positionToHeader, k+1);
144             positionToHeader[k] = v;
145         }
146         headerToPosition.put(v, k);
147     }
148 }