001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2020, by Object Refinery Limited and Contributors.
006 *
007 * Project Info:  http://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
022 * USA.
023 *
024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * --------
028 * CSV.java
029 * --------
030 * (C) Copyright 2003-2008, by Object Refinery Limited.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   -;
034 *
035 * Changes
036 * -------
037 * 24-Nov-2003 : Version 1 (DG);
038 *
039 */
040
041package org.jfree.data.io;
042
043import java.io.BufferedReader;
044import java.io.IOException;
045import java.io.Reader;
046import java.util.List;
047
048import org.jfree.data.category.CategoryDataset;
049import org.jfree.data.category.DefaultCategoryDataset;
050
051/**
052 * A utility class for reading {@link CategoryDataset} data from a CSV file.
053 * This initial version is very basic, and won't handle errors in the data
054 * file very gracefully.
055 */
056public class CSV {
057
058    /** The field delimiter. */
059    private char fieldDelimiter;
060
061    /** The text delimiter. */
062    private char textDelimiter;
063
064    /**
065     * Creates a new CSV reader where the field delimiter is a comma, and the
066     * text delimiter is a double-quote.
067     */
068    public CSV() {
069        this(',', '"');
070    }
071
072    /**
073     * Creates a new reader with the specified field and text delimiters.
074     *
075     * @param fieldDelimiter  the field delimiter (usually a comma, semi-colon,
076     *                        colon, tab or space).
077     * @param textDelimiter  the text delimiter (usually a single or double
078     *                       quote).
079     */
080    public CSV(char fieldDelimiter, char textDelimiter) {
081        this.fieldDelimiter = fieldDelimiter;
082        this.textDelimiter = textDelimiter;
083    }
084
085    /**
086     * Reads a {@link CategoryDataset} from a CSV file or input source.
087     *
088     * @param in  the input source.
089     *
090     * @return A category dataset.
091     *
092     * @throws IOException if there is an I/O problem.
093     */
094    public CategoryDataset readCategoryDataset(Reader in) throws IOException {
095
096        DefaultCategoryDataset dataset = new DefaultCategoryDataset();
097        BufferedReader reader = new BufferedReader(in);
098        List columnKeys = null;
099        int lineIndex = 0;
100        String line = reader.readLine();
101        while (line != null) {
102            if (lineIndex == 0) {  // first line contains column keys
103                columnKeys = extractColumnKeys(line);
104            }
105            else {  // remaining lines contain a row key and data values
106                extractRowKeyAndData(line, dataset, columnKeys);
107            }
108            line = reader.readLine();
109            lineIndex++;
110        }
111        return dataset;
112
113    }
114
115    /**
116     * Extracts the column keys from a string.
117     *
118     * @param line  a line from the input file.
119     *
120     * @return A list of column keys.
121     */
122    private List extractColumnKeys(String line) {
123        List keys = new java.util.ArrayList();
124        int fieldIndex = 0;
125        int start = 0;
126        for (int i = 0; i < line.length(); i++) {
127            if (line.charAt(i) == this.fieldDelimiter) {
128                if (fieldIndex > 0) {  // first field is ignored, since
129                                       // column 0 is for row keys
130                    String key = line.substring(start, i);
131                    keys.add(removeStringDelimiters(key));
132                }
133                start = i + 1;
134                fieldIndex++;
135            }
136        }
137        String key = line.substring(start, line.length());
138        keys.add(removeStringDelimiters(key));
139        return keys;
140    }
141
142    /**
143     * Extracts the row key and data for a single line from the input source.
144     *
145     * @param line  the line from the input source.
146     * @param dataset  the dataset to be populated.
147     * @param columnKeys  the column keys.
148     */
149    private void extractRowKeyAndData(String line,
150                                      DefaultCategoryDataset dataset,
151                                      List columnKeys) {
152        Comparable rowKey = null;
153        int fieldIndex = 0;
154        int start = 0;
155        for (int i = 0; i < line.length(); i++) {
156            if (line.charAt(i) == this.fieldDelimiter) {
157                if (fieldIndex == 0) {  // first field contains the row key
158                    String key = line.substring(start, i);
159                    rowKey = removeStringDelimiters(key);
160                }
161                else {  // remaining fields contain values
162                    Double value = Double.valueOf(
163                        removeStringDelimiters(line.substring(start, i))
164                    );
165                    dataset.addValue(
166                        value, rowKey,
167                        (Comparable) columnKeys.get(fieldIndex - 1)
168                    );
169                }
170                start = i + 1;
171                fieldIndex++;
172            }
173        }
174        Double value = Double.valueOf(
175            removeStringDelimiters(line.substring(start, line.length()))
176        );
177        dataset.addValue(
178            value, rowKey, (Comparable) columnKeys.get(fieldIndex - 1)
179        );
180    }
181
182    /**
183     * Removes the string delimiters from a key (as well as any white space
184     * outside the delimiters).
185     *
186     * @param key  the key (including delimiters).
187     *
188     * @return The key without delimiters.
189     */
190    private String removeStringDelimiters(String key) {
191        String k = key.trim();
192        if (k.charAt(0) == this.textDelimiter) {
193            k = k.substring(1);
194        }
195        if (k.charAt(k.length() - 1) == this.textDelimiter) {
196            k = k.substring(0, k.length() - 1);
197        }
198        return k;
199    }
200
201}