I am generating a CSV file along with CTL file for use with sqlldr
. The CTL file needs to know the names of the columns that I want to load, and my CSV file needs to know the default values of these fields.
/*
* Models a line in the CSV file
*/
public class CSVRecord {
...
}
/*
* Models the CTL file
*/
public class ControlFile {
...
}
These 2 classes are initialised and used inside CSVExportFile
and I have 2 approaches:
public enum Columns {
ID("1"),
NAME("Bob"),
...
}
public class CSVExportFile {
private HashMap<String, String> columns;
public CSVExportFile() {
columns = new HashMap<String, String>();
columns.put("ID", "1");
columns.put("Name", "Bob");
...
}
}
The HashMap
reduces the scope of the columns and will mean that they can only be used inside CSVExportFile
. I do not plan to extend this functionality (all classes will be final
), so I'm not sure that my enum
gains me anything.
What are the arguments for/against each approach, is this a specific case where one is superior, or is one way always superior?
I would always use an enum
here as enum
have an innate ordering while Map
s do not.
By using an enum
you can generate the CTL file from the enum itself and use the enum
values as factories to populate your csv file.
class MyObj {
final String foreName;
final String surname;
public MyObj(String foreName, String surname) {
this.foreName = foreName;
this.surname = surname;
}
public String getForeName() {
return foreName;
}
public String getSurname() {
return surname;
}
}
enum Column {
Forename {
@Override
String fromMyObj(MyObj it) {
return it.getForeName();
}
},
Surname {
@Override
String fromMyObj(MyObj it) {
return it.getSurname();
}
},;
abstract String fromMyObj(MyObj it);
static String asSelectStatement(Set<Column> columns, String tableName) {
return join(columns, ",", "SELECT ", " FROM " + tableName);
}
static String asCSVHeader(Set<Column> columns) {
return join(columns, ",");
}
static String asCSV(Set<Column> columns, MyObj it) {
return join(columns, (Column a) -> a.fromMyObj(it), ",");
}
private static String join(Set<Column> columns, String between) {
return join(columns, new StringJoiner(between));
}
private static String join(Set<Column> columns, String between, String prefix, String suffix) {
return join(columns, new StringJoiner(between, prefix, suffix));
}
private static String join(Set<Column> columns, StringJoiner joined) {
return join(columns, (Column a) -> a.name(), joined);
}
private static String join(Set<Column> columns, Function<Column, String> as, String between) {
return join(columns, as, new StringJoiner(between));
}
private static String join(Set<Column> columns, Function<Column, String> as, String between, String prefix, String suffix) {
return join(columns, as, new StringJoiner(between, prefix, suffix));
}
private static String join(Set<Column> columns, Function<Column, String> as, StringJoiner joined) {
for (Column c : columns) {
joined.add(as.apply(c));
}
return joined.toString();
}
// Also simple to auto-populate prepared statements, build INSERT statements etc.
}
public void test() {
Set<Column> columns = EnumSet.of(Column.Forename, Column.Surname);
System.out.println("As Select: " + Column.asSelectStatement(columns, "MyTable"));
System.out.println("As CSV Header: " + Column.asCSVHeader(columns));
MyObj it = new MyObj("My Forename", "My Surname");
System.out.println("As CSV: " + Column.asCSV(columns, it));
}
I would prefer Enum - using a type will give you flexibility to extend and change your implementation, using abstraction and still keeping it encapsulated in your class. To use an example, what if at some later point you decide, that you need to format your dates differently for CTL file? With enum you can implement that with abstraction, what matters, when you have ten dates and one hundred columns:
public enum Column {
ID("1"),
NAME("Bob"),
DATE_OF_BIRTH("1980-01-01", "yyyy-MM-dd", "yyyyMMdd");
private String defaultValue;
private String ctlDefaultValue;
Column(String defaultValue) {
this.defaultValue = defaultValue;
}
Column(String defaultValue, String csvFormat, String ctlFormat) {
this(defaultValue);
try {
this.ctlDefaultValue = new SimpleDateFormat(ctlFormat)
.format(new SimpleDateFormat(csvFormat)
.parseObject(defaultValue));
} catch (ParseException e) {
this.ctlDefaultValue = "";
}
}
public String valueForCTL() {
return ctlDefaultValue == null ? defaultValue : ctlDefaultValue;
}
public String valueForCsv() {
return defaultValue;
}
public static void main(String[] args) {
System.out.println(DATE_OF_BIRTH.valueForCTL());
System.out.println(DATE_OF_BIRTH.valueForCsv());
}
}
You may also wish to store a type of value for some reason, then you need only to add new property to your enum. With map approach, you would need actually a second map or to define a type to be used as map value.
And what if you discover, that you need different order for CSV and CTL? Well, with Map (Sorted one) it should be easy to create a differently sorted copy, but you can go with enum with the same ease:
public enum ColumnDifferentOrder{
ID("1", 3),
NAME("Bob", 2),
DATE_OF_BIRTH("1980-01-01", 1);
private String defaultValue;
private int csvOrder;
ColumnDifferentOrder(String defaultValue, int csvOrder) {
this.defaultValue = defaultValue;
this.csvOrder = csvOrder;
}
public static ColumnDifferentOrder[] orderForCsv() {
ColumnDifferentOrder[] columns = ColumnDifferentOrder.values();
Arrays.sort(columns, new Comparator<ColumnDifferentOrder>() {
@Override
public int compare(ColumnDifferentOrder o1, ColumnDifferentOrder o2) {
return o1.csvOrder - o2.csvOrder;
}
});
return columns;
}
public static ColumnDifferentOrder[] orderForCtl() {
return ColumnDifferentOrder.values();
}
public static void main(String[] args) {
System.out.println(Arrays.toString(ColumnDifferentOrder.orderForCsv()));
System.out.println(Arrays.toString(ColumnDifferentOrder.orderForCtl()));
}
}
The only thing I can thought of, where Map will be better, is when you actually do not want to iterate, but access chosen value - Map will get it faster.
I would create a class with properties for column names like:
public class CSVRecord {
private int id;
private String name;
// getters and setters here.
}
Where id and name are actual columns in csv file.
Then create a list of your records List<CSVRecord> csvRecordList
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With