001package org.cpsolver.ifs.util;
002
003import java.io.BufferedReader;
004import java.io.File;
005import java.io.FileReader;
006import java.io.FileWriter;
007import java.io.IOException;
008import java.io.PrintWriter;
009import java.io.Reader;
010import java.io.Serializable;
011import java.io.Writer;
012import java.util.ArrayList;
013import java.util.Calendar;
014import java.util.Collection;
015import java.util.Date;
016import java.util.HashMap;
017import java.util.Iterator;
018import java.util.List;
019import java.util.Locale;
020
021/**
022 * Support for CSV (comma separated) text files.
023 * 
024 * @author  Tomáš Müller
025 * @version IFS 1.3 (Iterative Forward Search)<br>
026 *          Copyright (C) 2006 - 2014 Tomáš Müller<br>
027 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
028 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
029 * <br>
030 *          This library is free software; you can redistribute it and/or modify
031 *          it under the terms of the GNU Lesser General Public License as
032 *          published by the Free Software Foundation; either version 3 of the
033 *          License, or (at your option) any later version. <br>
034 * <br>
035 *          This library is distributed in the hope that it will be useful, but
036 *          WITHOUT ANY WARRANTY; without even the implied warranty of
037 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
038 *          Lesser General Public License for more details. <br>
039 * <br>
040 *          You should have received a copy of the GNU Lesser General Public
041 *          License along with this library; if not see
042 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
043 */
044
045public class CSVFile implements Serializable {
046    private static final long serialVersionUID = 1L;
047    HashMap<String, Integer> iHeaderMap = null;
048    CSVLine iHeader = null;
049    List<CSVLine> iLines = null;
050    String iSeparator = ",";
051    String iQuotationMark = "\"";
052
053    public CSVFile() {
054    }
055
056    public CSVFile(File file) throws IOException {
057        load(file);
058    }
059
060    public CSVFile(File file, String separator) throws IOException {
061        setSeparator(separator);
062        load(file);
063    }
064
065    public CSVFile(File file, String separator, String quotationMark) throws IOException {
066        setSeparator(separator);
067        setQuotationMark(quotationMark);
068        load(file);
069    }
070    
071    public CSVFile(Reader file) throws IOException {
072        load(file);
073    }
074
075    public CSVFile(Reader file, String separator) throws IOException {
076        setSeparator(separator);
077        load(file);
078    }
079
080    public CSVFile(Reader file, String separator, String quotationMark) throws IOException {
081        setSeparator(separator);
082        setQuotationMark(quotationMark);
083        load(file);
084    }
085
086    public void setSeparator(String separator) {
087        iSeparator = separator;
088    }
089
090    public String getSeparator() {
091        return iSeparator;
092    }
093
094    public void setQuotationMark(String quotationMark) {
095        iQuotationMark = quotationMark;
096    }
097
098    public String getQuotationMark() {
099        return iQuotationMark;
100    }
101
102    public void load(File file) throws IOException {
103        load(new FileReader(file));
104    }
105    
106    public void load(Reader file) throws IOException {
107        LineReader reader = null;
108        try {
109            reader = new LineReader(file);
110            iHeader = new CSVLine(reader.readLine()); // read header
111            iHeaderMap = new HashMap<String, Integer>();
112            iLines = new ArrayList<CSVLine>();
113            int idx = 0;
114            for (Iterator<CSVField> i = iHeader.fields(); i.hasNext(); idx++) {
115                CSVField field = i.next();
116                iHeaderMap.put(field.toString(), idx);
117            }
118            String line = null;
119            while ((line = reader.readLine()) != null) {
120                if (line.trim().length() == 0)
121                    continue;
122                iLines.add(new CSVLine(line));
123            }
124        } finally {
125            if (reader != null)
126                reader.close();
127        }
128    }
129    
130    public void save(File file) throws IOException {
131        save(new FileWriter(file));
132    }
133
134    public void save(Writer file) throws IOException {
135        PrintWriter writer = null;
136        try {
137            writer = new PrintWriter(file);
138            if (iHeader != null)
139                writer.println(iHeader.toString());
140
141            if (iLines != null) {
142                for (CSVLine line : iLines) {
143                    writer.println(line.toString());
144                }
145            }
146
147            writer.flush();
148        } finally {
149            if (writer != null)
150                writer.close();
151        }
152    }
153
154    public CSVLine getHeader() {
155        return iHeader;
156    }
157
158    public void setHeader(CSVLine header) {
159        iHeader = header;
160    }
161
162    public List<CSVLine> getLines() {
163        return iLines;
164    }
165
166    public int size() {
167        return iLines.size();
168    }
169
170    public boolean isEmpty() {
171        return iLines.isEmpty();
172    }
173
174    public CSVLine getLine(int idx) {
175        return iLines.get(idx);
176    }
177
178    public Iterator<CSVLine> lines() {
179        return iLines.iterator();
180    }
181
182    public void addLine(CSVLine line) {
183        if (iLines == null)
184            iLines = new ArrayList<CSVLine>();
185        iLines.add(line);
186    }
187
188    public void addLine(String line) {
189        if (iLines == null)
190            iLines = new ArrayList<CSVLine>();
191        iLines.add(new CSVLine(line));
192    }
193
194    public List<CSVLine> filter(CSVFilter filter) {
195        List<CSVLine> ret = new ArrayList<CSVLine>();
196        for (CSVLine line : iLines) {
197            if (filter.match(line))
198                ret.add(line);
199        }
200        return ret;
201    }
202
203    public CSVLine addLine() {
204        CSVLine line = new CSVLine();
205        addLine(line);
206        return line;
207    }
208
209    public CSVLine addLine(CSVField fields[]) {
210        CSVLine line = new CSVLine(fields);
211        addLine(line);
212        return line;
213    }
214
215    public CSVLine addLine(Collection<CSVField> fields) {
216        CSVLine line = new CSVLine(fields);
217        addLine(line);
218        return line;
219    }
220
221    public CSVLine setHeader(CSVField fields[]) {
222        CSVLine header = new CSVLine(fields);
223        setHeader(header);
224        return header;
225    }
226
227    public CSVLine setHeader(Collection<CSVField> fields) {
228        CSVLine header = new CSVLine(fields);
229        setHeader(header);
230        return header;
231    }
232
233    /** Representation of a line of a CSV file */
234    public class CSVLine implements Serializable {
235        private static final long serialVersionUID = 1L;
236        List<CSVField> iFields = new ArrayList<CSVField>(iHeader == null ? 10 : iHeader.size());
237
238        public CSVLine(String line) {
239            int idx = 0;
240            int newIdx = 0;
241            int fromIdx = 0;
242            while ((newIdx = line.indexOf(iSeparator, fromIdx)) >= 0) {
243                String field = line.substring(idx, newIdx);
244                if (iQuotationMark != null && field.startsWith(iQuotationMark) && (!field.endsWith(iQuotationMark) || field.length() == 1)) {
245                    fromIdx = newIdx + iSeparator.length();
246                    continue;
247                }
248                iFields.add(new CSVField(field, iQuotationMark));
249                idx = newIdx + iSeparator.length();
250                fromIdx = idx;
251            }
252            iFields.add(new CSVField(line.substring(idx), iQuotationMark));
253        }
254
255        public CSVLine() {
256        }
257
258        public CSVLine(CSVField fields[]) {
259            for (int i = 0; i < fields.length; i++)
260                iFields.add(fields[i]);
261        }
262
263        public CSVLine(Collection<CSVField> fields) {
264            iFields.addAll(fields);
265        }
266
267        public List<CSVField> getFields() {
268            return iFields;
269        }
270
271        public int size() {
272            return iFields.size();
273        }
274
275        public boolean isEmpty() {
276            return iFields.isEmpty();
277        }
278
279        public CSVField getField(int idx) {
280            try {
281                return iFields.get(idx);
282            } catch (ArrayIndexOutOfBoundsException e) {
283                return null;
284            }
285        }
286
287        public void setField(int idx, CSVField field) {
288            iFields.set(idx, field);
289        }
290
291        public Iterator<CSVField> fields() {
292            return iFields.iterator();
293        }
294
295        public CSVField getField(String name) {
296            Integer idx = iHeaderMap.get(name);
297            return (idx == null ? null : getField(idx.intValue()));
298        }
299
300        public void setField(String name, CSVField field) {
301            Integer idx = iHeaderMap.get(name);
302            if (idx != null)
303                setField(idx.intValue(), field);
304        }
305
306        @Override
307        public String toString() {
308            StringBuffer sb = new StringBuffer();
309            for (Iterator<CSVField> i = iFields.iterator(); i.hasNext();) {
310                CSVField field = i.next();
311                if (field != null)
312                    sb.append((iQuotationMark == null ? "" : iQuotationMark) + field.toString()
313                            + (iQuotationMark == null ? "" : iQuotationMark));
314                if (i.hasNext())
315                    sb.append(iSeparator);
316            }
317            return sb.toString();
318        }
319
320        public void debug(int offset, PrintWriter out) {
321            int idx = 0;
322            for (Iterator<CSVField> i = iFields.iterator(); i.hasNext();) {
323                CSVField field = i.next();
324                if (field == null || field.toString().length() == 0)
325                    continue;
326                for (int j = 0; j < offset; j++)
327                    out.print(" ");
328                out.println(iHeader.getField(idx) + "=" + (iQuotationMark == null ? "" : iQuotationMark) + field
329                        + (iQuotationMark == null ? "" : iQuotationMark));
330            }
331        }
332    }
333
334    /** Representation of a field of a CSV file */
335    public static class CSVField implements Serializable {
336        private static final long serialVersionUID = 1L;
337        String iField = null;
338
339        public CSVField(String field, String quotationMark) {
340            field = field.trim();
341            if (quotationMark != null && field.startsWith(quotationMark) && field.endsWith(quotationMark))
342                field = field.substring(1, field.length() - 1);
343            iField = field;
344        }
345
346        public CSVField(Object field) {
347            set(field == null ? "" : field.toString());
348        }
349
350        public CSVField(int field) {
351            set(field);
352        }
353
354        public CSVField(boolean field) {
355            set(field);
356        }
357
358        public CSVField(double field) {
359            set(field);
360        }
361
362        public CSVField(long field) {
363            set(field);
364        }
365
366        public CSVField(float field) {
367            set(field);
368        }
369
370        public void set(Object value) {
371            iField = (value == null ? "" : value.toString());
372        }
373
374        public void set(int value) {
375            iField = String.valueOf(value);
376        }
377
378        public void set(boolean value) {
379            iField = (value ? "1" : "0");
380        }
381
382        public void set(double value) {
383            iField = String.valueOf(value);
384        }
385
386        public void set(long value) {
387            iField = String.valueOf(value);
388        }
389
390        public void set(float value) {
391            iField = String.valueOf(value);
392        }
393
394        @Override
395        public String toString() {
396            return (iField == null ? "" : iField);
397        }
398
399        public boolean isEmpty() {
400            return (iField.length() == 0);
401        }
402
403        public int toInt() {
404            return toInt(0);
405        }
406
407        public int toInt(int defaultValue) {
408            try {
409                return Integer.parseInt(iField);
410            } catch (NumberFormatException e) {
411                return defaultValue;
412            }
413        }
414
415        public long toLong() {
416            return toLong(0);
417        }
418
419        public long toLong(long defaultValue) {
420            try {
421                return Long.parseLong(iField);
422            } catch (NumberFormatException e) {
423                return defaultValue;
424            }
425        }
426
427        public double toDouble() {
428            return toDouble(0);
429        }
430
431        public double toDouble(double defaultValue) {
432            try {
433                return Double.parseDouble(iField);
434            } catch (NumberFormatException e) {
435                return defaultValue;
436            }
437        }
438
439        public Date toDate() {
440            int month = Integer.parseInt(iField.substring(0, 2));
441            int day = Integer.parseInt(iField.substring(3, 5));
442            int year = Integer.parseInt(iField.substring(6, 8));
443            Calendar c = Calendar.getInstance(Locale.US);
444            c.set(year, month - 1, day, 0, 0, 0);
445            return c.getTime();
446        }
447
448        public boolean toBoolean() {
449            return "Y".equalsIgnoreCase(iField) || "on".equalsIgnoreCase(iField) || "true".equalsIgnoreCase(iField)
450                    || "1".equalsIgnoreCase(iField);
451        }
452    }
453
454    /** An interface for filtering lines of a CSV file */
455    public static interface CSVFilter {
456        public boolean match(CSVLine line);
457    }
458
459    public static CSVFilter eq(String name, String value) {
460        return (new CSVFilter() {
461            String n, v;
462
463            @Override
464            public boolean match(CSVLine line) {
465                return line.getField(n).equals(v);
466            }
467
468            private CSVFilter set(String n, String v) {
469                this.n = n;
470                this.v = v;
471                return this;
472            }
473        }).set(name, value);
474    }
475
476    public static CSVFilter and(CSVFilter first, CSVFilter second) {
477        return (new CSVFilter() {
478            CSVFilter a, b;
479
480            @Override
481            public boolean match(CSVLine line) {
482                return a.match(line) && b.match(line);
483            }
484
485            private CSVFilter set(CSVFilter a, CSVFilter b) {
486                this.a = a;
487                this.b = b;
488                return this;
489            }
490        }).set(first, second);
491    }
492
493    public static CSVFilter or(CSVFilter first, CSVFilter second) {
494        return (new CSVFilter() {
495            CSVFilter a, b;
496
497            @Override
498            public boolean match(CSVLine line) {
499                return a.match(line) || b.match(line);
500            }
501
502            private CSVFilter set(CSVFilter a, CSVFilter b) {
503                this.a = a;
504                this.b = b;
505                return this;
506            }
507        }).set(first, second);
508    }
509
510    public static CSVFilter not(CSVFilter filter) {
511        return (new CSVFilter() {
512            CSVFilter f;
513
514            @Override
515            public boolean match(CSVLine line) {
516                return !f.match(line);
517            }
518
519            private CSVFilter set(CSVFilter f) {
520                this.f = f;
521                return this;
522            }
523        }).set(filter);
524    }
525    
526    private class LineReader extends BufferedReader {
527        LineReader(Reader r) {
528            super(r);
529        }
530        
531        public boolean isOpenEnded(String line) {
532            if (iQuotationMark == null) return false;
533            int idx = 0;
534            int newIdx = 0;
535            int fromIdx = 0;
536            while ((newIdx = line.indexOf(iSeparator, fromIdx)) >= 0) {
537                String field = line.substring(idx, newIdx);
538                if (field.startsWith(iQuotationMark) && (!field.endsWith(iQuotationMark) || field.length() == 1)) {
539                    fromIdx = newIdx + iSeparator.length();
540                    continue;
541                }
542                idx = newIdx + iSeparator.length();
543                fromIdx = idx;
544            }
545            String field = line.substring(idx);
546            if (field.startsWith(iQuotationMark) && (!field.endsWith(iQuotationMark) || field.length() == 1)) return true;
547            return false;
548        }
549        
550        @Override
551        public String readLine() throws IOException {
552            String line = super.readLine();
553            while (line != null && isOpenEnded(line)) {
554                String next = super.readLine();
555                if (next == null) return line;
556                line += "\n" + next;
557            }
558            return line;
559        }
560    }
561}