CSVReader extends AbstractDataReader { public DataSource read(InputStream input, Class<? extends Comparable<?>>... types) throws IOException { Scanner scanner = new Scanner(input).useDelimiter("\\Z"); String content = scanner.next(); Character separator = getSetting(SEPARATOR_CHAR); CSVTokenizer tokenizer = new CSVTokenizer(separator); List<Token> tokens = tokenizer.tokenize(content); Token lastToken = tokens.get(tokens.size() - 1); if (lastToken.getType() != CSVTokenType.ROW_SEPARATOR) { Token eof = new Token(lastToken.getEnd(), lastToken.getEnd(), CSVTokenType.ROW_SEPARATOR, ""); tokens.add(eof); } Map<Class<? extends Comparable<?>>, Method> parseMethods = new HashMap<>(); for (Class<? extends Comparable<?>> type : types) { if (parseMethods.containsKey(type)) { continue; } Method parseMethod = getParseMethod(type); if (parseMethod != null) { parseMethods.put(type, parseMethod); } } DataTable data = new DataTable(types); List<Comparable<?>> row = new LinkedList<>(); int rowIndex = 0; int colIndex = 0; StringBuilder cellContent = new StringBuilder(); for (Token token : tokens) { if (token.getType() == CSVTokenType.TEXT || token.getType() == CSVTokenType.EMPTY_SPACE) { cellContent.append(token.getContent()); } else if (token.getType() == CSVTokenType.COLUMN_SEPARATOR || token.getType() == CSVTokenType.ROW_SEPARATOR) { if (colIndex >= types.length) { throw new IllegalArgumentException(MessageFormat.format( "Too many columns in line {0,number,integer}: got {1,number,integer}, but expected {2,number,integer}.", rowIndex + 1, colIndex + 1, types.length)); } Class<? extends Comparable<?>> colType = types[colIndex]; Method parseMethod = parseMethods.get(colType); Comparable<?> cell = null; try { cell = (Comparable<?>) parseMethod.invoke( null, cellContent.toString().trim()); } catch (IllegalArgumentException e) { throw new RuntimeException(MessageFormat.format( "Could not invoke method for parsing data type {0} in column {1,number,integer}.", types[colIndex].getSimpleName(), colIndex)); } catch (IllegalAccessException e) { throw new RuntimeException(MessageFormat.format( "Could not access method for parsing data type {0} in column {1,number,integer}.", types[colIndex].getSimpleName(), colIndex)); } catch (InvocationTargetException e) { if (cellContent.length() > 0) { throw new IOException(MessageFormat.format( "Type mismatch in line {0,number,integer}, column {1,number,integer}: got \"{2}\", but expected {3} value.", rowIndex + 1, colIndex + 1, cellContent.toString(), colType.getSimpleName())); } } row.add(cell); colIndex++; if (token.getType() == CSVTokenType.ROW_SEPARATOR) { if (row.size() < types.length) { throw new IllegalArgumentException(MessageFormat.format( "Not enough columns in line {0,number,integer}: got {1,number,integer}, but expected {2,number,integer}.", rowIndex + 1, row.size(), types.length)); } data.add(row); rowIndex++; row.clear(); colIndex = 0; } cellContent = new StringBuilder(); } } return data; } CSVReader(String mimeType); DataSource read(InputStream input, Class<? extends Comparable<?>>... types); static final String SEPARATOR_CHAR; }
@Test @SuppressWarnings("unchecked") public void testMimeTypes() throws IOException { String[] formats = { "text/csv", "text/tab-separated-values" }; String[] dataStrings = { "0,10.0,20\r\n" + "1,11.0,21\r\n" + "2,12.0,22\r\n", "0\t10.0\t20\r\n" + "1\t11.0\t21\r\n" + "2\t12.0\t22\r\n" }; for (int i = 0; i < formats.length; i++) { DataReader reader = DataReaderFactory.getInstance().get(formats[i]); ByteArrayInputStream input = new ByteArrayInputStream(dataStrings[i].getBytes()); DataSource data = reader.read(input, Integer.class, Double.class, Double.class); assertEquals( 0, data.get(0, 0)); assertEquals( 1, data.get(0, 1)); assertEquals( 2, data.get(0, 2)); assertEquals(10.0, data.get(1, 0)); assertEquals(11.0, data.get(1, 1)); assertEquals(12.0, data.get(1, 2)); assertEquals(20.0, data.get(2, 0)); assertEquals(21.0, data.get(2, 1)); assertEquals(22.0, data.get(2, 2)); } }
@Test @SuppressWarnings("unchecked") public void testSeparator() throws IOException { InputStream input = new ByteArrayInputStream(( "0;10.0;20\r\n" + "1;11.0;21\r\n" + "2;12.0;22\r\n" ).getBytes()); DataReader reader = DataReaderFactory.getInstance().get("text/csv"); reader.setSetting(CSVReader.SEPARATOR_CHAR, ';'); DataSource data = reader.read(input, Integer.class, Double.class, Double.class); assertEquals( 0, data.get(0, 0)); assertEquals( 1, data.get(0, 1)); assertEquals( 2, data.get(0, 2)); assertEquals(10.0, data.get(1, 0)); assertEquals(11.0, data.get(1, 1)); assertEquals(12.0, data.get(1, 2)); assertEquals(20.0, data.get(2, 0)); assertEquals(21.0, data.get(2, 1)); assertEquals(22.0, data.get(2, 2)); }
@Test @SuppressWarnings("unchecked") public void testQuotedNumbers() throws IOException { InputStream input = new ByteArrayInputStream(( "\"0\",\"10.0\",\"20\"\r\n" + "\"1\",\"11.0\",\"21\"\r\n" + "\"2\",\"12.0\",\"22\"\r\n" ).getBytes()); DataReader reader = DataReaderFactory.getInstance().get("text/csv"); DataSource data = reader.read(input, Integer.class, Double.class, Double.class); assertEquals( 0, data.get(0, 0)); assertEquals( 1, data.get(0, 1)); assertEquals( 2, data.get(0, 2)); assertEquals(10.0, data.get(1, 0)); assertEquals(11.0, data.get(1, 1)); assertEquals(12.0, data.get(1, 2)); assertEquals(20.0, data.get(2, 0)); assertEquals(21.0, data.get(2, 1)); assertEquals(22.0, data.get(2, 2)); }
@Test @SuppressWarnings("unchecked") public void testQuotedString() throws IOException { InputStream input = new ByteArrayInputStream(( "\"foo\tbar\"\tfoo bar\r\n" + "foobar\t\"foo \"\"the\"\" bar\"\r\n" ).getBytes()); DataReader reader = DataReaderFactory.getInstance().get("text/tab-separated-values"); DataSource data = reader.read(input, String.class, String.class); assertEquals("foo\tbar", data.get(0, 0)); assertEquals("foo bar", data.get(1, 0)); assertEquals("foobar", data.get(0, 1)); assertEquals("foo \"the\" bar", data.get(1, 1)); }
@Test @SuppressWarnings("unchecked") public void testLineSeparators() throws IOException { String[] dataStrings = { "0,10.0,20\r\n" + "1,11.0,21\r\n" + "2,12.0,22\r\n", "0,10.0,20\r" + "1,11.0,21\r" + "2,12.0,22\r", "0,10.0,20\n" + "1,11.0,21\n" + "2,12.0,22\n", "0,10.0,20\r\n" + "1,11.0,21\r\n" + "2,12.0,22", "0,10.0,20\r" + "1,11.0,21\r" + "2,12.0,22", "0,10.0,20\n" + "1,11.0,21\n" + "2,12.0,22" }; for (String dataString : dataStrings) { DataReader reader = DataReaderFactory.getInstance().get("text/csv"); ByteArrayInputStream input = new ByteArrayInputStream(dataString.getBytes()); DataSource data = reader.read(input, Integer.class, Double.class, Double.class); assertEquals( 0, data.get(0, 0)); assertEquals( 1, data.get(0, 1)); assertEquals( 2, data.get(0, 2)); assertEquals(10.0, data.get(1, 0)); assertEquals(11.0, data.get(1, 1)); assertEquals(12.0, data.get(1, 2)); assertEquals(20.0, data.get(2, 0)); assertEquals(21.0, data.get(2, 1)); assertEquals(22.0, data.get(2, 2)); } }
@Test @SuppressWarnings("unchecked") public void testIllegalType() throws IOException { InputStream input = new ByteArrayInputStream(( "0.0,10.0,20\r\n" + "1,11.0,21\r\n" + "2,12.0,22\r\n" ).getBytes()); DataReader reader = DataReaderFactory.getInstance().get("text/csv"); try { reader.read(input, Integer.class, Double.class, Double.class); fail("Expected IOException"); } catch (IOException e) { } }
@Test @SuppressWarnings("unchecked") public void testNotEnoughColumns() throws IOException, ParseException { InputStream input = new ByteArrayInputStream(( "0,10.0,20\r\n" + "1,11.0\r\n" + "2,12.0,22\r\n" ).getBytes()); DataReader reader = DataReaderFactory.getInstance().get("text/csv"); try { reader.read(input, Integer.class, Double.class, Double.class); fail("Expected IllegalArgumentException because there are not enough columns."); } catch (IllegalArgumentException e) { } }
@Test @SuppressWarnings("unchecked") public void testTooManyColumns() throws IOException, ParseException { InputStream input = new ByteArrayInputStream(( "0,10.0,20\r\n" + "1,11.0,21,42\r\n" + "2,12.0,22\r\n" ).getBytes()); DataReader reader = DataReaderFactory.getInstance().get("text/csv"); try { reader.read(input, Integer.class, Double.class, Double.class); fail("Expected IllegalArgumentException because there are too many columns."); } catch (IllegalArgumentException e) { } }
@Test @SuppressWarnings("unchecked") public void testEmptyValues() throws IOException, ParseException { InputStream input = new ByteArrayInputStream(( "0,10.0,\r\n" + "1,,21\r\n" + ",,3\r\n" ).getBytes()); DataReader reader = DataReaderFactory.getInstance().get("text/csv"); DataSource data = reader.read(input, Integer.class, Double.class, Double.class); assertEquals( 0, data.get(0, 0)); assertEquals(10.0, data.get(1, 0)); assertNull( data.get(2, 0)); assertEquals( 1, data.get(0, 1)); assertNull( data.get(1, 1)); assertEquals(21.0, data.get(2, 1)); assertNull( data.get(0, 2)); assertNull( data.get(1, 2)); assertEquals( 3.0, data.get(2, 2)); }
@Test @SuppressWarnings("unchecked") public void testNegativeIntegerValues() throws IOException, ParseException { InputStream input = new ByteArrayInputStream(( "-0,-10.0,-20\r\n" + "-1,-11.0,-21\r\n" + "-2,-12.0,-22\r\n" ).getBytes()); DataReader reader = DataReaderFactory.getInstance().get("text/csv"); DataSource data = reader.read(input, Integer.class, Double.class, Double.class); assertEquals( 0, data.get(0, 0)); assertEquals( -1, data.get(0, 1)); assertEquals( -2, data.get(0, 2)); assertEquals(-10.0, data.get(1, 0)); assertEquals(-11.0, data.get(1, 1)); assertEquals(-12.0, data.get(1, 2)); assertEquals(-20.0, data.get(2, 0)); assertEquals(-21.0, data.get(2, 1)); assertEquals(-22.0, data.get(2, 2)); }