Coverage Report - net.sf.jmatchparser.util.csv.fieldreader.FieldSource
 
Classes in this File Line Coverage Branch Coverage Complexity
FieldSource
97%
47/48
83%
20/24
2,038
FieldSource$1
100%
3/3
N/A
2,038
FieldSource$2
100%
4/4
100%
2/2
2,038
FieldSource$3
100%
9/9
100%
4/4
2,038
FieldSource$4
66%
4/6
N/A
2,038
FieldSource$5
83%
10/12
78%
11/14
2,038
FieldSource$EmptyAction
100%
5/5
N/A
2,038
 
 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.text.ParseException;
 36  
 import java.util.ArrayList;
 37  
 import java.util.List;
 38  
 import java.util.Map;
 39  
 import java.util.StringTokenizer;
 40  
 import java.util.regex.Matcher;
 41  
 import java.util.regex.Pattern;
 42  
 
 43  
 import net.sf.jmatchparser.util.PatternCache;
 44  
 import net.sf.jmatchparser.util.csv.fieldreader.FieldReaderException.FieldReaderExceptionInfo;
 45  
 
 46  
 /**
 47  
  * A description of the "source" from where data should be filled into the
 48  
  * field, as seen from the perspective of the "provider" that provides the CSV
 49  
  * files.
 50  
  */
 51  59
 public abstract class FieldSource {
 52  
 
 53  
         /**
 54  
          * Return the value of this field source.
 55  
          * 
 56  
          * @param columns
 57  
          *            Hashtable of fields read from the CSV file
 58  
          * @return Value, or <code>null</code> if the field should be treated as
 59  
          *         absent
 60  
          */
 61  
         public abstract String getValue(Map<String, String> columns) throws FieldReaderException;
 62  
 
 63  
         /**
 64  
          * Return an artificial name for this field, used for debugging and in error
 65  
          * messages.
 66  
          */
 67  
         public abstract String getField();
 68  
 
 69  
         /**
 70  
          * Return whether this field has to be filled for new records
 71  
          */
 72  
         public boolean isMandatoryIfNew() {
 73  0
                 return false;
 74  
         }
 75  
 
 76  
         /**
 77  
          * Create a new field source that always returns the same constant value.
 78  
          * 
 79  
          * @param constant
 80  
          *            The constant value
 81  
          */
 82  
         public static FieldSource fromConstant(final String constant) {
 83  7
                 return new FieldSource() {
 84  
                         @Override
 85  
                         public String getValue(Map<String, String> columns) {
 86  21
                                 return constant;
 87  
                         }
 88  
 
 89  
                         @Override
 90  
                         public String getField() {
 91  17
                                 return constant;
 92  
                         }
 93  
                 };
 94  
         }
 95  
 
 96  
         /**
 97  
          * Create a new field source that returns a value from a CSV column.
 98  
          * 
 99  
          * @param columnName
 100  
          *            Name or index of the column
 101  
          */
 102  
         public static FieldSource fromColumn(final String columnName) {
 103  23
                 return new FieldSource() {
 104  
                         @Override
 105  
                         public String getValue(Map<String, String> columns) {
 106  67
                                 String result = columns.get(columnName);
 107  67
                                 return result == null ? "" : result;
 108  
                         }
 109  
 
 110  
                         @Override
 111  
                         public String getField() {
 112  45
                                 return "{" + columnName + "}";
 113  
                         }
 114  
                 };
 115  
         }
 116  
 
 117  
         /**
 118  
          * Create a new field source that returns a value concatenated from several
 119  
          * other {@link FieldSource}s.
 120  
          * 
 121  
          * @param sources
 122  
          *            Other field sources to be concatenated
 123  
          */
 124  
         public static FieldSource concat(final FieldSource... sources) {
 125  5
                 return new FieldSource() {
 126  
                         @Override
 127  
                         public String getValue(Map<String, String> columns) throws FieldReaderException {
 128  15
                                 StringBuilder sb = new StringBuilder();
 129  57
                                 for (FieldSource source : sources) {
 130  42
                                         sb.append(source.getValue(columns));
 131  
                                 }
 132  15
                                 return sb.toString();
 133  
                         }
 134  
 
 135  
                         @Override
 136  
                         public String getField() {
 137  10
                                 StringBuilder sb = new StringBuilder();
 138  43
                                 for (FieldSource source : sources) {
 139  33
                                         sb.append(source.getField());
 140  
                                 }
 141  10
                                 return sb.toString();
 142  
                         }
 143  
                 };
 144  
         }
 145  
 
 146  
         /**
 147  
          * Create a field source that performs a series of regex replacements on
 148  
          * another field source.
 149  
          * 
 150  
          * @param source
 151  
          *            Field source
 152  
          * @param mappings
 153  
          *            A list delimited by <tt>;;</tt> of regex replacement pairs,
 154  
          *            where <tt>::</tt> separates regex and replacement, or
 155  
          *            <code>null</code> for no mappings
 156  
          */
 157  
         public static FieldSource withMappings(FieldSource source, String mappings) {
 158  5
                 if (mappings == null)
 159  2
                         return source;
 160  3
                 final String[] mappingArray = mappings.split(";;");
 161  6
                 for (int i = mappingArray.length - 1; i >= 0; i--) {
 162  3
                         String[] parts = mappingArray[i].split("::", 2);
 163  3
                         if (parts.length == 2) {
 164  3
                                 source = withMapping(source, parts[0], parts[1]);
 165  
                         }
 166  
                 }
 167  3
                 return source;
 168  
         }
 169  
 
 170  
         /**
 171  
          * Create a field source that performs a regex replacement on another field
 172  
          * source.
 173  
          * 
 174  
          * @param source
 175  
          *            Field source
 176  
          * @param regex
 177  
          *            regular expression
 178  
          * @param replacement
 179  
          *            replacement string
 180  
          */
 181  
         public static FieldSource withMapping(FieldSource source, String regex, String replacement) {
 182  3
                 return withMapping(source, PatternCache.compile(regex), replacement);
 183  
         }
 184  
 
 185  
         /**
 186  
          * Create a field source that performs a regex replacement on another field
 187  
          * source.
 188  
          * 
 189  
          * @param source
 190  
          *            Field source
 191  
          * @param regex
 192  
          *            regular expression
 193  
          * @param replacement
 194  
          *            replacement string
 195  
          */
 196  
         public static FieldSource withMapping(FieldSource source, Pattern regex, String replacement) {
 197  3
                 return withFormat(source, ParseFormat.fromReplacement(regex, replacement, ParseFormat.IDENTITY));
 198  
         }
 199  
 
 200  
         /**
 201  
          * Create a field source that applies a {@link ParseFormat} on another field
 202  
          * source.
 203  
          * 
 204  
          * @param source
 205  
          *            Field source
 206  
          * @param format
 207  
          *            {@link ParseFormat}
 208  
          */
 209  
         public static FieldSource withFormat(final FieldSource source, final ParseFormat<String> format) {
 210  3
                 return new FieldSource() {
 211  
 
 212  
                         @Override
 213  
                         public String getValue(Map<String, String> columns) throws FieldReaderException {
 214  9
                                 String value = source.getValue(columns);
 215  
                                 try {
 216  9
                                         return format.parse(value);
 217  0
                                 } catch (ParseException ex) {
 218  0
                                         throw new FieldReaderException(FieldReaderErrorCodes.INVALID_FORMAT, true, new FieldReaderExceptionInfo<String, String>(source.getField(), value, columns), ex);
 219  
                                 }
 220  
                         }
 221  
 
 222  
                         @Override
 223  
                         public String getField() {
 224  4
                                 return source.getField();
 225  
                         }
 226  
                 };
 227  
         }
 228  
 
 229  
         /**
 230  
          * Create a field source that performs checks on the output of another field
 231  
          * source and returns the result verbatim. This field source is usually the
 232  
          * outermost field source used in a stack of other field sources.
 233  
          * 
 234  
          * @param source
 235  
          *            field source
 236  
          * @param emptyAction
 237  
          *            Action to perform when the field source returns an empty
 238  
          *            string
 239  
          * @param mask
 240  
          *            regex pattern the field source result has to match, or
 241  
          *            <code>null</code>
 242  
          */
 243  
         public static FieldSource withChecks(FieldSource source, EmptyAction emptyAction, String mask) {
 244  21
                 return withChecks(source, emptyAction, mask == null ? null : PatternCache.compile(mask));
 245  
         }
 246  
 
 247  
         /**
 248  
          * Create a field source that performs checks on the output of another field
 249  
          * source and returns the result verbatim. This field source is usually the
 250  
          * outermost field source used in a stack of other field sources.
 251  
          * 
 252  
          * @param source
 253  
          *            field source
 254  
          * @param emptyAction
 255  
          *            Action to perform when the field source returns an empty
 256  
          *            string
 257  
          * @param mask
 258  
          *            regex pattern the field source result has to match, or
 259  
          *            <code>null</code>
 260  
          */
 261  
         public static FieldSource withChecks(final FieldSource source, final EmptyAction emptyAction, final Pattern mask) {
 262  21
                 return new FieldSource() {
 263  
 
 264  
                         @Override
 265  
                         public String getValue(Map<String, String> columns) throws FieldReaderException {
 266  61
                                 String value = source.getValue(columns);
 267  
 
 268  61
                                 if (value.length() == 0) {
 269  22
                                         if (emptyAction == EmptyAction.COMPLAIN) {
 270  0
                                                 throw new FieldReaderException(FieldReaderErrorCodes.MISSING, true, new FieldReaderExceptionInfo<String, String>(source.getField(), "", columns));
 271  22
                                         } else if (emptyAction != EmptyAction.CLEAR) {
 272  20
                                                 value = null;
 273  
                                         }
 274  
                                 }
 275  61
                                 if (value != null && mask != null
 276  
                                                 && !mask.matcher(value).matches()) {
 277  0
                                         throw new FieldReaderException(FieldReaderErrorCodes.INVALID_FORMAT, true, new FieldReaderExceptionInfo<String, String>(source.getField(), value, columns));
 278  
                                 }
 279  
 
 280  61
                                 return value;
 281  
                         }
 282  
 
 283  
                         @Override
 284  
                         public boolean isMandatoryIfNew() {
 285  6
                                 return emptyAction == EmptyAction.COMPLAIN_IF_NEW;
 286  
                         }
 287  
 
 288  
                         @Override
 289  
                         public String getField() {
 290  39
                                 return source.getField();
 291  
                         }
 292  
                 };
 293  
         }
 294  
 
 295  
         /**
 296  
          * Parse a field source from several parameters.
 297  
          * 
 298  
          * @param columns
 299  
          *            Columns spec, either one or multiple column names separated by
 300  
          *            one of <tt>|,;: </tt> (the separator will be added to the
 301  
          *            result), or a literal string where column names are included
 302  
          *            in curly braces
 303  
          * @param emptyAction
 304  
          *            Action to perform when the field source returns an empty
 305  
          *            string
 306  
          * @param mask
 307  
          *            regex pattern the field source result has to match
 308  
          * @param mappings
 309  
          *            A list delimited by <tt>;;</tt> of regex replacement pairs,
 310  
          *            where <tt>::</tt> separates regex and replacement.
 311  
          * 
 312  
          * @see #withChecks(FieldSource, EmptyAction, Pattern)
 313  
          * @see #withMappings(FieldSource, String)
 314  
          */
 315  
         public static FieldSource parse(String columns, EmptyAction emptyAction, String mask, String mappings) {
 316  
                 FieldSource[] sources;
 317  5
                 if (columns.indexOf('{') == -1)
 318  3
                         sources = parseLegacyColumns(columns);
 319  
                 else
 320  2
                         sources = parseColumns(columns);
 321  5
                 FieldSource result = concat(sources);
 322  5
                 result = withMappings(result, mappings);
 323  5
                 return withChecks(result, emptyAction, mask);
 324  
         }
 325  
 
 326  
         private static FieldSource[] parseColumns(String columns) {
 327  2
                 Matcher m = PatternCache.compile("\\{(.*?)\\}").matcher(columns);
 328  2
                 List<FieldSource> result = new ArrayList<FieldSource>();
 329  2
                 StringBuffer constantBuffer = new StringBuffer();
 330  6
                 while (m.find()) {
 331  4
                         m.appendReplacement(constantBuffer, "");
 332  4
                         if (constantBuffer.length() > 0) {
 333  4
                                 result.add(fromConstant(constantBuffer.toString()));
 334  4
                                 constantBuffer.setLength(0);
 335  
                         }
 336  
                         // workaround to be able to use fully constant strings by adding {}
 337  
                         // somewhere in them
 338  4
                         if (m.group(1).length() > 0) {
 339  4
                                 result.add(fromColumn(m.group(1)));
 340  
                         }
 341  
                 }
 342  2
                 m.appendTail(constantBuffer);
 343  2
                 if (constantBuffer.length() > 0) {
 344  1
                         result.add(fromConstant(constantBuffer.toString()));
 345  1
                         constantBuffer.setLength(0);
 346  
                 }
 347  2
                 return result.toArray(new FieldSource[result.size()]);
 348  
         }
 349  
 
 350  
         private static FieldSource[] parseLegacyColumns(String columns) {
 351  3
                 final StringTokenizer st = new StringTokenizer(columns, "|,;: ", true);
 352  3
                 final FieldSource[] result = new FieldSource[st.countTokens()];
 353  8
                 for (int i = 0; i < result.length; i++) {
 354  5
                         String token = st.nextToken();
 355  5
                         if (token.length() == 1 && "|,;: ".indexOf(token) != -1) {
 356  2
                                 result[i] = fromConstant(token);
 357  
                         } else {
 358  3
                                 result[i] = fromColumn(token);
 359  
                         }
 360  
                 }
 361  3
                 return result;
 362  
         }
 363  
 
 364  
         /**
 365  
          * How to treat a field source that returns an empty string.
 366  
          */
 367  6
         public static enum EmptyAction {
 368  
 
 369  
                 /**
 370  
                  * Ignore the field as if it was not in the field source list, to not
 371  
                  * touch the underlying field when updating a record.
 372  
                  */
 373  1
                 IGNORE,
 374  
 
 375  
                 /**
 376  
                  * Throw an exception.
 377  
                  */
 378  1
                 COMPLAIN,
 379  
 
 380  
                 /**
 381  
                  * Return an empty value, to clear the underlying field when updating a
 382  
                  * record.
 383  
                  */
 384  1
                 CLEAR,
 385  
 
 386  
                 /**
 387  
                  * Throw an exception if a new record should be created, ignore it
 388  
                  * otherwise.
 389  
                  * 
 390  
                  * @see #COMPLAIN
 391  
                  * @see #IGNORE
 392  
                  */
 393  1
                 COMPLAIN_IF_NEW;
 394  
         }
 395  
 }