diff --git a/src/main/java/edu/unl/cse/csv_io/AbstractFormattedFile.java b/src/main/java/edu/unl/cse/csv_io/AbstractFormattedFile.java index bb5a1c8384ba2e2b113b810f2a8a3f7873309705..53230404cb19dad5f54700ff821d8bc192bbebcc 100644 --- a/src/main/java/edu/unl/cse/csv_io/AbstractFormattedFile.java +++ b/src/main/java/edu/unl/cse/csv_io/AbstractFormattedFile.java @@ -13,6 +13,13 @@ public abstract class AbstractFormattedFile implements FormattedFile { fields = new LinkedHashSet<>(); } + /** + * Provides a collection of unique field names. If the file has not been read from nor written to, there are no + * guarantees that the returned {@link java.util.Set} contains an accurate collection of field names. Otherwise, the + * {@link java.util.Set} will contain the fields names that are current as of the last file access. + * + * @return a {@link java.util.Set} of the file's field names + */ public Set<String> getFields() { return Collections.unmodifiableSet(fields); } diff --git a/src/main/java/edu/unl/cse/csv_io/CsvFile.java b/src/main/java/edu/unl/cse/csv_io/CsvFile.java index 565498b7acc611e9df8fb07d96c64a1efb1f5a59..9f0e51338d2a9e7e79922279be75463f4416475f 100644 --- a/src/main/java/edu/unl/cse/csv_io/CsvFile.java +++ b/src/main/java/edu/unl/cse/csv_io/CsvFile.java @@ -13,19 +13,27 @@ import java.io.*; import java.net.URL; import java.util.*; -@SuppressWarnings("unused") public class CsvFile extends AbstractFormattedFile implements FormattedFile { CsvFile(String filename) { super("csv/" + filename + ".csv"); } + /** + * Provides the contents of the file as a mapping from the CSV header entries to the values for those header + * entries. Each {@link java.util.Map} will represent a CSV row. The {@link java.util.Map}s will be returned as a + * {@link java.util.Collection}; no guarantees are made about the order they will be accessed when iterating over + * the {@link java.util.Collection}. + * + * @return a {@link java.util.Collection} of {@link java.util.Map}s that relate CSV header entries to values for + * each CSV row + */ @Override public Collection<Map<String, String>> readFile() { List<Map<String, String>> csvList = null; try (InputStreamReader inputStreamReader = new InputStreamReader(Objects.requireNonNull( CsvFile.class.getClassLoader().getResourceAsStream(filename)))) { csvList = (List<Map<String, String>>) parseCSV(inputStreamReader, new LinkedList<>()); - for(Map<String,String> row:csvList) { + for (Map<String, String> row : csvList) { addAllFields(row.keySet()); } } catch (NullPointerException nullPointerException) { @@ -36,23 +44,13 @@ public class CsvFile extends AbstractFormattedFile implements FormattedFile { return csvList; } - Collection<Map<String, String>> parseCSV(Reader reader, Collection<Map<String, String>> destination) { - Map<String, String> line; - try { - CSVReaderHeaderAware csvReader = new CSVReaderHeaderAwareBuilder(reader).build(); - while ((line = csvReader.readMap()) != null) { - destination.add(line); - } - } catch (NullPointerException ignored) { - // CSVReaderHeaderAwareBuilder.build() throws NullPointerException if reader is empty - } catch (IOException ioException) { - System.err.println("Error reading CSV file. " + ioException); - } catch (CsvValidationException csvValidationException) { - System.err.println("Could not validate a line in CSV file. " + csvValidationException); - } - return destination; - } - + /** + * Overwrites the file's contents with the data passed to this method, in the file's format. + * + * @param data a {@link java.util.Collection} of {@link java.util.Map}s that relate fields to values; see + * {@link #readFile()} for a description of this data structure + * @return {@code true} if the file was successfully written to; {@code false} otherwise + */ @Override public boolean writeFile(Collection<Map<String, String>> data) { boolean wroteFile = true; @@ -61,9 +59,9 @@ public class CsvFile extends AbstractFormattedFile implements FormattedFile { if (resource == null) { try { String s = Objects.requireNonNull(classLoader.getResource(("csv/"))).getFile(); - File f = new File(s, filename); + File file = new File(s, filename); //noinspection ResultOfMethodCallIgnored - f.createNewFile(); + file.createNewFile(); } catch (IOException ioException) { System.err.println("Error creating " + filename + ". " + ioException); } @@ -74,7 +72,8 @@ public class CsvFile extends AbstractFormattedFile implements FormattedFile { try (FileWriter fileWriter = new FileWriter(file)) { placeCSVonWriter(data, fileWriter); } catch (FileNotFoundException fileNotFoundException) { - System.err.println("Could not open " + filename + "; probably due to a bad pathname. " + fileNotFoundException); + System.err.println("Could not open " + filename + "; probably due to a bad pathname. " + + fileNotFoundException); wroteFile = false; } catch (IOException ioException) { System.err.println("Error opening " + filename + ". " + ioException); @@ -87,6 +86,77 @@ public class CsvFile extends AbstractFormattedFile implements FormattedFile { return wroteFile; } + /** + * Converts a {@link java.util.List} or {@link java.util.Set} (or any other {@link java.util.Collection}) into a + * delimited {@link java.lang.String} suitable for storage as an entry in a CSV file. The + * {@link java.util.Collection}'s elements will be stored in the string, separated by the specified delimiter. + * + * @param collection the data to be placed in a delimited string + * @param delimiter the delimiter that will separate the elements in the string + * @return a {@link java.lang.String} representing the {@link java.util.Collection} + */ + public static String createDelimitedCollection(Collection<String> collection, String delimiter) { + if (delimiter.equals("\"")) { + throw new IllegalArgumentException("The double-quote character \" cannot be a delimiter."); + } + StringBuilder delimitedCollection = new StringBuilder(); + int tokensRemaining = collection.size(); + for (String datum : collection) { + if (datum.contains(delimiter)) { + delimitedCollection.append("\"").append(datum).append("\""); + } else { + delimitedCollection.append(datum); + } + if (--tokensRemaining > 0) { + delimitedCollection.append(delimiter); + } + } + return delimitedCollection.toString(); + } + + /** + * Converts a delimited "list" from a {@link java.lang.String} representation to a {@link java.util.List}. For + * example, {@code createFreeList("foo bar", " ")} returns {@code ["foo", "bar"]}. + * + * @param delimitedCollection the delimited {@link java.lang.String} to be converted into a {@link java.util.List} + * @param delimiter the delimiter that separates elements of the "list" + * @return a {@link java.util.List} of {@link java.lang.String}s corresponding to the delimited "list" + */ + @SuppressWarnings("unused") + public static List<String> createFreeList(String delimitedCollection, String delimiter) { + return (List<String>) createFreeCollection(delimitedCollection, delimiter, new LinkedList<>()); + } + + /** + * Converts a delimited "set" from a {@link java.lang.String} representation to a {@link java.util.Set}. For + * example, {@code createFreeList("foo bar", " ")} returns {@code {"foo", "bar"}}. + * + * @param delimitedCollection the delimited {@link java.lang.String} to be converted into a {@link java.util.Set} + * @param delimiter the delimiter that separates elements of the "set" + * @return a {@link java.util.List} of Strings corresponding to the delimited "set" + */ + @SuppressWarnings("unused") + public static Set<String> createFreeSet(String delimitedCollection, String delimiter) { + return (Set<String>) createFreeCollection(delimitedCollection, delimiter, new HashSet<>()); + } + + Collection<Map<String, String>> parseCSV(Reader reader, Collection<Map<String, String>> destination) { + Map<String, String> line; + try { + CSVReaderHeaderAware csvReader = new CSVReaderHeaderAwareBuilder(reader).build(); + while ((line = csvReader.readMap()) != null) { + destination.add(line); + } + } catch (NullPointerException ignored) { + // CSVReaderHeaderAwareBuilder.build() throws NullPointerException if reader is empty + } catch (IOException ioException) { + System.err.println("Error reading CSV file. " + ioException); + } catch (CsvValidationException csvValidationException) { + System.err.println("Could not validate a line in CSV file. " + csvValidationException); + } + return destination; + } + void placeCSVonWriter(Collection<Map<String, String>> data, Writer writer) { CSVWriter csvWriter = new CSVWriter(writer); List<String[]> allLines = new LinkedList<>(); @@ -115,33 +185,6 @@ public class CsvFile extends AbstractFormattedFile implements FormattedFile { return fieldArray; } - public static String createDelimitedCollection(Collection<String> collection, String delimiter) { - if (delimiter.equals("\"")) { - throw new IllegalArgumentException("The double-quote character \" cannot be a delimiter."); - } - StringBuilder delimitedCollection = new StringBuilder(); - int tokensRemaining = collection.size(); - for (String datum : collection) { - if (datum.contains(delimiter)) { - delimitedCollection.append("\"").append(datum).append("\""); - } else { - delimitedCollection.append(datum); - } - if (--tokensRemaining > 0) { - delimitedCollection.append(delimiter); - } - } - return delimitedCollection.toString(); - } - - public static List<String> createFreeList(String delimitedCollection, String delimiter) { - return (List<String>) createFreeCollection(delimitedCollection, delimiter, new LinkedList<>()); - } - - public static Set<String> createFreeSet(String delimitedCollection, String delimiter) { - return (Set<String>) createFreeCollection(delimitedCollection, delimiter, new HashSet<>()); - } - static Collection<String> createFreeCollection(String delimitedCollection, String delimiter, Collection<String> collection) { // NOTE: if there are 2n+1 quotes, where n>0, then the delimitedCollection is ambiguous diff --git a/src/main/java/edu/unl/cse/csv_io/FormattedFile.java b/src/main/java/edu/unl/cse/csv_io/FormattedFile.java index fa19088cc9e5daa23e954570bb91bbc7aef73cff..1beba2ccaa7abd2020c0be266d7251186fe6279c 100644 --- a/src/main/java/edu/unl/cse/csv_io/FormattedFile.java +++ b/src/main/java/edu/unl/cse/csv_io/FormattedFile.java @@ -4,10 +4,39 @@ import java.util.Collection; import java.util.Map; import java.util.Set; +/** + * Provides access to the contents of a data file in a well-defined format, such as CSV or JSON. + */ @SuppressWarnings("unused") public interface FormattedFile { - // TODO: change from Map<String,String> to Map<String,Object>, where Object is constrained to String, Long, Boolean, or List<String> + // TODO: change from Map<String,String> to Map<String,Object>, where Object is constrained to String, Long, + // Boolean, or List<String> + + /** + * Provides the contents of the file as a mapping from the fields (such as CSV headers) to the values for those + * fields. Each {@link java.util.Map} will represent a single entity (such as a CSV row). The + * {@link java.util.Map}s will be returned as a {@link java.util.Collection}; no guarantees are made about the + * order they will be accessed when iterating over the {@link java.util.Collection}. + * + * @return a {@link java.util.Collection} of {@link java.util.Map}s that relate fields to values + */ Collection<Map<String, String>> readFile(); + + /** + * Overwrites the file's contents with the data passed to this method, in the file's format. + * + * @param data a {@link java.util.Collection} of {@link java.util.Map}s that relate fields to values; see + * {@link #readFile()} for a description of this data structure + * @return {@code true} if the file was successfully written to; {@code false} otherwise + */ boolean writeFile(Collection<Map<String, String>> data); + + /** + * Provides a collection of unique field names. If the file has not been read from nor written to, there are no + * guarantees that the returned {@link java.util.Set} contains an accurate collection of field names. Otherwise, the + * {@link java.util.Set} will contain the fields names that are current as of the last file access. + * + * @return a {@link java.util.Set} of the file's field names + */ Set<String> getFields(); }