package io.github.lucaseasedup.logit.storage;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileFilter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public final class CsvStorage implements Storage
{
public CsvStorage(File dir)
{
if (dir == null)
throw new IllegalArgumentException();
this.dir = dir;
}
@Override
public void connect() throws IOException
{
if (!dir.isDirectory())
{
throw new IOException(
"CSV path is not a directory: " + dir
);
}
connected = true;
}
@Override
public boolean isConnected() throws IOException
{
return connected;
}
@Override
public void ping() throws IOException
{
if (!dir.isDirectory())
{
throw new IOException(
"CSV path is not a directory: " + dir
);
}
}
@Override
public void close() throws IOException
{
connected = false;
}
@Override
public List<String> getUnitNames() throws IOException
{
File[] files = dir.listFiles(new FileFilter()
{
@Override
public boolean accept(File pathname)
{
return pathname.isFile();
}
});
List<String> units = new LinkedList<>();
for (File file : files)
{
units.add(file.getName());
}
return units;
}
@Override
public UnitKeys getKeys(String unit) throws IOException
{
if (!connected)
throw new IOException("Database closed.");
UnitKeys keys = new UnitKeys();
try (
FileReader fr = new FileReader(new File(dir, unit));
BufferedReader br = new BufferedReader(fr);
)
{
String line = br.readLine();
if (line == null)
throw new IOException("Null line.");
String[] topValues = line.split(",");
for (String topValue : topValues)
{
keys.put(unescapeValue(topValue), DataType.TEXT);
}
}
return keys;
}
@Override
public String getPrimaryKey(String unit) throws IOException
{
return null;
}
@Override
public List<StorageEntry> selectEntries(String unit) throws IOException
{
return selectEntries(unit, null, new SelectorConstant(true));
}
@Override
public List<StorageEntry> selectEntries(String unit, Selector selector)
throws IOException
{
return selectEntries(unit, null, selector);
}
@Override
public List<StorageEntry> selectEntries(String unit, List<String> keys)
throws IOException
{
return selectEntries(unit, keys, new SelectorConstant(true));
}
@Override
public List<StorageEntry> selectEntries(
String unit, List<String> keys, Selector selector
) throws IOException
{
List<StorageEntry> entries = new ArrayList<>();
try (
FileReader fr = new FileReader(new File(dir, unit));
BufferedReader br = new BufferedReader(fr);
)
{
String line = br.readLine();
if (line == null)
throw new IOException("Null line.");
String[] tableKeys = line.split(",");
for (int i = 0; i < tableKeys.length; i++)
{
tableKeys[i] = unescapeValue(tableKeys[i]);
}
while ((line = br.readLine()) != null)
{
StringBuilder lineBuilder = new StringBuilder(line);
// Read records spanning multiple lines.
while (!line.endsWith("\""))
{
line = br.readLine();
if (line == null)
throw new IOException("Corrupted CSV file");
lineBuilder.append("\r\n");
lineBuilder.append(line);
}
String[] lineValues =
lineBuilder.toString().split("(?<=\"),(?=\")");
StorageEntry.Builder entryBuilder =
new StorageEntry.Builder();
for (int i = 0; i < lineValues.length; i++)
{
if (keys == null || keys.contains(tableKeys[i]))
{
entryBuilder.put(tableKeys[i],
unescapeValue(lineValues[i]));
}
}
StorageEntry entry = entryBuilder.build();
if (SqlUtils.resolveSelector(selector, entry))
{
entries.add(entry);
}
}
}
return entries;
}
@Override
public void createUnit(String unit, UnitKeys keys, String primaryKey)
throws IOException
{
if (!connected)
throw new IOException("Database closed.");
if (primaryKey != null && !keys.containsKey(primaryKey))
{
throw new IllegalArgumentException(
"Cannot create index on a non-existing key"
);
}
File file = new File(dir, unit);
if (file.exists())
return;
try (BufferedWriter bw = new BufferedWriter(new FileWriter(file)))
{
StringBuilder sb = new StringBuilder();
for (String key : keys.keySet())
{
if (sb.length() > 0)
sb.append(",");
sb.append(escapeValue(key));
}
sb.append("\r\n");
bw.write(sb.toString());
}
}
@Override
public void renameUnit(String unit, String newName) throws IOException
{
if (!connected)
throw new IOException("Database closed.");
new File(dir, unit).renameTo(new File(dir, newName));
}
@Override
public void eraseUnit(String unit) throws IOException
{
if (!connected)
throw new IOException("Database closed.");
String keys;
try (
FileReader fr = new FileReader(new File(dir, unit));
BufferedReader br = new BufferedReader(fr);
)
{
keys = br.readLine();
}
try (
FileWriter fw = new FileWriter(new File(dir, unit));
BufferedWriter bw = new BufferedWriter(fw);
)
{
bw.write(keys + "\r\n");
}
}
@Override
public void removeUnit(String unit) throws IOException
{
if (!connected)
throw new IOException("Database closed.");
new File(dir, unit).delete();
}
@Override
public void addKey(String unit, String key, DataType type)
throws IOException
{
if (!connected)
throw new IOException("Database closed.");
UnitKeys keys = getKeys(unit);
String primaryKey = getPrimaryKey(unit);
if (keys.containsKey(key))
throw new IOException("Key with this name already exists: " + key);
List<StorageEntry> entries = selectEntries(unit);
keys.put(key, type);
removeUnit(unit);
createUnit(unit, keys, primaryKey);
for (StorageEntry entry : entries)
{
addEntry(unit, entry);
}
}
@Override
public void addEntry(String unit, StorageEntry entry) throws IOException
{
if (!connected)
throw new IOException("Database closed.");
UnitKeys keys = getKeys(unit);
try (
FileWriter fw = new FileWriter(new File(dir, unit), true);
BufferedWriter bw = new BufferedWriter(fw);
)
{
StringBuilder sb = new StringBuilder();
for (String key : keys.keySet())
{
if (sb.length() > 0)
{
sb.append(",");
}
String value = entry.get(key);
if (value != null && !value.isEmpty())
{
sb.append(escapeValue(value));
}
else
{
sb.append("\"\"");
}
}
sb.append("\r\n");
bw.write(sb.toString());
}
}
@Override
public void updateEntries(
String unit, StorageEntry entrySubset, Selector selector
) throws IOException
{
if (!connected)
throw new IOException("Database closed.");
UnitKeys keys = getKeys(unit);
String primaryKey = getPrimaryKey(unit);
List<StorageEntry> entries = selectEntries(unit);
removeUnit(unit);
createUnit(unit, keys, primaryKey);
for (StorageEntry entry : entries)
{
if (SqlUtils.resolveSelector(selector, entry))
{
for (StorageDatum datum : entrySubset)
{
entry.put(datum.getKey(), datum.getValue());
}
}
addEntry(unit, entry);
}
}
@Override
public void removeEntries(String unit, Selector selector) throws IOException
{
if (!connected)
throw new IOException("Database closed.");
UnitKeys keys = getKeys(unit);
String primaryKey = getPrimaryKey(unit);
List<StorageEntry> entries = selectEntries(unit);
removeUnit(unit);
createUnit(unit, keys, primaryKey);
for (StorageEntry entry : entries)
{
if (!SqlUtils.resolveSelector(selector, entry))
{
addEntry(unit, entry);
}
}
}
@Override
public boolean isAutobatchEnabled()
{
return false;
}
@Override
public void setAutobatchEnabled(boolean status)
{
// Batching is not supported.
}
@Override
public void executeBatch() throws IOException
{
// Batching is not supported.
}
@Override
public void clearBatch() throws IOException
{
// Batching is not supported.
}
private String escapeValue(String s)
{
s = s.replace(",", "\\,");
return "\"" + s + "\"";
}
private String unescapeValue(String s)
{
if (s == null)
throw new IllegalArgumentException();
s = s.trim();
s = s.replace("\\,", ",");
if (s.startsWith("\""))
{
s = s.substring(1);
}
if (s.endsWith("\""))
{
s = s.substring(0, s.length() - 1);
}
return s;
}
private final File dir;
private boolean connected = false;
}