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