View Javadoc
1   /*
2    * Copyright (c) 2001-2017, Zoltan Farkas All Rights Reserved.
3    *
4    * This library is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Lesser General Public
6    * License as published by the Free Software Foundation; either
7    * version 2.1 of the License, or (at your option) any later version.
8    *
9    * This library is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU General Public License for more details.
13   *
14   * You should have received a copy of the GNU Lesser General Public
15   * License along with this program; if not, write to the Free Software
16   * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17   *
18   * Additionally licensed with:
19   *
20   * Licensed under the Apache License, Version 2.0 (the "License");
21   * you may not use this file except in compliance with the License.
22   * You may obtain a copy of the License at
23   *
24   *      http://www.apache.org/licenses/LICENSE-2.0
25   *
26   * Unless required by applicable law or agreed to in writing, software
27   * distributed under the License is distributed on an "AS IS" BASIS,
28   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29   * See the License for the specific language governing permissions and
30   * limitations under the License.
31   */
32  package org.spf4j.io;
33  
34  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
35  import java.io.File;
36  import java.io.IOException;
37  import java.io.Reader;
38  import java.io.StringReader;
39  import java.io.UncheckedIOException;
40  import java.nio.charset.Charset;
41  import java.util.List;
42  import java.util.Map;
43  import javax.annotation.CheckReturnValue;
44  import javax.annotation.ParametersAreNonnullByDefault;
45  import org.spf4j.base.CharSequences;
46  import org.spf4j.io.csv.CharSeparatedValues;
47  import org.spf4j.io.csv.CsvParseException;
48  import org.spf4j.io.csv.CsvReader;
49  
50  /**
51   * Supports CSV format as described at: https://en.wikipedia.org/wiki/Comma-separated_values. either of \n \r or \r\n
52   * are valid end of line delimiters
53   *
54   * why another implementation? because I need one that is as fast as possible, and as flexible as possible.
55   *
56   * @author zoly
57   */
58  @ParametersAreNonnullByDefault
59  @SuppressFBWarnings("NP_LOAD_OF_KNOWN_NULL_VALUE") // FB gets it wrong here
60  public final class Csv {
61  
62    public static final CharSeparatedValues CSV = new CharSeparatedValues(',');
63  
64    private static final char[] TO_ESCAPE = new char[]{',', '\n', '\r', '"'};
65  
66    private Csv() {
67    }
68  
69    public interface CsvHandler<T> extends org.spf4j.io.csv.CsvHandler<T> {
70    }
71  
72    public interface CsvRowHandler<T> extends org.spf4j.io.csv.CsvRowHandler<T> {
73    }
74  
75    public interface CsvMapHandler<T> extends org.spf4j.io.csv.CsvMapHandler<T> {
76    }
77  
78    public static String[] readSystemProperty(final String propertyName, final String... defaults) {
79      String propertyVal = System.getProperty(propertyName);
80      if (propertyVal == null) {
81        return defaults;
82      } else {
83        List<String> row;
84        try {
85          row = readRow(propertyVal);
86        } catch (CsvParseException ex) {
87           throw new RuntimeException("Unable to parser property " + propertyName + " = " + propertyVal, ex);
88        }
89        return row.toArray(new String[row.size()]);
90      }
91    }
92  
93    public static void writeCsvRow(final Appendable writer, final Object... elems) throws IOException {
94      CSV.writeCsvRow(writer, elems);
95    }
96  
97    public static void writeCsvRow2(final Appendable writer, final Object obj, final Object... elems)
98            throws IOException {
99      CSV.writeCsvRow2(writer, obj, elems);
100   }
101 
102   public static void writeCsvRow(final Appendable writer, final long... elems) throws IOException {
103     CSV.writeCsvRow(writer, elems);
104   }
105 
106   public static void writeCsvRowNoEOL(final long[] elems, final Appendable writer) throws IOException {
107     CSV.writeCsvRowNoEOL(elems, writer);
108   }
109 
110   public static void writeCsvRow(final Appendable writer, final Iterable<?> elems) throws IOException {
111     CSV.writeCsvRow(writer, elems);
112   }
113 
114   public static void writeCsvRowNoEOL(final Iterable<?> elems, final Appendable writer) throws IOException {
115     CSV.writeCsvRowNoEOL(elems, writer);
116   }
117 
118   public static <T> T read(final File file, final Charset charset,
119           final CsvMapHandler<T> handler) throws IOException, CsvParseException {
120     return CSV.read(file, charset, handler);
121   }
122 
123   public static <T> T read(final File file, final Charset charset,
124           final CsvHandler<T> handler) throws IOException, CsvParseException {
125     return CSV.read(file, charset, handler);
126   }
127 
128   public static List<Map<String, String>> read(final Reader preader) throws IOException, CsvParseException {
129     return CSV.read(preader);
130   }
131 
132   public static <T> T read(final Reader preader,
133           final CsvMapHandler<T> handler) throws IOException, CsvParseException {
134     return CSV.read(preader, handler);
135   }
136 
137   public static List<String> readRow(final String row) throws CsvParseException {
138     try {
139       return readRow(new StringReader(row));
140     } catch (IOException ex) {
141       throw new UncheckedIOException(ex);
142     }
143   }
144 
145   public static List<String> readRow(final Reader reader) throws IOException, CsvParseException {
146     return CSV.readRow(reader);
147   }
148 
149   public static <T> T readRow(final Reader reader, final CsvRowHandler<T> handler)
150           throws IOException, CsvParseException {
151     return CSV.readRow(reader, handler);
152   }
153 
154   public static <T> T read(final Reader preader,
155           final CsvHandler<T> handler) throws IOException, CsvParseException {
156     return CSV.read(preader, handler);
157   }
158 
159   /**
160    * reads CSV format until EOF of reader.
161    *
162    * @param <T>
163    * @param preader
164    * @param handler
165    * @return
166    * @throws IOException
167    */
168   public static <T> T readNoBom(final PushbackReader reader, final CsvHandler<T> handler)
169           throws IOException, CsvParseException {
170     return CSV.readNoBom(reader, handler);
171   }
172 
173   /**
174    * read a CSV stream, as a Iterable over rows.
175    * the List<String> instance is reused during iteration, you will need to copy content into
176    * own data structure.
177    * @param preader
178    * @return
179    */
180   public static Iterable<Iterable<String>> asIterable(final Reader preader) {
181     return CSV.asIterable(preader);
182   }
183 
184   public static CsvReader reader(final Reader preader) throws IOException {
185     return CSV.reader(preader);
186   }
187 
188   public static CsvReader readerNoBOM(final PushbackReader reader) {
189     return CSV.readerNoBOM(reader);
190   }
191 
192   public static void writeCsvElement(final CharSequence elem, final Appendable writer) throws IOException {
193     if (CharSequences.containsAnyChar(elem, TO_ESCAPE)) {
194       writeQuotedCsvElement(elem, writer);
195     } else {
196       writer.append(elem);
197     }
198   }
199 
200   public static void writeQuotedCsvElement(final CharSequence elem, final Appendable writer) throws IOException {
201     int length = elem.length();
202     writer.append('"');
203     for (int i = 0; i < length; i++) {
204       char c = elem.charAt(i);
205       if (c == '"') {
206         writer.append("\"\"");
207       } else {
208         writer.append(c);
209       }
210     }
211     writer.append('"');
212   }
213 
214   public static CharSequence toCsvElement(final CharSequence elem) {
215     if (CharSequences.containsAnyChar(elem, TO_ESCAPE)) {
216       StringBuilder sw = new StringBuilder(elem.length() + 2);
217       try {
218         writeQuotedCsvElement(elem, sw);
219       } catch (IOException ex) {
220         throw new UncheckedIOException(ex);
221       }
222       return sw.toString();
223     } else {
224       return elem;
225     }
226   }
227 
228   /**
229    * returns next character.
230    *
231    * @param reader
232    * @param addElemTo
233    * @return - next character or -1 if eof has been reached.
234    * @throws IOException
235    */
236   @CheckReturnValue
237   public static int readCsvElement(final Reader reader, final StringBuilder addElemTo) throws IOException {
238     int c = reader.read();
239     if (c < 0) {
240       return c;
241     }
242     if (c == '"') {
243       c = reader.read();
244       while (c >= 0) {
245         if (c == '"') {
246           int c2 = reader.read();
247           if (c2 >= 0) {
248             if (c2 == '"') {
249               addElemTo.append((char) c);
250             } else {
251               return c2;
252             }
253           } else {
254             return c2;
255           }
256         } else {
257           addElemTo.append((char) c);
258         }
259         c = reader.read();
260       }
261     } else {
262       while (c != ',' && c != '\n' && c != '\r' && c >= 0) {
263         addElemTo.append((char) c);
264         c = reader.read();
265       }
266     }
267     return c;
268   }
269 
270 }