diff --git a/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/DataBase/DbTable.java b/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/DataBase/DbTable.java new file mode 100644 index 000000000..0734e7c5a --- /dev/null +++ b/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/DataBase/DbTable.java @@ -0,0 +1,462 @@ +package ru.fizteh.fivt.students.PotapovaSofia.parallel.DataBase; + +import ru.fizteh.fivt.storage.structured.Storeable; +import ru.fizteh.fivt.storage.structured.Table; +import ru.fizteh.fivt.storage.structured.TableProvider; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.text.ParseException; +import java.util.*; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.stream.Collectors; + +class ValueWrapper { + private Storeable value; + private boolean isRemoved; + private boolean isContains; + + public ValueWrapper(Storeable value, boolean isRemoved, boolean isContains) { + this.value = value; + this.isRemoved = isRemoved; + this.isContains = isContains; + } + + public Storeable getValue() { + return value; + } + + public void setValue(Storeable value) { + this.value = value; + } + + public boolean isRemoved() { + return isRemoved; + } + + public void setIsRemoved(boolean isRemoved) { + this.isRemoved = isRemoved; + } + + public boolean isContains() { + return isContains; + } + + public void setIsContains(boolean isContains) { + this.isContains = isContains; + } + +} + +public class DbTable implements Table { + private String tableName; + private Path tablePath; + //private Map diff; + private final ThreadLocal> diff = + ThreadLocal.withInitial(()-> new HashMap<>()); + private Map state; + private List> signature; + private TableProvider tableProvider; + + private ReadWriteLock lock = new ReentrantReadWriteLock(true); + + public static final int DIR_COUNT = 16; + public static final int FILE_COUNT = 16; + + public DbTable(final Path tablePath, final String tableName, final TableProvider provider) { + this.tablePath = tablePath; + this.tableName = tableName; + state = new HashMap<>(); + //diff = new HashMap<>(); + signature = new ArrayList<>(); + tableProvider = provider; + try { + readFromDisk(); + } catch (IOException e) { + throw new RuntimeException("Error reading table '" + tableName + "'" + e.getMessage()); + } + } + + public DbTable(final Path tablePath, final String tableName, + final Map records, List> columnTypes, final TableProvider provider) { + this.tablePath = tablePath; + this.tableName = tableName; + state = records; + tableProvider = provider; + //diff = new HashMap<>(); + signature = columnTypes; + } + + @Override + public String getName() { + return tableName; + } + + @Override + public Storeable get(String key) { + checkOnNull(key, null); + Storeable value = null; + lock.readLock().lock(); + try { + if (diff.get().containsKey(key)) { + ValueWrapper valueWrapper = diff.get().get(key); + if (!valueWrapper.isRemoved()) { + value = valueWrapper.getValue(); + } + } else { + value = state.get(key); + } + return value; + } finally { + lock.readLock().unlock(); + } + + } + + @Override + public Storeable put(String key, Storeable value) { + checkOnNull(key, null); + checkOnNull(value, null); + Storeable oldValue = null; + boolean isContains = false; + lock.readLock().lock(); + if (state.containsKey(key)) { + isContains = true; + } + try { + if (diff.get().containsKey(key)) { + ValueWrapper valueWrapper = diff.get().get(key); + if (valueWrapper.isRemoved()) { + valueWrapper.setIsRemoved(false); + } else { + oldValue = valueWrapper.getValue(); + diff.get().remove(key); + diff.get().put(key, new ValueWrapper(value, false, isContains)); + } + } else { + if (state.containsKey(key)) { + oldValue = state.get(key); + } + diff.get().put(key, new ValueWrapper(value, false, isContains)); + } + return oldValue; + } finally { + lock.readLock().unlock(); + } + + } + + @Override + public Storeable remove(String key) { + checkOnNull(key, null); + Storeable removedValue = null; + lock.readLock().lock(); + try { + if (diff.get().containsKey(key)) { + ValueWrapper valueWrapper = diff.get().get(key); + if (!valueWrapper.isRemoved()) { + removedValue = diff.get().get(key).getValue(); + diff.get().remove(key); + if (valueWrapper.isContains()) { + diff.get().put(key, new ValueWrapper(removedValue, true, true)); + } + } + } else { + if (state.containsKey(key)) { + removedValue = state.get(key); + diff.get().put(key, new ValueWrapper(removedValue, true, true)); + } + } + return removedValue; + } finally { + lock.readLock().unlock(); + } + + } + + @Override + public int size() { + int recordsCount = state.size(); + for (Map.Entry f : diff.get().entrySet()) { + ValueWrapper valueWrapper = f.getValue(); + if (valueWrapper.isRemoved()) { + recordsCount--; + } else { + if (!valueWrapper.isContains()) { + recordsCount++; + } + } + } + return recordsCount; + } + + @Override + public int commit() throws RuntimeException { + lock.writeLock().lock(); + try { + int commitCount = diff.get().size(); + for (Map.Entry f : diff.get().entrySet()) { + ValueWrapper valueWrapper = f.getValue(); + if (valueWrapper.isRemoved()) { + state.remove(f.getKey()); + } else { + if (valueWrapper.isContains()) { + state.remove(f.getKey()); + } + state.put(f.getKey(), valueWrapper.getValue()); + } + } + try { + writeToDisk(); + } catch (IOException e) { + throw new RuntimeException("Error writing table " + tableName, e); + } + diff.get().clear(); + return commitCount; + } finally { + lock.writeLock().unlock(); + } + + } + + @Override + public int rollback() { + lock.readLock().lock(); + try { + int diffCount = diff.get().size(); + diff.get().clear(); + return diffCount; + } finally { + lock.readLock().unlock(); + } + } + + @Override + public int getNumberOfUncommittedChanges() { + return diff.get().size(); + } + + @Override + public int getColumnsCount() { + return signature.size(); + } + + @Override + public Class getColumnType(int columnIndex) throws IndexOutOfBoundsException { + return signature.get(columnIndex); + } + + @Override + public List list() { + lock.readLock().lock(); + try { + List list = new LinkedList<>(); + Set keySet = state.entrySet() + .stream() + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + for (Map.Entry f : diff.get().entrySet()) { + ValueWrapper valueWrapper = f.getValue(); + if (valueWrapper.isRemoved()) { + keySet.remove(f.getKey()); + } else { + if (!valueWrapper.isContains()) { + keySet.add(f.getKey()); + } + } + } + list.addAll(keySet); + return list; + } finally { + lock.readLock().unlock(); + } + } + + private void readFromDisk() throws IOException { + Path signaturePath = tablePath.resolve(DbTableProvider.TABLE_SIGNATURE); + if (!signaturePath.toFile().exists()) { + throw new IllegalArgumentException("Table signature file does not exist"); + } + lock.writeLock().lock(); + try { + //read from signature.tsv + try (RandomAccessFile read = new RandomAccessFile(signaturePath.toString(), "r")) { + if (read.length() > 0) { + while (read.getFilePointer() < read.length()) { + String string = read.readLine(); + String[] types = string.trim().split(" "); + int i = 0; + for (String s : types) { + if (DbTableProvider.AVAILABLE_TYPES.containsKey(s)) { + signature.add(i, DbTableProvider.AVAILABLE_TYPES.get(s)); + i += 1; + } else { + throw new IllegalArgumentException("Table signature is invalid"); + } + } + } + } else { + throw new IllegalArgumentException("Table signature is invalid"); + } + } + //read from files + state = new HashMap<>(); + try { + for (int dir = 0; dir < DIR_COUNT; ++dir) { + for (int file = 0; file < FILE_COUNT; ++file) { + Path filePath = tablePath.resolve(dir + ".dir").resolve(file + ".dat"); + if (Files.exists(filePath)) { + DataInputStream in = new DataInputStream(Files.newInputStream(filePath)); + while (true) { + try { + String key = readWord(in); + String value = readWord(in); + state.put(key, tableProvider.deserialize(this, value)); + } catch (EOFException e) { + break; + } catch (ParseException e) { + e.printStackTrace(); + } + } + } + } + } + } catch (UnsupportedEncodingException e) { + throw new IOException("Key or value can't be encoded to " + DbTableProvider.ENCODING, e); + } catch (IllegalArgumentException e) { + throw new IOException("Key is in a wrong file or directory", e); + } catch (EOFException e) { + throw new IOException("File breaks unexpectedly", e); + } catch (IOException e) { + throw new IOException("Unable read from file", e); + } + //check on right directory content + boolean signatureExists = false; + for (File directory : tablePath.toFile().listFiles()) { + int k = directory.getName().indexOf('.'); + if ((k < 0) || !((directory.getName().endsWith(".dir")) || (directory.getName().endsWith(".tsv")))) { + throw new IllegalArgumentException("Table subdirectories don't have appropriate name"); + } + if (directory.getName().endsWith(".dir")) { + File[] directoryFiles = directory.listFiles(); + if (directory.list().length == 0) { + throw new IllegalArgumentException("Table has the wrong format"); + } + for (File file : directoryFiles) { + k = file.getName().indexOf('.'); + if ((k < 0) || !(file.getName().endsWith(".dat"))) { + throw new IllegalArgumentException("Table subdirectory's files" + + "doesn't have appropriate name"); + } + if (file.length() == 0) { + throw new IllegalArgumentException("Table file '" + file.toString() + "' is empty"); + } + } + } else { + if (signatureExists) { + throw new IllegalArgumentException(directory.getName() + " must not exist"); + } else { + signatureExists = true; + } + } + } + } finally { + lock.writeLock().unlock(); + } + + } + + private String readWord(DataInputStream in) throws IOException { + int length = in.readInt(); + byte[] word = new byte[length]; + in.read(word, 0, length); + return new String(word, DbTableProvider.ENCODING); + } + + private void writeToDisk() throws IOException { + if (!Files.exists(tablePath)) { + String tableToCreatePath = tablePath.toString(); + File tableDirectory = new File(tableToCreatePath); + if (!tableDirectory.mkdir()) { + throw new IOException("Can't create table directory"); + } + } + Map[][] db = new Map[DIR_COUNT][FILE_COUNT]; + for (int i = 0; i < DIR_COUNT; i++) { + for (int j = 0; j < FILE_COUNT; j++) { + db[i][j] = new HashMap<>(); + } + } + for (Map.Entry entry : state.entrySet()) { + String key = entry.getKey(); + Storeable value = entry.getValue(); + Integer hashCode = Math.abs(key.hashCode()); + Integer dir = hashCode % DIR_COUNT; + Integer file = hashCode / DIR_COUNT % FILE_COUNT; + db[dir][file].put(key, value); + } + for (int i = 0; i < DIR_COUNT; i++) { + for (int j = 0; j < FILE_COUNT; j++) { + if (!db[i][j].isEmpty()) { + Path dirPath = tablePath.resolve(i + ".dir"); + String newPath = dirPath.toString(); + File directory = new File(newPath); + if (!directory.exists()) { + if (!directory.mkdir()) { + throw new IOException("Cannot create directory"); + } + } + String newFilePath = dirPath.resolve(j + ".dat").toString(); + File file = new File(newFilePath); + if (!file.exists()) { + if (!file.createNewFile()) { + throw new IOException("Cannot create file"); + } + } + DataOutputStream out = new DataOutputStream(new FileOutputStream(newFilePath)); + for (Map.Entry entry : db[i][j].entrySet()) { + String key = entry.getKey(); + String value = tableProvider.serialize(this, entry.getValue()); + writeWord(out, key); + writeWord(out, value); + } + out.close(); + } else { + Path dirPath = tablePath.resolve(i + ".dir"); + String newPath = dirPath.toString(); + File directory = new File(newPath); + if (directory.exists()) { + String newFilePath = dirPath.resolve(j + ".dat").toString(); + File file = new File(newFilePath); + Files.deleteIfExists(file.toPath()); + } + } + } + } + File pathDirectory = tablePath.toFile(); + File[] tableDirectories = pathDirectory.listFiles(); + for (File directory: tableDirectories) { + if (directory.getName().endsWith(".dir")) { + File[] directoryFiles = directory.listFiles(); + if (directoryFiles.length == 0) { + Files.delete(directory.toPath()); + } + } + } + } + + private void writeWord(DataOutputStream out, String word) throws IOException { + byte[] byteWord = word.getBytes(DbTableProvider.ENCODING); + out.writeInt(byteWord.length); + out.write(byteWord); + out.flush(); + } + + private static void checkOnNull(Object name, String msg) { + if (name == null) { + throw new IllegalArgumentException(msg); + } + } +} diff --git a/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/DataBase/DbTableProvider.java b/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/DataBase/DbTableProvider.java new file mode 100644 index 000000000..563d43ea3 --- /dev/null +++ b/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/DataBase/DbTableProvider.java @@ -0,0 +1,295 @@ +package ru.fizteh.fivt.students.PotapovaSofia.parallel.DataBase; + +import ru.fizteh.fivt.storage.structured.Table; +import ru.fizteh.fivt.storage.structured.TableProvider; +import ru.fizteh.fivt.storage.structured.ColumnFormatException; +import ru.fizteh.fivt.storage.structured.Storeable; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.file.*; +import java.text.ParseException; +import java.util.*; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Function; + +public class DbTableProvider implements TableProvider { + + public static final String ILLEGAL_TABLE_NAME_PATTERN = ".*\\.|\\..*|.*(/|\\\\).*"; + public static final String IGNORE_SYMBOLS_IN_DOUBLE_QUOTES_REGEX = "(?=([^\"]*\"[^\"]*\")*[^\"]*$)"; + public static final String ENCODING = "UTF-8"; + public static final String TABLE_SIGNATURE = "signature.tsv"; + + public static final Map AVAILABLE_TYPES = new HashMap<>(); + public static final Map AVAILABLE_CLASSES = new HashMap<>(); + public static final Map, Function> PARSE_TYPES; + + static { + String[] primitiveNames = new String[]{"int", "long", "byte", "float", "double", "boolean", "String"}; + Class[] classes = new Class[]{Integer.class, Long.class, Byte.class, Float.class, Double.class, + Boolean.class, String.class}; + + for (int i = 0; i < primitiveNames.length; i++) { + AVAILABLE_TYPES.put(primitiveNames[i], classes[i]); + AVAILABLE_CLASSES.put(classes[i], primitiveNames[i]); + } + } + + static { + Map, Function> unitializerMap = new HashMap<>(); + unitializerMap.put(Integer.class, Integer::parseInt); + unitializerMap.put(Long.class, Long::parseLong); + unitializerMap.put(Byte.class, Byte::parseByte); + unitializerMap.put(Float.class, Float::parseFloat); + unitializerMap.put(Double.class, Double::parseDouble); + unitializerMap.put(Boolean.class, string -> { + if (!string.matches("(?i)true|false")) { + throw new ColumnFormatException("Expected 'true' or 'false'"); + } + return Boolean.parseBoolean(string); + }); + unitializerMap.put(String.class, string -> { + if (string.charAt(0) != '"' || string.charAt(string.length() - 1) != '"') { + throw new ColumnFormatException("String must be quoted"); + } + return string.substring(1, string.length() - 1); + }); + PARSE_TYPES = Collections.unmodifiableMap(unitializerMap); + } + + private Map tables; + private Path dbPath; + private final ReadWriteLock lock = new ReentrantReadWriteLock(true); + + public DbTableProvider(String dir) { + try { + dbPath = Paths.get(dir); + if (!dbPath.toFile().exists()) { + dbPath.toFile().mkdir(); + } + if (!dbPath.toFile().isDirectory()) { + throw new IllegalArgumentException("Error connecting database" + + ": path is incorrect or does not lead to a directory"); + } + } catch (InvalidPathException e) { + throw new IllegalArgumentException("Error connecting database: '" + + dir + "' is illegal directory name", e); + } + tables = new TreeMap<>(); + + String[] tablesDirlist = dbPath.toFile().list(); + for (String curTableDir : tablesDirlist) { + Path curTableDirPath = dbPath.resolve(curTableDir); + if (curTableDirPath.toFile().isDirectory()) { + Table curTable = new DbTable(curTableDirPath, curTableDir, this); + tables.put(curTableDir, curTable); + } else { + throw new IllegalArgumentException("Error connecting database" + + ": root directory contains non-directory files"); + } + } + } + + @Override + public Table getTable(String name) { + checkTableName(name); + lock.readLock().lock(); + try { + if (tables.containsKey(name)) { + if (tables.get(name) == null) { + throw new IllegalStateException("Table was removed"); + } + return tables.get(name); + } else { + return null; + } + } finally { + lock.readLock().unlock(); + } + } + + @Override + public Table createTable(String name, List> columnTypes) throws IOException { + checkTableName(name); + lock.writeLock().lock(); + try { + //check types on available + if (columnTypes == null) { + throw new IllegalArgumentException("Wrong type (column type is null)"); + } + + for (Class columnType : columnTypes) { + if (!AVAILABLE_CLASSES.containsKey(columnType)) { + throw new IllegalArgumentException("Wrong type (" + columnType.toString() + ")"); + } + } + + if (tables.get(name) != null) { + return null; + } + //creating directory + Path newTablePath = dbPath.resolve(name); + newTablePath.toFile().mkdir(); + Table newTable = new DbTable(newTablePath, name, new HashMap<>(), columnTypes, this); + Path signaturePath = newTablePath.resolve(TABLE_SIGNATURE); + signaturePath.toFile().createNewFile(); + try (RandomAccessFile write = new RandomAccessFile(signaturePath.toString(), "rw")) { + for (Class type : columnTypes) { + String s = AVAILABLE_CLASSES.get(type) + " "; + write.write(s.getBytes(ENCODING)); + } + } + tables.put(name, newTable); + return newTable; + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public void removeTable(String name) throws IOException { + checkTableName(name); + lock.writeLock().lock(); + try { + if (tables.get(name) == null) { + throw new IllegalStateException(name + " doesn't exist"); + } else { + Path tableDir = dbPath.resolve(name); + Table removedTable = tables.remove(name); + if (removedTable == null) { + throw new IllegalStateException("There is no such table"); + } else { + recoursiveDelete(tableDir.toFile()); + } + } + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public Storeable deserialize(Table table, String value) throws ParseException { + value = value.trim(); + if ((value.charAt(0) != '[') || (value.charAt(value.length() - 1) != ']')) { + throw new ParseException("'[' or ']' is missing", 0); + } + value = value.substring(1, value.length() - 1); + String[] records = value.split("," + IGNORE_SYMBOLS_IN_DOUBLE_QUOTES_REGEX); + if (records.length != table.getColumnsCount()) { + throw new ParseException("Incorrect number of records: " + + table.getColumnsCount() + " expected, but " + records.length + " found.", 0); + } + Storeable deserialized = createFor(table); + for (int i = 0; i < records.length; i++) { + String record = records[i].trim(); + try { + if (record.equals("null")) { + deserialized.setColumnAt(i, null); + } else { + deserialized.setColumnAt(i, PARSE_TYPES.get(table.getColumnType(i)).apply(record)); + } + } catch (ColumnFormatException e) { + throw new ParseException("Record " + i + " is not correct: " + e.getMessage(), 0); + } catch (NumberFormatException e) { + throw new ParseException("Record " + i + " is not correct: " + record + " is not " + + table.getColumnType(i).getSimpleName(), 0); + } + } + return deserialized; + } + + @Override + public String serialize(Table table, Storeable value) { + List storeableValues = getStoreableValues(table, value); + String joined = String.join(", ", storeableValues); + return "[" + joined + "]"; + } + + @Override + public Storeable createFor(Table table) { + int size = table.getColumnsCount(); + List> types = new ArrayList<>(); + for (int i = 0; i < size; i++) { + types.add(table.getColumnType(i)); + } + return new StoreableImpl(types); + } + + @Override + public Storeable createFor(Table table, List values) { + Storeable record = createFor(table); + int size = table.getColumnsCount(); + if (values.size() != size) { + throw new IndexOutOfBoundsException("wrong amount of columns"); + } else { + for (int i = 0; i < size; i++) { + Object currentValue = values.get(i); + record.setColumnAt(i, currentValue); + } + return record; + } + } + + @Override + public List getTableNames() { + List tableNames = new ArrayList<>(); + for (Map.Entry entry : tables.entrySet()) { + if (!(entry.getValue() == null)) { + tableNames.add(entry.getKey()); + } + } + return tableNames; + } + + private static void checkTableName(final String name) { + if (name == null || name.matches(ILLEGAL_TABLE_NAME_PATTERN)) { + throw new IllegalArgumentException("Invalid format: " + name); + } + } + + public static List getStoreableValues(Table table, Storeable value) { + int columnsCount = table.getColumnsCount(); + List storeableValues = new ArrayList<>(); + Method[] methods = value.getClass().getDeclaredMethods(); + Map, Method> getMethods = new HashMap<>(); + for (Method method: methods) { + getMethods.put(method.getReturnType(), method); + } + for (int i = 0; i < columnsCount; i++) { + + Class currentColumnType = table.getColumnType(i); + try { + Object columnValue = getMethods.get(currentColumnType).invoke(value, i); + if (columnValue == null) { + storeableValues.add("null"); + } else { + if (columnValue.getClass().equals(String.class)) { + storeableValues.add('"' + columnValue.toString() + '"'); + } else { + storeableValues.add(columnValue.toString()); + } + } + } catch (InvocationTargetException e) { + throw new ColumnFormatException(e.getTargetException().getMessage()); + } catch (IllegalAccessException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + } + return storeableValues; + } + + public static void recoursiveDelete(File file) throws IOException { + if (file.isDirectory()) { + for (File currentFile : file.listFiles()) { + recoursiveDelete(currentFile); + } + } + if (!file.delete()) { + throw new IOException("Unable to delete: " + file); + } + } +} diff --git a/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/DataBase/DbTableProviderFactory.java b/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/DataBase/DbTableProviderFactory.java new file mode 100644 index 000000000..d01d75be1 --- /dev/null +++ b/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/DataBase/DbTableProviderFactory.java @@ -0,0 +1,18 @@ +package ru.fizteh.fivt.students.PotapovaSofia.parallel.DataBase; + +import ru.fizteh.fivt.storage.structured.TableProvider; +import ru.fizteh.fivt.storage.structured.TableProviderFactory; +import java.io.IOException; + +public class DbTableProviderFactory implements TableProviderFactory{ + public DbTableProviderFactory() { + + } + @Override + public TableProvider create(String dir) throws IOException { + if (dir == null) { + throw new IllegalArgumentException("Directory name is null"); + } + return new DbTableProvider(dir); + } +} diff --git a/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/DataBase/StoreableImpl.java b/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/DataBase/StoreableImpl.java new file mode 100644 index 000000000..ad7ea6b3a --- /dev/null +++ b/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/DataBase/StoreableImpl.java @@ -0,0 +1,88 @@ +package ru.fizteh.fivt.students.PotapovaSofia.parallel.DataBase; + +import ru.fizteh.fivt.storage.structured.ColumnFormatException; +import ru.fizteh.fivt.storage.structured.Storeable; + +import java.util.ArrayList; +import java.util.List; + +public class StoreableImpl implements Storeable{ + private List> signature; + private List storage; + + public StoreableImpl(List> signature) { + storage = new ArrayList<>(signature.size()); + this.signature = new ArrayList<>(signature); + } + + @Override + public void setColumnAt(int columnIndex, Object value) throws ColumnFormatException, IndexOutOfBoundsException { + if (columnIndex < 0 || columnIndex >= signature.size()) { + throw new IndexOutOfBoundsException(); + } + if (value != null) { + checkColumnFormat(columnIndex, value.getClass()); + } + storage.add(columnIndex, value); + } + + @Override + public Object getColumnAt(int columnIndex) throws IndexOutOfBoundsException { + if (columnIndex < 0 || columnIndex >= signature.size()) { + throw new IndexOutOfBoundsException(); + } + return storage.get(columnIndex); + } + + @Override + public Integer getIntAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException { + return getCastType(columnIndex, Integer.class); + } + + @Override + public Long getLongAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException { + return getCastType(columnIndex, Long.class); + } + + @Override + public Byte getByteAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException { + return getCastType(columnIndex, Byte.class); + } + + @Override + public Float getFloatAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException { + return getCastType(columnIndex, Float.class); + } + + @Override + public Double getDoubleAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException { + return getCastType(columnIndex, Double.class); + } + + @Override + public Boolean getBooleanAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException { + return getCastType(columnIndex, Boolean.class); + } + + @Override + public String getStringAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException { + return getCastType(columnIndex, String.class); + } + + private void checkColumnFormat(int columnIndex, Class classType) { + if (signature.get(columnIndex) != classType) { + throw new ColumnFormatException("Expected: " + signature.get(columnIndex).getSimpleName() + + "; found: " + classType.getSimpleName()); + } + } + + private T getCastType(int columnIndex, Class type) { + Object value = getColumnAt(columnIndex); + checkColumnFormat(columnIndex, type); + if (value != null) { + return type.cast(value); + } else { + return null; + } + } +} diff --git a/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/Interpreter/Command.java b/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/Interpreter/Command.java new file mode 100644 index 000000000..c8b5a6a91 --- /dev/null +++ b/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/Interpreter/Command.java @@ -0,0 +1,28 @@ +package ru.fizteh.fivt.students.PotapovaSofia.parallel.Interpreter; + +import java.util.function.BiConsumer; + +public class Command { + private String name; + private int numArgs; + private BiConsumer callback; + + public Command(String name, int numArgs, BiConsumer callback) { + this.name = name; + this.numArgs = numArgs; + this.callback = callback; + } + + public String getName() { + return name; + } + + public void execute(Object state, String[] params) { + if (params.length != numArgs) { + System.out.println("Invalid number of arguments: " + numArgs + " expected, " + params.length + + " found."); + } else { + callback.accept(state, params); + } + } +} diff --git a/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/Interpreter/Interpreter.java b/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/Interpreter/Interpreter.java new file mode 100644 index 000000000..34cb49980 --- /dev/null +++ b/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/Interpreter/Interpreter.java @@ -0,0 +1,114 @@ +package ru.fizteh.fivt.students.PotapovaSofia.parallel.Interpreter; + +import java.io.InputStream; +import java.io.PrintStream; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +public class Interpreter { + public static final String PROMPT = "$ "; + public static final String STATEMENT_DELIMITER = ";"; + public static final String SPLIT_BY_SPACES_NOT_IN_BRACKETS_REGEX = "\\s*(\".*\"|\\(.*\\)|\\[.*\\]|[^\\s]+)\\s*"; + public static final String IGNORE_SYMBOLS_IN_DOUBLE_QUOTES_REGEX = "(?=([^\"]*\"[^\"]*\")*[^\"]*$)"; + + private InputStream in; + private PrintStream out; + + private final Map commands; + private final Object state; + + public Interpreter(Object state, Command[] commands, InputStream in, PrintStream out) { + if (in == null || out == null) { + throw new IllegalArgumentException("Input or Output stream is null"); + } + this.in = in; + this.out = out; + this.state = state; + this.commands = new HashMap<>(); + for (Command command : commands) { + this.commands.put(command.getName(), command); + } + } + + public Interpreter(Object state, Command[] commands) { + this.in = System.in; + this.out = System.out; + this.state = state; + this.commands = new HashMap<>(); + for (Command command : commands) { + this.commands.put(command.getName(), command); + } + } + + public void run(String[] args) { + try { + if (args.length == 0) { + runInteractiveMode(); + } else { + runBatchMode(args); + } + } catch (StopInterpretationException e) { + // Just stop the interpretation. + } + } + + private void runBatchMode(String[] args) throws StopInterpretationException { + executeLine(String.join(" ", args)); + } + + private void runInteractiveMode() throws StopInterpretationException { + Scanner in = new Scanner(this.in); + while (true) { + out.print(PROMPT); + try { + String line = in.nextLine(); + executeLine(line); + } catch (NoSuchElementException e) { + break; + } + } + } + + private int executeLine(String line) throws StopInterpretationException { + String[] cmds = line.split(STATEMENT_DELIMITER + IGNORE_SYMBOLS_IN_DOUBLE_QUOTES_REGEX); + Pattern p = Pattern.compile(SPLIT_BY_SPACES_NOT_IN_BRACKETS_REGEX); + List tokens = new LinkedList<>(); + for (String current : cmds) { + tokens.clear(); + Matcher m = p.matcher(current.trim()); + while (m.find()) { + tokens.add(m.group().trim()); + } + parse(tokens.toArray(new String[tokens.size()])); + } + return 0; + } + + private void parse(String[] cmdWithArgs) throws StopInterpretationException { + if (cmdWithArgs.length > 0 && !cmdWithArgs[0].isEmpty()) { + String commandName = cmdWithArgs[0]; + /* + if (commandName.equals("exit")) { + exit(this.state); + } + */ + Command command = commands.get(commandName); + if (command == null) { + out.println("Wrong command: " + commandName); + } else { + String[] args = new String[cmdWithArgs.length - 1]; + for (int i = 1; i < cmdWithArgs.length; i++) { + if (cmdWithArgs[i].charAt(0) == '"' + && cmdWithArgs[i].charAt(cmdWithArgs[i].length() - 1) == '"') { + args[i - 1] = cmdWithArgs[i].substring(1, cmdWithArgs[i].length() - 1); + } else { + args[i - 1] = cmdWithArgs[i]; + } + } + command.execute(this.state, args); + } + } + } +} diff --git a/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/Interpreter/StopInterpretationException.java b/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/Interpreter/StopInterpretationException.java new file mode 100644 index 000000000..0708033ef --- /dev/null +++ b/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/Interpreter/StopInterpretationException.java @@ -0,0 +1,4 @@ +package ru.fizteh.fivt.students.PotapovaSofia.parallel.Interpreter; + +public class StopInterpretationException extends Exception { +} diff --git a/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/ParallelMain.java b/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/ParallelMain.java new file mode 100644 index 000000000..f5e99e5d1 --- /dev/null +++ b/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/ParallelMain.java @@ -0,0 +1,204 @@ +package ru.fizteh.fivt.students.PotapovaSofia.parallel; + +import ru.fizteh.fivt.storage.structured.*; +import ru.fizteh.fivt.students.PotapovaSofia.parallel.DataBase.DbTable; +import ru.fizteh.fivt.students.PotapovaSofia.parallel.DataBase.DbTableProvider; +import ru.fizteh.fivt.students.PotapovaSofia.parallel.DataBase.DbTableProviderFactory; +import ru.fizteh.fivt.students.PotapovaSofia.parallel.Interpreter.Command; +import ru.fizteh.fivt.students.PotapovaSofia.parallel.Interpreter.Interpreter; + +import java.io.IOException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; + +public class ParallelMain { + + public static void main(String[] args) throws IOException { + String pathName = System.getProperty("fizteh.db.dir"); + if (pathName == null) { + System.err.println("You must specify db.file via -Ddb.file JVM parameter"); + System.exit(1); + } + TableProviderFactory factory = new DbTableProviderFactory(); + TableState state = new TableState(factory.create(pathName)); + + start(state, args); + } + + private static void start(TableState state, String[] args) { + Interpreter dbInterpreter = new Interpreter(state, new Command[]{ + new Command("put", 2, (state1, args1) -> { + TableProvider tableProvider = ((TableState) state1).getTableProvider(); + Table currentTable = ((TableState) state1).getUsedTable(); + if (currentTable != null) { + try { + Storeable oldValue = currentTable.put(args1[0], + tableProvider.deserialize(currentTable, args1[1])); + if (oldValue != null) { + System.out.println("overwrite"); + System.out.println(tableProvider.serialize(currentTable, oldValue)); + } else { + System.out.println("new"); + } + } catch (ColumnFormatException | ParseException e) { + System.out.println("wrong type: (" + e.getMessage() + ")"); + } + } else { + System.out.println("no table"); + } + }), + new Command("get", 1, (state1, args1) -> { + TableProvider tableProvider = ((TableState) state1).getTableProvider(); + Table currentTable = ((TableState) state1).getUsedTable(); + if (currentTable != null) { + Storeable value = currentTable.get(args1[0]); + if (value != null) { + System.out.println("found"); + System.out.println(tableProvider.serialize(currentTable, value)); + } else { + System.out.println("not found"); + } + } else { + System.out.println("no table"); + } + }), + new Command("remove", 1, (state1, args1) -> { + Table currentTable = ((TableState) state1).getUsedTable(); + if (currentTable != null) { + Storeable removedValue = currentTable.remove(args1[0]); + if (removedValue != null) { + System.out.println("removed"); + } else { + System.out.println("not found"); + } + } else { + System.out.println("no table"); + } + }), + new Command("list", 0, (state1, args1) -> { + Table currentTable = ((TableState) state1).getUsedTable(); + if (currentTable != null) { + System.out.println(String.join(", ", currentTable.list())); + } else { + System.out.println("no table"); + } + }), + new Command("size", 0, (state1, args1) -> { + Table currentTable = ((TableState) state1).getUsedTable(); + if (currentTable != null) { + System.out.println(currentTable.size()); + } else { + System.out.println("no table"); + } + }), + new Command("commit", 0, (state1, args1) -> { + Table currentTable = ((TableState) state1).getUsedTable(); + if (currentTable != null) { + try { + System.out.println(currentTable.commit()); + } catch (Exception e) { + System.out.println(e.getMessage()); + System.exit(1); + } + } else { + System.out.println("no table"); + } + }), + new Command("rollback", 0, (state1, args1) -> { + Table currentTable = ((TableState) state1).getUsedTable(); + if (currentTable != null) { + System.out.println(currentTable.rollback()); + } else { + System.out.println("no table"); + } + }), + new Command("create", 2, (state1, args1) -> { + String[] types = args1[1].substring(1, args1[1].length() - 1).split(" "); + List> typesList = new ArrayList<>(); + boolean isCreating = true; + for (String type : types) { + Class typeClass = DbTableProvider.AVAILABLE_TYPES.get(type); + if (typeClass != null) { + typesList.add(typeClass); + } else { + System.out.println("wrong type (" + type + " is not avaliable type)"); + isCreating = false; + } + } + if (isCreating) { + TableProvider tableProvider = ((TableState) state1).getTableProvider(); + try { + if (tableProvider.createTable(args1[0], typesList) != null) { + System.out.println("created"); + } else { + System.out.println(args1[0] + " exists"); + } + } catch (IOException e) { + System.out.println(e.getMessage()); + } + } + }), + new Command("use", 1, (state1, args1) -> { + TableState dbState = ((TableState) state1); + TableProvider tableProvider = dbState.getTableProvider(); + Table newTable = tableProvider.getTable(args1[0]); + DbTable usedTable = (DbTable) dbState.getUsedTable(); + if (newTable != null) { + if (usedTable != null && (usedTable.getNumberOfUncommittedChanges() > 0) + && (usedTable != newTable)) { + System.out.println(usedTable.getNumberOfUncommittedChanges() + " unsaved changes"); + } else { + dbState.setUsedTable(newTable); + System.out.println("using " + args1[0]); + } + } else { + System.out.println(args1[0] + " not exists"); + } + }), + new Command("drop", 1, (state1, args1) -> { + TableProvider tableProvider = ((TableState) state1).getTableProvider(); + Table currentTable = ((TableState) state1).getUsedTable(); + if (currentTable != null && currentTable.getName().equals(args1[0])) { + ((TableState) state1).setUsedTable(null); + } + try { + tableProvider.removeTable(args1[0]); + System.out.println("dropped"); + } catch (IllegalStateException e) { + System.out.println(args1[0] + " not exists"); + } catch (IOException e) { + System.out.println(e.getMessage()); + } + }), + new Command("show", 1, (state1, args1) -> { + if (args1[0].equals("tables")) { + TableProvider tableProvider = ((TableState) state1).getTableProvider(); + List tableNames = tableProvider.getTableNames(); + System.out.println("table_name row_count"); + for (String name : tableNames) { + Table curTable = tableProvider.getTable(name); + System.out.println(curTable.getName() + " " + curTable.size()); + } + } else { + System.out.println("Wrong command: " + args1[0]); + } + }), + new Command("exit", 0, (state1, args1) -> { + if (state1 != null) { + TableState dbState = ((TableState) state1); + DbTable usedTable = (DbTable) dbState.getUsedTable(); + if (usedTable != null && (usedTable.getNumberOfUncommittedChanges() > 0)) { + System.out.println(usedTable.getNumberOfUncommittedChanges() + " unsaved changes"); + } else { + System.exit(0); + } + } else { + System.exit(0); + } + }) + }); + dbInterpreter.run(args); + } +} + diff --git a/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/TableState.java b/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/TableState.java new file mode 100644 index 000000000..958ace14e --- /dev/null +++ b/src/ru/fizteh/fivt/students/PotapovaSofia/parallel/TableState.java @@ -0,0 +1,26 @@ +package ru.fizteh.fivt.students.PotapovaSofia.parallel; + +import ru.fizteh.fivt.storage.structured.Table; +import ru.fizteh.fivt.storage.structured.TableProvider; + +public class TableState { + private TableProvider tableProvider; + private Table usedTable; + + public TableState(TableProvider tableProvider) { + this.tableProvider = tableProvider; + usedTable = null; + } + + public void setUsedTable(Table newUsedTable) { + usedTable = newUsedTable; + } + + public Table getUsedTable() { + return usedTable; + } + + public TableProvider getTableProvider() { + return tableProvider; + } +}