Coverage Report - net.sf.jmatchparser.util.csv.CSVFactory
 
Classes in this File Line Coverage Branch Coverage Complexity
CSVFactory
79%
95/120
70%
43/61
12
CSVFactory$1
100%
1/1
N/A
12
CSVFactory$Format
100%
2/2
N/A
12
 
 1  
 /*
 2  
  * Copyright (c) 2006 - 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;
 34  
 
 35  
 import java.io.IOException;
 36  
 import java.io.InputStream;
 37  
 import java.io.OutputStream;
 38  
 import java.nio.charset.Charset;
 39  
 import java.util.Arrays;
 40  
 import java.util.HashSet;
 41  
 import java.util.Properties;
 42  
 import java.util.Set;
 43  
 
 44  
 import javax.xml.parsers.DocumentBuilderFactory;
 45  
 
 46  
 import org.w3c.dom.Document;
 47  
 
 48  
 /**
 49  
  * Utility class to create CSV readers and writers based on an input file and a
 50  
  * format string that describes the format of the CSV file.
 51  
  */
 52  
 public class CSVFactory {
 53  
 
 54  
         /**
 55  
          * Create an {@link AbstractCSVReader} from a format string.
 56  
          * 
 57  
          * <h2>{@link CSVReader}</h2>
 58  
          * <tt>separator=auto|comma|semicolon|tab|<i>char</i>[, flags=comments|quotedlines|multiline[+<i>...</i>]][, charset=<i>charset</i>]</tt>
 59  
          * 
 60  
          * <h2>{@link FixedWidthCSVReader}</h2>
 61  
          * 
 62  
          * <tt>fixed=<i>columnSpec1</i>[,<i>...</i>][, prefix=<i>prefix</i>][, separator=<i>separator</i>][, suffix=<i>suffix</i>][, flags=comments][, charset=<i>charset</i>]</tt>
 63  
          * 
 64  
          * <h2>{@link PropertiesCSVReader}</h2>
 65  
          * 
 66  
          * <tt>properties=<i>firstIndex</i>,<i>columnFormat1</i>[,<i>...</i>]</tt>
 67  
          * 
 68  
          * <h2>{@link XMLCSVReader}</h2>
 69  
          * 
 70  
          * <tt>xml=<i>rowTag</i>,<i>cellTag1</i>[,<i>...</i>]</tt>
 71  
          * 
 72  
          * 
 73  
          * <h2>{@link XPathCSVReader}</h2>
 74  
          * 
 75  
          * <tt>xpath=<i>rowExpression</i>,<i>columnExpression1</i>[,<i>...</i>]</tt>
 76  
          * 
 77  
          * @param in
 78  
          *            Input stream to read data from
 79  
          * @param formatString
 80  
          *            Format string
 81  
          * @return The CSV reader
 82  
          */
 83  
         public static AbstractCSVReader createReader(InputStream in, String formatString) throws IOException {
 84  7
                 return new CSVFactory(formatString, true).createReader(in);
 85  
         }
 86  
 
 87  
         /**
 88  
          * Create an {@link AbstractCSVWriter} from a format string.
 89  
          * 
 90  
          * <h2>{@link CSVWriter}</h2>
 91  
          * <tt>separator=comma|semicolon|tab|<i>char</i>[, charset=<i>charset</i>]</tt>
 92  
          * 
 93  
          * <h2>{@link FixedWidthCSVWriter}</h2>
 94  
          * 
 95  
          * <tt>fixed=<i>columnSpec1</i>[,<i>...</i>][, prefix=<i>prefix</i>][, separator=<i>separator</i>][, suffix=<i>suffix</i>][, flags=truncate][, charset=<i>charset</i>]</tt>
 96  
          * 
 97  
          * <h2>{@link PropertiesCSVWriter}</h2>
 98  
          * 
 99  
          * <tt>properties=<i>firstIndex</i>,<i>columnFormat1</i>[,<i>...</i>]</tt>
 100  
          * 
 101  
          * <h2>{@link XMLCSVWriter}</h2>
 102  
          * 
 103  
          * <tt>xml=<i>rowTag</i>,<i>cellTag1</i>[,<i>...</i>]</tt>
 104  
          * 
 105  
          * @param out
 106  
          *            Output stream to write to
 107  
          * @param formatString
 108  
          *            Format string
 109  
          * @return The CSV reader
 110  
          */
 111  
         public static AbstractCSVWriter createWriter(OutputStream out, String formatString) throws IOException {
 112  5
                 return new CSVFactory(formatString, false).createWriter(out);
 113  
         }
 114  
 
 115  
         private final String charset;
 116  
         private final Format format;
 117  
         private final HashSet<String> flags;
 118  
         private final char separatorChar;
 119  
         private final FixedWidthColumn[] columns;
 120  
         private final String rowString;
 121  
         private final String[] columnStrings;
 122  
 
 123  12
         private CSVFactory(String formatString, boolean forReader) {
 124  12
                 String charset = null;
 125  12
                 int pos = formatString.indexOf(", charset=");
 126  12
                 if (pos != -1) {
 127  5
                         charset = formatString.substring(pos + 10);
 128  5
                         formatString = formatString.substring(0, pos);
 129  
                 }
 130  12
                 flags = new HashSet<String>();
 131  12
                 pos = formatString.indexOf(", flags=");
 132  12
                 if (pos != -1) {
 133  1
                         flags.addAll(Arrays.asList(formatString.substring(pos + 8).split("\\+")));
 134  1
                         formatString = formatString.substring(0, pos);
 135  
                 }
 136  
                 final String[] supportedFlags;
 137  
                 final boolean charsetSupported;
 138  12
                 if (formatString.startsWith("separator=")) {
 139  3
                         String separatorName = formatString.substring(10);
 140  3
                         if (separatorName.equals("auto") && forReader) {
 141  1
                                 separatorChar = 0;
 142  2
                         } else if (separatorName.equals("comma")) {
 143  2
                                 separatorChar = ',';
 144  0
                         } else if (separatorName.equals("semicolon")) {
 145  0
                                 separatorChar = ';';
 146  0
                         } else if (separatorName.equals("tab")) {
 147  0
                                 separatorChar = '\t';
 148  0
                         } else if (separatorName.length() == 1) {
 149  0
                                 separatorChar = separatorName.charAt(0);
 150  
                         } else {
 151  0
                                 throw new IllegalArgumentException("Unsupported separator: " + separatorName);
 152  
                         }
 153  3
                         charsetSupported = true;
 154  3
                         rowString = null;
 155  3
                         columns = null;
 156  3
                         columnStrings = null;
 157  3
                         supportedFlags = forReader ? new String[] { "comments", "quotedlines", "multiline" } : new String[0];
 158  3
                         format = Format.CSV;
 159  3
                 } else if (formatString.startsWith("fixed=")) {
 160  2
                         String prefix = "", separator = "", suffix = "";
 161  2
                         pos = formatString.indexOf(", suffix=");
 162  2
                         if (pos != -1) {
 163  0
                                 suffix = formatString.substring(pos + 9);
 164  0
                                 formatString = formatString.substring(0, pos);
 165  
                         }
 166  2
                         pos = formatString.indexOf(", separator=");
 167  2
                         if (pos != -1) {
 168  0
                                 separator = formatString.substring(pos + 12);
 169  0
                                 formatString = formatString.substring(0, pos);
 170  
                         }
 171  2
                         pos = formatString.indexOf(", prefix=");
 172  2
                         if (pos != -1) {
 173  0
                                 prefix = formatString.substring(pos + 9);
 174  0
                                 formatString = formatString.substring(0, pos);
 175  
                         }
 176  2
                         columnStrings = new String[] { prefix, separator, suffix };
 177  2
                         String[] colSpecs = formatString.substring(6).split(",");
 178  2
                         columns = new FixedWidthColumn[colSpecs.length];
 179  6
                         for (int i = 0; i < colSpecs.length; i++) {
 180  4
                                 columns[i] = FixedWidthColumn.parse(colSpecs[i]);
 181  
                         }
 182  2
                         charsetSupported = true;
 183  2
                         rowString = null;
 184  2
                         separatorChar = 0;
 185  2
                         supportedFlags = forReader ? new String[] { "comments" } : new String[] { "truncate" };
 186  2
                         format = Format.FIXED;
 187  2
                 } else {
 188  
                         String[] parts;
 189  7
                         if (formatString.startsWith("properties=")) {
 190  2
                                 parts = formatString.substring(11).split(",", 2);
 191  2
                                 format = Format.PROPERTIES;
 192  5
                         } else if (formatString.startsWith("xml=")) {
 193  4
                                 parts = formatString.substring(4).split(",", 2);
 194  4
                                 format = Format.XML;
 195  1
                         } else if (formatString.startsWith("xpath=") && forReader) {
 196  1
                                 parts = formatString.substring(6).split(",", 2);
 197  1
                                 format = Format.XPATH;
 198  
                         } else {
 199  0
                                 throw new IllegalArgumentException("Unparsable format string: " + formatString);
 200  
                         }
 201  7
                         rowString = parts[0];
 202  7
                         columnStrings = parts[1].split(",");
 203  7
                         charsetSupported = false;
 204  7
                         supportedFlags = new String[0];
 205  7
                         columns = null;
 206  7
                         separatorChar = 0;
 207  
                 }
 208  
 
 209  12
                 if (charsetSupported) {
 210  5
                         if (charset == null)
 211  0
                                 charset = "UTF-BOM." + Charset.defaultCharset().name();
 212  7
                 } else if (charset != null) {
 213  0
                         throw new IllegalArgumentException("Charset not supported for this format");
 214  
                 }
 215  12
                 this.charset = charset;
 216  12
                 Set<String> supportedFlagsSet = new HashSet<String>();
 217  12
                 supportedFlagsSet.addAll(Arrays.asList(supportedFlags));
 218  12
                 for (String flag : flags) {
 219  2
                         if (!supportedFlagsSet.contains(flag))
 220  0
                                 throw new IllegalArgumentException("Unsupported flag: " + flag);
 221  
                 }
 222  12
         }
 223  
 
 224  
         private AbstractCSVReader createReader(InputStream in) throws IOException {
 225  7
                 switch (format) {
 226  
                 case CSV: {
 227  2
                         CSVReader r = new CSVReader(in, charset);
 228  2
                         r.setSeparator(separatorChar);
 229  2
                         r.setStripComments(flags.contains("comments"));
 230  2
                         r.setSupportFullyQuotedLines(flags.contains("quotedlines"));
 231  2
                         r.setSupportMultiLineCells(flags.contains("multiline"));
 232  2
                         return r;
 233  
                 }
 234  
                 case FIXED: {
 235  1
                         final String prefix = columnStrings[0], separator = columnStrings[1], suffix = columnStrings[2];
 236  1
                         FixedWidthCSVReader r = new FixedWidthCSVReader(in, charset, prefix, separator, suffix, columns);
 237  1
                         r.setStripComments(flags.contains("comments"));
 238  1
                         return r;
 239  
                 }
 240  
                 case PROPERTIES: {
 241  1
                         Properties props = new Properties();
 242  1
                         props.load(in);
 243  1
                         in.close();
 244  1
                         return new PropertiesCSVReader(props, Integer.parseInt(rowString), columnStrings);
 245  
                 }
 246  
                 case XML: {
 247  
                         try {
 248  2
                                 Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in);
 249  2
                                 return new XMLCSVReader(doc, rowString, columnStrings);
 250  0
                         } catch (Exception ex) {
 251  0
                                 throw new IOException("Exception while parsing XML file", ex);
 252  
                         }
 253  
                 }
 254  
                 case XPATH: {
 255  
                         try {
 256  1
                                 Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in);
 257  1
                                 return new XPathCSVReader(doc, rowString, columnStrings);
 258  0
                         } catch (Exception ex) {
 259  0
                                 throw new IOException("Exception while parsing XML file", ex);
 260  
                         }
 261  
                 }
 262  
                 default:
 263  0
                         throw new IOException("Unsupported CSV format");
 264  
                 }
 265  
         }
 266  
 
 267  
         private AbstractCSVWriter createWriter(OutputStream out) throws IOException {
 268  5
                 switch (format) {
 269  
                 case CSV: {
 270  1
                         return new CSVWriter(out, charset, separatorChar);
 271  
                 }
 272  
                 case FIXED: {
 273  1
                         final String prefix = columnStrings[0], separator = columnStrings[1], suffix = columnStrings[2];
 274  1
                         FixedWidthCSVWriter w = new FixedWidthCSVWriter(out, charset, prefix, separator, suffix, columns);
 275  1
                         w.setTruncateValues(flags.contains("truncate"));
 276  1
                         return w;
 277  
                 }
 278  
                 case PROPERTIES: {
 279  1
                         return new PropertiesCSVWriter(out, Integer.parseInt(rowString), columnStrings);
 280  
                 }
 281  
                 case XML: {
 282  
                         try {
 283  2
                                 return new XMLCSVWriter(out, "csv", rowString, columnStrings);
 284  0
                         } catch (Exception ex) {
 285  0
                                 throw new IOException("Exception while creating XML file", ex);
 286  
                         }
 287  
                 }
 288  
                 default:
 289  0
                         throw new IOException("Unsupported CSV format");
 290  
                 }
 291  
         }
 292  
 
 293  7
         private enum Format {
 294  1
                 CSV, FIXED, PROPERTIES, XML, XPATH
 295  
         }
 296  
 }