    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}