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 }