Skip to content

Instantly share code, notes, and snippets.

@snarkbait
Created April 3, 2017 19:06
Show Gist options
  • Save snarkbait/8ff89caffff0c2f0c8dc0c9411f6b77a to your computer and use it in GitHub Desktop.
Save snarkbait/8ff89caffff0c2f0c8dc0c9411f6b77a to your computer and use it in GitHub Desktop.
Annotations and Reflection: CSV-to-Object Reader
package csv;
import java.lang.annotation.*;
/**
* @author /u/Philboyd_Studge on 4/1/2017.
*/
@Inherited
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CSVField {
int index();
CSVTypes type() default CSVTypes.STRING;
boolean quotes() default false;
String format() default "";
}
package csv;
import java.lang.annotation.*;
/**
* @author /u/Philboyd_Studge on 4/2/2017.
*/
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface CSVHeader {
boolean has_header() default false;
String header() default "";
}
package csv;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
/**
* @author /u/Philboyd_Studge on 4/1/2017.
*/
class CSVInfo {
final int index;
final CSVTypes type;
final Field field;
final boolean quotes;
final String format;
CSVInfo(CSVField annotation, Field field) {
this.index = annotation.index();
this.type = annotation.type();
this.quotes = annotation.quotes();
this.format = annotation.format();
this.field = field;
}
static List<CSVInfo> getAnnotatedFieldInfo(Class<?> csvClass) {
List<CSVInfo> fields = new ArrayList<>();
Field[] fieldArray = csvClass.getDeclaredFields();
for (Field each : fieldArray) {
csv.CSVField annotation = each.getAnnotation(csv.CSVField.class);
if (annotation != null) {
each.setAccessible(true);
fields.add(new CSVInfo(annotation, each));
}
}
return fields;
}
}
package csv;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
/**
* @author /u/Philboyd_Studge on 4/2/2017.
*/
public class CSVParser {
enum State { QUOTED, READ, ACCEPT } // FSM states
private final static char COMMA = ',';
private final static char DOUBLE_QUOTE = '\"';
private CSVParser() {}
/**
* Parse CSV String into String array, removing quotes around entries, and preserving
* commas and quotes inside the tokens.
* additional commas, even at the beginning or end, will be treated as entries
* with a value of ""
* @param input Comma-separated-values string
* @return array of Strings
*/
public static String[] CSVSplit(String input) {
List<String> split = new ArrayList<>();
Deque<Character> queue = new ArrayDeque<>();
State current = State.ACCEPT;
for (int i = 0; i < input.length(); i++) {
char currentChar = input.charAt(i);
switch (current) {
case ACCEPT:
switch (currentChar) {
case COMMA:
split.add(queueToString(queue));
break;
case DOUBLE_QUOTE:
queue = new ArrayDeque<>();
current = State.QUOTED;
break;
default:
queue = new ArrayDeque<>();
queue.addLast(currentChar);
current = State.READ;
}
break;
case QUOTED:
if (currentChar == DOUBLE_QUOTE) {
if (i < input.length() - 1 && input.charAt(i + 1) == COMMA) {
current = State.ACCEPT;
} else {
if(i < input.length() - 1) {
queue.addLast(currentChar);
}
}
} else {
queue.addLast(currentChar);
}
break;
case READ:
if (currentChar == COMMA) {
current = State.ACCEPT;
split.add(queueToString(queue));
} else {
queue.addLast(currentChar);
}
}
}
// dump any remaining chars in the queue
split.add(queueToString(queue));
// make new array to pass to 'toArray'
String[] a = new String[split.size()];
return split.toArray(a);
}
private static String queueToString(Deque<Character> queue) {
String result = "";
while (queue.size() > 0) {
result += queue.pop();
}
return result;
}
}
package csv;
import java.io.*;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* @author /u/Philboyd_Studge on 4/1/2017.
*/
public class CSVReader<T> {
private Class csvClass;
private List<CSVInfo> fields;
public CSVReader(Class csvClass) {
this.csvClass = csvClass;
fields = CSVInfo.getAnnotatedFieldInfo(csvClass);
}
@SuppressWarnings("unchecked")
private T getObjectFromCSV(String line) {
try {
String[] split = CSVParser.CSVSplit(line);
Object instance;
try {
instance = csvClass.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
return null;
}
for (CSVInfo each : fields) {
if (each.index < 0 || each.index >= split.length) {
System.out.println("Incorrect CSV entry for line:");
System.out.println(line);
System.out.println("Ignoring line");
return null;
}
String temp = split[each.index];
if (!temp.isEmpty()) {
try {
switch (each.type) {
case INTEGER:
int t = Integer.parseInt(temp);
each.field.set(instance, t);
break;
case FLOAT:
float f = Float.parseFloat(temp);
each.field.set(instance, f);
break;
case DOUBLE:
double d = Double.parseDouble(temp);
each.field.set(instance, d);
break;
case DATE:
SimpleDateFormat format = new SimpleDateFormat(each.format);
Date date = format.parse(temp);
each.field.set(instance, date);
break;
case BOOL:
boolean b = Boolean.parseBoolean(temp);
each.field.set(instance, b);
break;
case STRING:
each.field.set(instance, temp);
break;
}
}
catch (NumberFormatException nfe) {
System.out.println("Incorrect CSV entry for line: Number Format exception");
System.out.println(line);
System.out.println("Ignoring line");
return null;
}
catch (ParseException pe) {
System.out.println("Incorrect CSV entry for line: Problem parsing Date");
System.out.println(line);
System.out.println("Ignoring line");
return null;
}
}
}
return (T) instance;
} catch ( IllegalAccessException e) {
e.printStackTrace();
return null;
}
}
public List<T> readListFromCSV(String filename) {
List<T> results = null;
try (BufferedReader br = new BufferedReader(new FileReader(filename))){
results = readListFromCSV(br);
} catch (IOException ioe) {
ioe.printStackTrace();
}
return results;
}
public List<T> readListFromCSVURL(String url) {
List<T> results = null;
try (BufferedReader br = new BufferedReader(new InputStreamReader(new URL(url).openStream()))){
results = readListFromCSV(br);
} catch (IOException ioe) {
ioe.printStackTrace();
}
return results;
}
private List<T> readListFromCSV(BufferedReader br) throws IOException{
List<T> results = new ArrayList<>();
String input;
if (csvClass.isAnnotationPresent(CSVHeader.class)) {
CSVHeader header = (CSVHeader) csvClass.getAnnotation(CSVHeader.class);
if (header.has_header()) {
// ignore header line
br.readLine();
}
}
while ((input = br.readLine()) != null) {
T t = null;
t = getObjectFromCSV(input);
if (t != null) {
results.add(t);
}
}
return results;
}
}
package csv;
/**
* @author /u/Philboyd_Studge on 4/1/2017.
*/
public enum CSVTypes {
INTEGER, STRING, FLOAT, DOUBLE, DATE, BOOL
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment