Coverage Report - net.sf.jmatchparser.util.csv.fieldreader.FieldReader
 
Classes in this File Line Coverage Branch Coverage Complexity
FieldReader
92%
75/81
85%
41/48
4,1
 
 1  
 /*
 2  
  * Copyright (c) 2008 - 2011 Michael Schierl
 3  
  * 
 4  
  * All rights reserved.
 5  
  * 
 6  
  * Redistribution and use in source and binary forms, with or without
 7  
  * modification, are permitted provided that the following conditions
 8  
  * are met:
 9  
  * 
 10  
  * - Redistributions of source code must retain the above copyright notice,
 11  
  *   this list of conditions and the following disclaimer.
 12  
  *   
 13  
  * - Redistributions in binary form must reproduce the above copyright
 14  
  *   notice, this list of conditions and the following disclaimer in the
 15  
  *   documentation and/or other materials provided with the distribution.
 16  
  *   
 17  
  * - Neither name of the copyright holders nor the names of its
 18  
  *   contributors may be used to endorse or promote products derived from
 19  
  *   this software without specific prior written permission.
 20  
  *   
 21  
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND THE CONTRIBUTORS
 22  
  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 23  
  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 24  
  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 25  
  * HOLDERS OR THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 26  
  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 27  
  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 28  
  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 29  
  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 30  
  * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 31  
  * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 32  
  */
 33  
 package net.sf.jmatchparser.util.csv.fieldreader;
 34  
 
 35  
 import java.io.IOException;
 36  
 import java.util.Collection;
 37  
 import java.util.HashMap;
 38  
 import java.util.Map;
 39  
 
 40  
 import net.sf.jmatchparser.util.csv.AbstractCSVReader;
 41  
 import net.sf.jmatchparser.util.csv.fieldreader.FieldDefinition.MandatoryState;
 42  
 import net.sf.jmatchparser.util.csv.fieldreader.FieldReaderException.FieldReaderExceptionInfo;
 43  
 import net.sf.jmatchparser.util.csv.fieldreader.FieldSource.EmptyAction;
 44  
 
 45  
 /**
 46  
  * A parser that can be used to parse different CSV files into the same set of
 47  
  * records. It is useful for building parsers that need a fixed range of
 48  
  * information, but the files that contain them can have a multitude of
 49  
  * different formats; often the parser that consumes the records of this field
 50  
  * reader (who supplies {@link FieldDefinition}s) is in a different library or
 51  
  * component than the creator of this field reader (who supplies
 52  
  * {@link FieldSource}s).
 53  
  * 
 54  
  * <p>
 55  
  * Each record is formatted by this field reader into a {@link Map} that maps
 56  
  * fields (usually either {@link String}s or an {@link Enum} that implements
 57  
  * {@link FieldEnum}, see {@link EnumFieldReader}) to values (sometimes strings,
 58  
  * but often other objects as well).
 59  
  * 
 60  
  * <p>
 61  
  * By convention, keys that are mapped to <code>null</code> are expected to be
 62  
  * cleared by the parser (when updating anything), and records that are not
 63  
  * present are expected not to be touched. Therefore, parsers that use this
 64  
  * reader should use the {@link Map#containsKey(Object)} method to determine if
 65  
  * a key is present, instead of checking the value of {@link Map#get(Object)}
 66  
  * for <code>null</code>. Alternatively, the {@link #setEmptyFieldValue(Object)}
 67  
  * method can be used to set the "empty" (clear) value to something else.
 68  
  * 
 69  
  * @param <K>
 70  
  *            The type of the fields
 71  
  * @param <V>
 72  
  *            The type of the parsed values
 73  
  */
 74  
 public class FieldReader<K, V> {
 75  
 
 76  
         private final Map<K, FieldDefinition<? extends V>> fieldDefinitions;
 77  
         private final AbstractCSVReader reader;
 78  5
         private Map<K, FieldSource> fieldSources = null;
 79  5
         private V emptyFieldValue = null;
 80  5
         private String[] header = null;
 81  
 
 82  
         /**
 83  
          * Create a new field reader.
 84  
          * 
 85  
          * @param fieldDefinitions
 86  
          *            Field definitions to use
 87  
          * @param reader
 88  
          *            Reader to read the records from
 89  
          */
 90  5
         public FieldReader(Map<K, FieldDefinition<? extends V>> fieldDefinitions, AbstractCSVReader reader) {
 91  5
                 this.fieldDefinitions = fieldDefinitions;
 92  5
                 this.reader = reader;
 93  5
         }
 94  
 
 95  
         /**
 96  
          * Set the {@link FieldSource}s to be used for this field reader.
 97  
          */
 98  
         public void setFieldSources(Map<K, FieldSource> fieldSources) {
 99  5
                 if (this.fieldSources != null)
 100  0
                         throw new IllegalStateException("Field sources have already been assigned");
 101  5
                 this.fieldSources = fieldSources;
 102  5
                 for (K key : fieldSources.keySet()) {
 103  21
                         if (!fieldDefinitions.containsKey(key)) {
 104  0
                                 throw new IllegalStateException("Field source for field without definition: " + key);
 105  
                         }
 106  
                 }
 107  5
         }
 108  
 
 109  
         /**
 110  
          * Compute the field sources automatically from the index of the key inside
 111  
          * the given array.
 112  
          * 
 113  
          * To be used when header parsing is disabled.
 114  
          * 
 115  
          * @param emptyAction
 116  
          *            {@link EmptyAction} to use
 117  
          * @param fields
 118  
          *            Fields in the desired order
 119  
          */
 120  
         public void computeFieldSourcesFromIndex(EmptyAction emptyAction, K[] fields) {
 121  1
                 Map<K, FieldSource> fieldSources = new HashMap<K, FieldSource>();
 122  6
                 for (int i = 0; i < fields.length; i++) {
 123  5
                         fieldSources.put(fields[i], FieldSource.withChecks(FieldSource.fromColumn("" + (i + 1)), emptyAction, ".*"));
 124  
                 }
 125  1
                 setFieldSources(fieldSources);
 126  1
         }
 127  
 
 128  
         /**
 129  
          * Compute the field sources automatically from the names (see
 130  
          * {@link #toString()}) of all fields
 131  
          * 
 132  
          * To be used when header parsing is enabled.
 133  
          * 
 134  
          * @param emptyAction
 135  
          *            {@link EmptyAction} to use
 136  
          */
 137  
         public void computeFieldSourcesFromName(EmptyAction emptyAction) {
 138  3
                 computeFieldSourcesFromName(emptyAction, fieldDefinitions.keySet());
 139  3
         }
 140  
 
 141  
         /**
 142  
          * Compute the field sources automatically from the names (see
 143  
          * {@link #toString()}) of the given fields
 144  
          * 
 145  
          * To be used when header parsing is enabled.
 146  
          * 
 147  
          * @param emptyAction
 148  
          *            {@link EmptyAction} to use
 149  
          * @param fields
 150  
          *            Fields to use
 151  
          */
 152  
         public void computeFieldSourcesFromName(EmptyAction emptyAction, Collection<K> fields) {
 153  3
                 Map<K, FieldSource> fieldSources = new HashMap<K, FieldSource>();
 154  3
                 for (K key : fields) {
 155  11
                         fieldSources.put(key, FieldSource.withChecks(FieldSource.fromColumn(key.toString()), emptyAction, ".*"));
 156  
                 }
 157  3
                 setFieldSources(fieldSources);
 158  3
         }
 159  
 
 160  
         /**
 161  
          * Read a header line from the CSV reader, and optionally use it for column
 162  
          * assignment.
 163  
          * 
 164  
          * @param ignore
 165  
          *            Whether to ignore the read header (not use it for column
 166  
          *            assignment)
 167  
          */
 168  
         public void readHeader(boolean ignore) throws IOException {
 169  5
                 String[] header = reader.read();
 170  5
                 if (!ignore)
 171  3
                         this.header = header;
 172  5
         }
 173  
 
 174  
         /**
 175  
          * Set the value that should be used for records that should be updated, but
 176  
          * to an empty value (by default <code>null</code>).
 177  
          * 
 178  
          * @param emptyFieldValue
 179  
          *            new value for empty fields
 180  
          */
 181  
         public void setEmptyFieldValue(V emptyFieldValue) {
 182  3
                 this.emptyFieldValue = emptyFieldValue;
 183  3
         }
 184  
 
 185  
         /**
 186  
          * Read a record from this field reader.
 187  
          */
 188  
         public Map<K, V> read() throws IOException, FieldReaderException {
 189  20
                 if (fieldSources == null)
 190  0
                         throw new IllegalStateException("Field sources have not been assigned");
 191  20
                 String[] record = reader.read();
 192  
 
 193  20
                 if (record == null)
 194  7
                         return null;
 195  
 
 196  13
                 Map<String, String> rawRecord = new HashMap<String, String>();
 197  58
                 for (int i = 0; i < record.length; i++) {
 198  45
                         if (record[i].length() == 0)
 199  10
                                 continue;
 200  35
                         String key = (header != null && i < header.length) ? header[i] : "" + (i + 1);
 201  35
                         rawRecord.put(key, record[i]);
 202  
                 }
 203  13
                 Map<K, V> result = new HashMap<K, V>();
 204  13
                 for (Map.Entry<K, FieldSource> sourceEntry : fieldSources.entrySet()) {
 205  61
                         K key = sourceEntry.getKey();
 206  61
                         FieldSource source = sourceEntry.getValue();
 207  61
                         FieldDefinition<? extends V> definition = fieldDefinitions.get(key);
 208  61
                         String stringValue = source.getValue(rawRecord);
 209  61
                         if (stringValue == null)
 210  20
                                 continue;
 211  41
                         if (stringValue.length() == 0) {
 212  2
                                 result.put(key, emptyFieldValue);
 213  
                         } else {
 214  39
                                 result.put(key, definition.parse(stringValue, source.getField(), rawRecord));
 215  
                         }
 216  41
                 }
 217  13
                 for (Map.Entry<K, FieldDefinition<? extends V>> definitionEntry : fieldDefinitions.entrySet()) {
 218  61
                         K key = definitionEntry.getKey();
 219  61
                         FieldDefinition<? extends V> definition = definitionEntry.getValue();
 220  61
                         if (!result.containsKey(key) && definition.getMandatory() == MandatoryState.MANDATORY) {
 221  0
                                 throw new FieldReaderException(FieldReaderErrorCodes.MISSING, false, new FieldReaderExceptionInfo<K, V>(key, null, result));
 222  
                         }
 223  61
                 }
 224  13
                 return result;
 225  
         }
 226  
 
 227  
         /**
 228  
          * Verify and postprocess a new record (i. e. one that does not already
 229  
          * exist in the underlying database and has to be created instead of
 230  
          * updated.
 231  
          * 
 232  
          * Note that the current implementation does not perform any postprocessing
 233  
          * and returns the record as is.
 234  
          * 
 235  
          * @param record
 236  
          *            Record that has been read by this field reader before
 237  
          * @return postprocessed record
 238  
          * @throws FieldReaderException
 239  
          *             if mandatory fields are missing
 240  
          */
 241  
         public Map<K, V> postprocessNewRecord(Map<K, V> record) throws FieldReaderException {
 242  4
                 for (Map.Entry<K, FieldSource> sourceEntry : fieldSources.entrySet()) {
 243  20
                         K key = sourceEntry.getKey();
 244  20
                         FieldSource source = sourceEntry.getValue();
 245  20
                         if (record.containsKey(key))
 246  14
                                 continue;
 247  6
                         if (source.isMandatoryIfNew()) {
 248  0
                                 throw new FieldReaderException(FieldReaderErrorCodes.MISSING_MANDATORYIFNEW, false, new FieldReaderExceptionInfo<K, V>(key, null, record));
 249  
                         }
 250  6
                 }
 251  4
                 for (Map.Entry<K, FieldDefinition<? extends V>> definitionEntry : fieldDefinitions.entrySet()) {
 252  20
                         K key = definitionEntry.getKey();
 253  20
                         FieldDefinition<? extends V> definition = definitionEntry.getValue();
 254  20
                         if (record.containsKey(key))
 255  14
                                 continue;
 256  6
                         if (definition.getMandatory() == MandatoryState.MANDATORY_IF_NEW) {
 257  0
                                 throw new FieldReaderException(FieldReaderErrorCodes.MISSING_MANDATORYIFNEW, false, new FieldReaderExceptionInfo<K, V>(key, null, record));
 258  
                         }
 259  6
                 }
 260  4
                 return record;
 261  
         }
 262  
 
 263  
         /**
 264  
          * Close this field reader.
 265  
          */
 266  
         public void close() throws IOException {
 267  5
                 reader.close();
 268  5
         }
 269  
 }