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 }