The objective of the two classes below is to manage a collection of records where the record structure (i.e. the fields that comprise the record) is variable and defined at run-time. The record structure may contain integers, amounts, dates and strings.
The RecordDefinition class is used to define the record structure. For example, the class below defines a record that consists of an integer in field 1, and a string in field 2.
RecordDefinition def = new RecordDefinition();
def.defineField(RecordDefinition.TYPE_INT, 1);
def.defineField(RecordDefinition.TYPE_STRING, 2);
When a record is created, it is based on a record definition. For example two records are created below. Note that the interface is similar to the one implemented by JDBC to set and get values depending on the type and position:
Record r1 = new Record(def);
r1.setInt(1, 1000); // sets value in position 1
r1.setString(2, "Some String"); // sets value in position 2
Record r2 = new Record(def);
r2.setInt(1, 2000);
r2.setString(2, "Another String");
Then records can be added to collections, for example a list:
List<Record> list = new ArrayList<>();
list.add(r1);
list.add(r2);
To implement this, each Record class contains an array of each field type (i.e. an array of ints, and array of strings, etc). The arrays will contain the values (primitives are used when possible to avoid autoboxing). Also, arrays are used instead of ArrayList objects to improve performance.
A bi-directional HashMap will store the relationships between the field position in the record and the field position in the array (and vice versa). I'm using the Google Guava bi-directional HashMap for this purpose.
Am I on the right track or completely off charts? Any views on improving my approach (or changing it altogether!) will be greatly appreciated. This is the complete code (validation and exception handling removed for clarity):
import java.util.HashMap;
import java.util.Map;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
public class RecordDefinition {
public static final int TYPE_INT = 0;
public static final int TYPE_AMOUNT = 1;
public static final int TYPE_DATE = 2;
public static final int TYPE_STRING = 3;
public static final int NUMBER_OF_TYPES = 4;
/*
* Each entry determines the number of fields in the record for each type
*/
private int [] numberOfFields = new int [NUMBER_OF_TYPES];
/*
* For each type, have a BiMap that determines a field position
* in the record given the position in the array (and vice versa)
*/
private Map<Integer,BiMap<Integer,Integer>> positions = null;
public RecordDefinition() {
positions = new HashMap<>();
// for each field type, create BiMap
// and initialize number of fields
for ( int i=0; i<NUMBER_OF_TYPES; i++ ) {
BiMap<Integer, Integer> map = HashBiMap.create();
positions.put(i, map);
numberOfFields[i] = 0;
}
}
/*
* Defines the position of a field in the record
*
* @param field type (int, amount, etc.)
* @param field position in the record
*/
public void defineField (int type, int positionInRecord) {
BiMap<Integer, Integer> map = positions.get(type);
map.put(positionInRecord, numberOfFields[type]);
numberOfFields[type]++;
}
public int getNumberOfFields (int type) {
return numberOfFields[type];
}
// get position in array, given field type and position in record
public int getPositionInArray(int type, int positionInRecord) {
BiMap<Integer, Integer> map = positions.get(type);
return map.get(positionInRecord);
}
// get position in record, given field type and position in array
public int getPositionInRecord(int type, int positionInArray) {
BiMap<Integer, Integer> map = positions.get(type);
return map.inverse().get(positionInArray);
}
}
import java.util.Date;
public class Record {
private RecordDefinition definition = null;
private double [] doubles = null;
private Date [] dates = null;
private int [] ints = null;
private String [] strings = null;
public Record (RecordDefinition definition) {
this.definition = definition;
int size = definition.getNumberOfFields(RecordDefinition.TYPE_INT);
if ( size > 0 )
ints = new int[size];
size = definition.getNumberOfFields(RecordDefinition.TYPE_AMOUNT);
if ( size > 0 )
doubles = new double[size];
size = definition.getNumberOfFields(RecordDefinition.TYPE_DATE);
if ( size > 0 )
dates = new Date[size];
size = definition.getNumberOfFields(RecordDefinition.TYPE_STRING);
if ( size > 0 )
strings = new String[size];
}
public void setInt (int positionInRecord, int value ) {
int positionInArray = definition.getPositionInArray(RecordDefinition.TYPE_INT, positionInRecord);
ints [positionInArray] = value;
}
public int getInt (int positionInRecord) {
int positionInArray = definition.getPositionInArray(RecordDefinition.TYPE_INT, positionInRecord);
return ints [positionInArray];
}
public void setString (int positionInRecord, String value ) {
int positionInArray = definition.getPositionInArray(RecordDefinition.TYPE_STRING, positionInRecord);
strings [positionInArray] = value;
}
public String getString (int positionInRecord) {
int positionInArray = definition.getPositionInArray(RecordDefinition.TYPE_STRING, positionInRecord);
return strings [positionInArray];
}
/// Other getters and setters
}
Classes in github can be found here
Builder
pattern? Something like what is done inDragon
class here \$\endgroup\$RecordDefinition.TYPE_DATE
defined here. \$\endgroup\$