    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}