package org.reldb.rel.v0.storage.relvars.external.csv;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.reldb.rel.exceptions.ExceptionSemantic;
import org.reldb.rel.v0.generator.Generator;
import org.reldb.rel.v0.storage.relvars.RelvarExternal;
import org.reldb.rel.v0.storage.relvars.RelvarExternalMetadata;
import org.reldb.rel.v0.storage.relvars.RelvarHeading;
import org.reldb.rel.v0.storage.relvars.external.CSVLineParse;
import org.reldb.rel.v0.storage.relvars.external.csv.RelvarCSVMetadata.CSVSpec;
import org.reldb.rel.v0.storage.tables.TableCustom;
import org.reldb.rel.v0.types.Heading;
import org.reldb.rel.v0.values.RelTupleFilter;
import org.reldb.rel.v0.values.RelTupleMap;
import org.reldb.rel.v0.values.TupleFilter;
import org.reldb.rel.v0.values.TupleIterator;
import org.reldb.rel.v0.values.TupleIteratorAutokey;
import org.reldb.rel.v0.values.TupleIteratorCount;
import org.reldb.rel.v0.values.TupleIteratorUnique;
import org.reldb.rel.v0.values.Value;
import org.reldb.rel.v0.values.ValueCharacter;
import org.reldb.rel.v0.values.ValueRelation;
import org.reldb.rel.v0.values.ValueTuple;
import org.reldb.rel.v0.vm.Context;
public class TableCSV extends TableCustom {
private File file;
private DuplicateHandling duplicates;
private Generator generator;
private Heading fileHeading;
private boolean hasHeading = true;
public TableCSV(String Name, RelvarExternalMetadata metadata, Generator generator, DuplicateHandling duplicates) {
this.generator = generator;
this.duplicates = duplicates;
RelvarCSVMetadata meta = (RelvarCSVMetadata) metadata;
CSVSpec spec = RelvarCSVMetadata.obtainCSVSpec(meta.getPath());
file = new File(spec.filePath);
hasHeading = spec.hasHeading;
RelvarHeading heading = meta.getHeadingDefinition(generator.getDatabase());
Heading storedHeading = heading.getHeading();
fileHeading = RelvarCSVMetadata.getHeadingFromCSV(meta.getPath(), duplicates).getHeading();
if (storedHeading.toString().compareTo(fileHeading.toString()) != 0)
throw new ExceptionSemantic("RS0453: Stored CSV metadata is " + storedHeading + " but file metadata is " + fileHeading + ". Has the file structure changed?");
}
private ValueTuple toTuple(String line) {
String[] rawValues = CSVLineParse.parse(line);
Value[] values = new Value[rawValues.length];
if (values.length != fileHeading.getDegree() - ((duplicates == DuplicateHandling.DUP_COUNT || duplicates == DuplicateHandling.AUTOKEY) ? 1 : 0))
throw new ExceptionSemantic("RS0457: CSV file " + file.getAbsolutePath() + " with heading " + fileHeading + " has malformed line: " + line);
for (int i = 0; i < rawValues.length; i++)
values[i] = ValueCharacter.select(generator, rawValues[i]);
return new ValueTuple(generator, values);
}
@Override
public TupleIterator iterator() {
switch (duplicates) {
case DUP_REMOVE: return new TupleIteratorUnique(iteratorRaw());
case DUP_COUNT: return new TupleIteratorUnique(new TupleIteratorCount(iteratorRaw(), generator));
case AUTOKEY: return new TupleIteratorAutokey(iteratorRaw(), generator);
default: throw new ExceptionSemantic("EX0003: Duplicate handling method " + duplicates.toString() + " is not supported by CSV.");
}
}
@Override
public TupleIterator iterator(Generator generator) {
return iterator();
}
@Override
public long getCardinality() {
long count = 0;
TupleIterator iterator = iterator();
try {
while (iterator.hasNext()) {
count++;
iterator.next();
}
} finally {
iterator.close();
}
return count;
}
private static void notImplemented(String what) {
throw new ExceptionSemantic("EX0004: CSV relvars do not yet support " + what + ".");
}
@Override
public boolean contains(Generator generator, ValueTuple tuple) {
TupleIterator iterator = iterator();
try {
while (iterator.hasNext())
if (tuple.equals(iterator.next()))
return true;
} finally {
iterator.close();
}
return false;
}
@Override
public ValueTuple getTupleForKey(Generator generator, ValueTuple tuple) {
return null;
}
@Override
public void setValue(RelvarExternal relvarCSV, ValueRelation relation) {
notImplemented("assignment");
}
@Override
public long insert(Generator generator, ValueRelation relation) {
long count = 0;
TupleIterator iterator = relation.iterator();
while (iterator.hasNext())
count += insert(generator, iterator.next());
return count;
}
@Override
public long insert(Generator generator, ValueTuple tuple) {
try {
FileWriter fw = new FileWriter(file.getAbsolutePath(), true);
fw.write("\n" + tuple.toCSV());
fw.close();
return 1;
} catch (IOException e) {
}
return 0;
}
@Override
public long insertNoDuplicates(Generator generator, ValueRelation relation) {
long count = 0;
TupleIterator iterator = relation.iterator();
while (iterator.hasNext()) {
ValueTuple tuple = iterator.next();
if (!contains(generator, tuple))
count += insert(generator, tuple);
}
return count;
}
@Override
public void purge() {
BufferedReader br = null;
try {
String heading;
br = new BufferedReader(new FileReader(file));
heading = br.readLine();
br.close();
FileWriter fw = new FileWriter(file.getAbsolutePath());
fw.write(heading);
fw.close();
} catch (IOException e) {
}
}
@Override
public void delete(Generator generator, ValueTuple tuple) {
notImplemented("DELETE");
}
@Override
public long delete(Generator generator, RelTupleFilter relTupleFilter) {
long count = 0;
TupleIterator iterator = this.iterator();
ValueTuple tuple;
List<ValueTuple> tuplesToDelete = new ArrayList<ValueTuple>();
while (iterator.hasNext()) {
tuple = iterator.next();
if (relTupleFilter.filter(tuple))
tuplesToDelete.add(tuple);
}
for (ValueTuple tuples : tuplesToDelete) {
delete(generator, tuples);
count++;
}
return count;
}
@Override
public long delete(Generator generator, TupleFilter filter) {
long count = 0;
TupleIterator iterator = this.iterator();
ValueTuple tuple;
List<ValueTuple> tuplesToDelete = new ArrayList<ValueTuple>();
while (iterator.hasNext()) {
tuple = iterator.next();
if (filter.filter(tuple))
tuplesToDelete.add(tuple);
}
for (ValueTuple tuples : tuplesToDelete) {
delete(generator, tuples);
count++;
}
return count;
}
@Override
public long delete(Context context, ValueRelation tuplesToDelete, boolean errorIfNotIncluded) {
long count = 0;
TupleIterator iterator = tuplesToDelete.iterator();
while (iterator.hasNext()) {
delete(generator, iterator.next());
count++;
}
return count;
}
@Override
public long update(Generator generator, RelTupleMap relTupleMap) {
notImplemented("UPDATE");
return 0;
}
@Override
public long update(Generator generator, RelTupleFilter relTupleFilter, RelTupleMap relTupleMap) {
notImplemented("UPDATE");
return 0;
}
private TupleIterator iteratorRaw() {
return new TupleIterator() {
String currentLine = null;
BufferedReader reader = null;
@Override
public boolean hasNext() {
if (currentLine != null)
return true;
if (reader == null)
try {
reader = new BufferedReader(new FileReader(file));
if (hasHeading)
reader.readLine(); // skip heading line
} catch (IOException ioe) {
throw new ExceptionSemantic("EX0007: Unable to read CSV file '" + file.getPath() + "': " + ioe);
}
try {
currentLine = reader.readLine();
if (currentLine == null)
return false;
return true;
} catch (IOException e) {
throw new ExceptionSemantic("EX0008: Unable to read CSV file '" + file.getPath() + "': " + e);
}
}
@Override
public ValueTuple next() {
if (hasNext())
try {
// replaceAll to filter out Byte Order Mark (BOM), if present
return toTuple(currentLine.replaceAll("\ufeff", " "));
} finally {
currentLine = null;
}
else
return null;
}
@Override
public void close() {
if (reader != null)
try {
reader.close();
} catch (IOException ioe2) {
}
}
};
}
}