1/*
2 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.tools.jdeprscan;
27
28import java.io.PrintStream;
29import java.util.ArrayList;
30import java.util.Arrays;
31import java.util.List;
32import java.util.stream.Collectors;
33
34/**
35 * Utility class for manipulating comma-separated-value (CSV) data.
36 */
37public class CSV {
38    static String quote(String input) {
39        String result;
40        boolean needQuote = input.contains(",");
41
42        if (input.contains("\"")) {
43            needQuote = true;
44            result = input.replace("\"", "\"\"");
45        } else {
46            result = input;
47        }
48
49        if (needQuote) {
50            return "\"" + result + "\"";
51        } else {
52            return result;
53        }
54    }
55
56    /**
57     * Writes the objects' string representations to the output as a line of CSV.
58     * The objects are converted to String, quoted if necessary, joined with commas,
59     * and are written to the output followed by the line separator string.
60     *
61     * @param out the output destination
62     * @param objs the objects to write
63     */
64    public static void write(PrintStream out, Object... objs) {
65        out.println(Arrays.stream(objs)
66                          .map(Object::toString)
67                          .map(CSV::quote)
68                          .collect(Collectors.joining(",")));
69    }
70
71    /**
72     * The CSV parser state.
73     */
74    enum State {
75        START_FIELD, // the start of a field
76        IN_FIELD,    // within an unquoted field
77        IN_QFIELD,   // within a quoted field
78        END_QFIELD   // after the end of a quoted field
79    }
80
81    /**
82     * Splits an input line into a list of strings, handling quoting.
83     *
84     * @param input the input line
85     * @return the resulting list of strings
86     */
87    public static List<String> split(String input) {
88        List<String> result = new ArrayList<>();
89        StringBuilder cur = new StringBuilder();
90        State state = State.START_FIELD;
91
92        for (int i = 0; i < input.length(); i++) {
93            char ch = input.charAt(i);
94            switch (ch) {
95                case ',':
96                    switch (state) {
97                        case IN_QFIELD:
98                            cur.append(',');
99                            break;
100                        default:
101                            result.add(cur.toString());
102                            cur.setLength(0);
103                            state = State.START_FIELD;
104                            break;
105                    }
106                    break;
107                case '"':
108                    switch (state) {
109                        case START_FIELD:
110                            state = State.IN_QFIELD;
111                            break;
112                        case IN_QFIELD:
113                            state = State.END_QFIELD;
114                            break;
115                        case IN_FIELD:
116                            throw new CSVParseException("unexpected quote", input, i);
117                        case END_QFIELD:
118                            cur.append('"');
119                            state = State.IN_QFIELD;
120                            break;
121                    }
122                    break;
123                default:
124                    switch (state) {
125                        case START_FIELD:
126                            state = State.IN_FIELD;
127                            break;
128                        case IN_FIELD:
129                        case IN_QFIELD:
130                            break;
131                        case END_QFIELD:
132                            throw new CSVParseException("extra character after quoted string",
133                                                        input, i);
134                    }
135                    cur.append(ch);
136                    break;
137            }
138        }
139
140        if (state == State.IN_QFIELD) {
141            throw new CSVParseException("unclosed quote", input, input.length());
142        }
143
144        result.add(cur.toString());
145        return result;
146    }
147}
148