1/*******************************************************************************
2 * Copyright (c) 2009, 2015 Mountainminds GmbH & Co. KG and Contributors
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 *    Brock Janiczak - initial API and implementation
10 *
11 *******************************************************************************/
12package org.jacoco.report.csv;
13
14import java.io.IOException;
15import java.io.Writer;
16
17/**
18 * Helper class for writing out CSV or tab delimited files.
19 * <p>
20 * <strong>Example Usage:</strong>
21 *
22 * <pre>
23 * delimitedWriter.writeFields(&quot;header1&quot;, &quot;header2&quot;, ...);
24 * for each line to be written {
25 *   delimitedWriter.writeField(value1);
26 *   delimitedWriter.writeField(value2);
27 *   delimitedWriter.nextLine();
28 * }
29 * delimitedWriter.close();
30 * </pre>
31 *
32 * </p>
33 */
34class DelimitedWriter {
35	private static final String QUOTE = "\"";
36	private static final String ESCAPED_QUOTE = "\"\"";
37
38	private static final char DEFAULT_DELIMITER = ',';
39	private static final String NEW_LINE = System.getProperty("line.separator");
40	private final char delimiter;
41	private final Writer delegate;
42	private int fieldPosition = 0;
43
44	/**
45	 * Creates a new Delimited writer using the default delimiter
46	 *
47	 * @param delegate
48	 *            Writer to delegate all writes to
49	 */
50	public DelimitedWriter(final Writer delegate) {
51		this(delegate, DEFAULT_DELIMITER);
52	}
53
54	/**
55	 * Creates a new Delimited writer using the default delimiter
56	 *
57	 * @param delegate
58	 *            Writer to delegate all writes to
59	 * @param delimiter
60	 *            delimiter to use (usually a comma, tab or space)
61	 */
62	public DelimitedWriter(final Writer delegate, final char delimiter) {
63		this.delegate = delegate;
64		this.delimiter = delimiter;
65	}
66
67	/**
68	 * Write multiple fields at once. Values will be auto escaped and quoted as
69	 * needed. Each value will be separated using the current delimiter
70	 *
71	 * @param fields
72	 *            Values to write
73	 * @throws IOException
74	 *             Error writing to the underlying writer object
75	 */
76	public void write(final String... fields) throws IOException {
77		for (final String field : fields) {
78			write(field);
79		}
80	}
81
82	/**
83	 * Write a single value. Values will be auto escaped and quoted as needed.
84	 * If this is not the first field of the current line the value will be
85	 * prepended with the current delimiter
86	 *
87	 * @param field
88	 *            Value to write
89	 * @throws IOException
90	 *             Error writing to the underlying writer object
91	 */
92	public void write(final String field) throws IOException {
93		if (fieldPosition != 0) {
94			delegate.write(delimiter);
95		}
96		delegate.write(escape(field));
97		fieldPosition++;
98	}
99
100	/**
101	 * Write a single integer value.
102	 *
103	 * @param value
104	 *            Value to write
105	 * @throws IOException
106	 *             Error writing to the underlying writer object
107	 */
108	public void write(final int value) throws IOException {
109		write(Integer.toString(value));
110	}
111
112	/**
113	 * Write muliple integer values
114	 *
115	 * @param values
116	 *            values to write
117	 * @throws IOException
118	 *             Error writing to the underlying writer object
119	 */
120	public void write(final int... values) throws IOException {
121		for (final int value : values) {
122			write(Integer.toString(value));
123		}
124	}
125
126	/**
127	 * Output a new line and advance the writer to the next line. The line
128	 * delimiter is the default for the platform.
129	 *
130	 * @throws IOException
131	 *             Error writing to the underlying writer object
132	 */
133	public void nextLine() throws IOException {
134		delegate.write(NEW_LINE);
135		fieldPosition = 0;
136	}
137
138	/**
139	 * Close the underlying writer object. Once closed all write operations will
140	 * fail
141	 *
142	 * @throws IOException
143	 *             Error closing the underlying writer object
144	 */
145	public void close() throws IOException {
146		delegate.close();
147	}
148
149	/**
150	 * Escapes any occurrences of the quote character in value by replacing it
151	 * with a double quote. Also Quotes the value if a quote or delimiter value
152	 * is found.
153	 *
154	 * @param value
155	 *            String that needs escaping
156	 * @return New string with all values escaped
157	 */
158	private String escape(final String value) {
159		String escapedValue = value;
160
161		// Escape and quote if the source value contains the delimiter
162		// or the quote character
163		if (value.indexOf(QUOTE) != -1 || value.indexOf(delimiter) != -1) {
164			escapedValue = value.replace(QUOTE, ESCAPED_QUOTE);
165			escapedValue = QUOTE + escapedValue + QUOTE;
166		}
167
168		return escapedValue;
169	}
170}
171