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