    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}