Created
April 3, 2017 19:06
-
-
Save snarkbait/8ff89caffff0c2f0c8dc0c9411f6b77a to your computer and use it in GitHub Desktop.
Annotations and Reflection: CSV-to-Object Reader
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ""; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ""; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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