/******************************************************************************* * Copyright (c) 2009 The Eclipse Foundation. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * The Eclipse Foundation - initial API and implementation *******************************************************************************/ package org.eclipse.epp.usagedata.internal.recording; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.io.Writer; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.epp.usagedata.internal.gathering.events.UsageDataEvent; public class UsageDataRecorderUtils { /** * Write a header onto the {@link Writer}. * * @param writer * a {@link Writer}. Must not be <code>null</code>. * @throws IOException * if writing to the {@link Writer} fails. */ public static void writeHeader(Writer writer) throws IOException { writer.write("what"); //$NON-NLS-1$ writer.write(","); //$NON-NLS-1$ writer.write("kind"); //$NON-NLS-1$ writer.write(","); //$NON-NLS-1$ writer.write("bundleId"); //$NON-NLS-1$ writer.write(","); //$NON-NLS-1$ writer.write("bundleVersion"); //$NON-NLS-1$ writer.write(","); //$NON-NLS-1$ writer.write("description"); //$NON-NLS-1$ writer.write(","); //$NON-NLS-1$ writer.write("time"); //$NON-NLS-1$ writer.write("\n"); //$NON-NLS-1$ } /** * Dump the event on the writer. This method assumes exclusive access to the * writer. * * @param writer * target for the event information. Must not be * <code>null</code>. * @param event * event to write. Must not be <code>null</code>. * * @throws IOException * if writing to the {@link Writer} fails. */ public static void writeEvent(Writer writer, UsageDataEvent event) throws IOException { writer.write(event.what); writer.write(","); //$NON-NLS-1$ writer.write(event.kind); writer.write(","); //$NON-NLS-1$ writer.write(event.bundleId != null ? event.bundleId : ""); //$NON-NLS-1$ writer.write(","); //$NON-NLS-1$ writer.write(event.bundleVersion != null ? event.bundleVersion : ""); //$NON-NLS-1$ writer.write(","); //$NON-NLS-1$ writer.write(event.description != null ? encode(event.description) : ""); //$NON-NLS-1$ writer.write(","); //$NON-NLS-1$ writer.write(String.valueOf(event.when)); writer.write("\n"); //$NON-NLS-1$ } /** * This method encodes the description so that it can be successfully parsed * as a single entry by a CSV parser. This takes care of most of the * escape characters to ensure that the output gets dumped onto a single * line. We probably take care of more escape characters than we really * need to, but that's better than the opposite... * <p> * The escaped characters should only be an issue in the rare case when * a status message contains them. Most of our monitors do not generate * text that contains them. * * @param description * a {@link String}. Must not be <code>null</code>. * * @return a {@link String}. */ public static String encode(String description) { StringBuilder builder = new StringBuilder(); builder.append('"'); for(int index=0;index<description.length();index++) { char next = description.charAt(index); switch (next) { case '"' : builder.append('"'); builder.append(next); break; case '\\' : builder.append("\\\\"); //$NON-NLS-1$ break; case '\n' : builder.append("\\n"); //$NON-NLS-1$ break; case '\r' : builder.append("\\r"); //$NON-NLS-1$ break; case '\b' : builder.append("\\b"); //$NON-NLS-1$ break; case '\t' : builder.append("\\t"); //$NON-NLS-1$ break; case '\f' : builder.append("\\f"); //$NON-NLS-1$ break; default : builder.append(next); } } builder.append('"'); return builder.toString(); } /** * Split the String parameter into substrings. The parameter is assumed to * be in CSV format. Comma separators are assumed. An entry that starts and * ends with double-quotes may contain commas or escaped double-quotes. * Double-quotes are escaped by putting two one-after-the-other. This method * assumes that there are no extraneous white spaces (i.e. leading or * trailing) in the input. * <p> * Note that we don't worry about trying to re-translate escaped characters * back into their unescaped form. We assume that this method is used exclusively * for displaying events in a preview pane, or for applying filters; we don't * need to translate the escaped characters in either of these cases. * <p> * The value: "first,\"\"\"second\"\", third\",fourth" will be parsed into * three strings: "first", "\"second\", third", and "fourth". * <p> * Note that callers can safely assume that all entries in the resulting * array will be non-<code>null</code>. * * @param line * a {@link String}. Must not be <code>null</code>. * * @return an array of {@link String}s. */ public static String[] splitLine(String line) { List<String> strings = new java.util.ArrayList<String>(); try { splitLine(line, strings); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return (String[]) strings.toArray(new String[strings.size()]); } private static void splitLine(String line, List<String> strings) throws IOException { /* * There is a potential issue with this implementation in the case * where a quoted-wrapped field starts with an escaped quote. i.e. * the string \"\"\"value\"\"\" will be read as escaped-quote, followed * by quote rather than as quote followed by escaped-quote as is * intended. The net result is the same (evidenced in the test cases). */ Reader reader = new StringReader(line); int next = 0; StringBuilder builder = new StringBuilder(); boolean inQuote = false; while ((next = reader.read()) != -1) { if (next == '"') { reader.mark(1); if (reader.read() == '"') { builder.append('"'); } else { reader.reset(); inQuote = !inQuote; } } else if (next == ',') { if (inQuote) { builder.append(','); } else { strings.add(builder.toString()); builder = new StringBuilder(); } } else { builder.append((char)next); } } strings.add(builder.toString()); } }