HeaderIndex.java

  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. import java.util.Arrays;
  18. import java.util.Collection;
  19. import org.apache.commons.collections4.MultiValuedMap;
  20. import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
  21. import org.apache.commons.lang3.ArrayUtils;

  22. /**
  23.  * A bi-directional mapping between column positions and header names.
  24.  * A simple {@link org.apache.commons.collections4.BidiMap} will not do the
  25.  * trick, because header names (or null in place of a header name) may appear
  26.  * more than once.
  27.  *
  28.  * @author Andrew Rucker Jones
  29.  * @since 4.2
  30.  */
  31. public class HeaderIndex {
  32.    
  33.     /** The uni-directional map from column position to header name. */
  34.     private String[] positionToHeader = ArrayUtils.EMPTY_STRING_ARRAY;
  35.    
  36.     /**
  37.      * The uni-directional map from header name to (possibly multiple) column
  38.      * positions.
  39.      */
  40.     private MultiValuedMap<String, Integer> headerToPosition = new ArrayListValuedHashMap<>();
  41.    
  42.     /** Useless but explicit nullary constructor to make the style checker happy. */
  43.     public HeaderIndex(){}
  44.    
  45.     /**
  46.      * Empties the entire mapping.
  47.      */
  48.     public void clear() {
  49.         positionToHeader = ArrayUtils.EMPTY_STRING_ARRAY;
  50.         headerToPosition.clear();
  51.     }
  52.    
  53.     /**
  54.      * Finds and returns the highest index in this mapping.
  55.      * @return The maximum index that is mapped and will return a header name
  56.      *   (or null if specifically mapped that way). If there are no columns in
  57.      *   the mapping, returns -1.
  58.      */
  59.     public int findMaxIndex() {
  60.         return positionToHeader.length-1;
  61.     }
  62.    
  63.     /**
  64.      * Initializes the index with a list of header names in proper encounter
  65.      * order.
  66.      * "Proper encounter order" means the order in which they are expected to be
  67.      * found in the input CSV. Header names may be listed more than once if the
  68.      * destination field is annotated with {@link CsvBindAndJoinByPosition} or
  69.      * {@link CsvBindAndJoinByName}. Values of {@code null} indicate the column
  70.      * from the input should not be mapped to a bean field.
  71.      *
  72.      * @param header A list of header names in the order in which they are
  73.      *   expected in the CSV input
  74.      */
  75.     public void initializeHeaderIndex(String[] header) {
  76.         positionToHeader = header != null ? ArrayUtils.clone(header): ArrayUtils.EMPTY_STRING_ARRAY;
  77.         headerToPosition.clear();
  78.         int i = 0;
  79.         while(i < positionToHeader.length) {
  80.             headerToPosition.put(header[i], i);
  81.             i++;
  82.         }
  83.     }
  84.    
  85.     /** @return Whether or not the mapping is empty */
  86.     public boolean isEmpty() {
  87.         return positionToHeader.length == 0;
  88.     }
  89.    
  90.     /**
  91.      * Retrieves the column position(s) associated with the given header name.
  92.      *
  93.      * @param headerName The header name for which the associated column
  94.      *   positions should be returned
  95.      * @return The column positions associated with {@code headerName}
  96.      */
  97.     public int[] getByName(String headerName) {
  98.         Collection<Integer> positions = headerToPosition.get(headerName);
  99.         if(positions != null) {
  100.             return ArrayUtils.toPrimitive(positions.toArray(ArrayUtils.EMPTY_INTEGER_OBJECT_ARRAY));
  101.         }
  102.         return ArrayUtils.EMPTY_INT_ARRAY;
  103.     }
  104.    
  105.     /**
  106.      * Retrieves the header associated with the given column position.
  107.      *
  108.      * @param i The column position for which the header name is to be retrieved
  109.      * @return The header name mapped by position {@code i}
  110.      */
  111.     public String getByPosition(int i) {
  112.         if(i < positionToHeader.length) {
  113.             return positionToHeader[i];
  114.         }
  115.         return null;
  116.     }
  117.    
  118.     /**
  119.      * @return The current list of headers mapped by this index in the proper
  120.      *   order
  121.      */
  122.     public String[] getHeaderIndex() {
  123.         return ArrayUtils.clone(positionToHeader);
  124.     }
  125.    
  126.     /**
  127.      * @return The length of the current mapping, including all fields unmapped
  128.      */
  129.     public int getHeaderIndexLength() {return positionToHeader.length;}
  130.    
  131.     /**
  132.      * Adds a new mapping between a column position and a header.
  133.      * The header may already be present, in which case the column position is
  134.      * added to the list of column positions mapped to the header.
  135.      *
  136.      * @param k The column position for the mapping
  137.      * @param v The header to be associated with the column position
  138.      */
  139.     public void put(int k, String v) {
  140.         if(k >= positionToHeader.length) {
  141.             positionToHeader = Arrays.copyOf(positionToHeader, k+1);
  142.             positionToHeader[k] = v;
  143.         }
  144.         headerToPosition.put(v, k);
  145.     }
  146. }