diff --git a/src/ru/fizteh/fivt/students/gudkov394/telnet/RemoteTableProviderFactoryClass.java b/src/ru/fizteh/fivt/students/gudkov394/telnet/RemoteTableProviderFactoryClass.java deleted file mode 100644 index 9653fc720..000000000 --- a/src/ru/fizteh/fivt/students/gudkov394/telnet/RemoteTableProviderFactoryClass.java +++ /dev/null @@ -1,13 +0,0 @@ -package ru.fizteh.fivt.students.gudkov394.telnet; -import ru.fizteh.fivt.storage.structured.RemoteTableProvider; -import ru.fizteh.fivt.storage.structured.RemoteTableProviderFactory; - -import java.io.IOException; - -public class RemoteTableProviderFactoryClass implements RemoteTableProviderFactory { - - @Override - public RemoteTableProvider connect(String hostname, int port) throws IOException { - return null; - } -} diff --git a/src/ru/fizteh/fivt/students/pavel_voropaev/project/commands/database/Drop.java b/src/ru/fizteh/fivt/students/pavel_voropaev/project/commands/database/Drop.java index edbea21bf..dd75aeb51 100644 --- a/src/ru/fizteh/fivt/students/pavel_voropaev/project/commands/database/Drop.java +++ b/src/ru/fizteh/fivt/students/pavel_voropaev/project/commands/database/Drop.java @@ -3,7 +3,7 @@ import ru.fizteh.fivt.storage.structured.Table; import ru.fizteh.fivt.students.pavel_voropaev.project.commands.DatabaseAbstractCommand; import ru.fizteh.fivt.students.pavel_voropaev.project.commands.DatabaseInterpreterState; -import ru.fizteh.fivt.students.pavel_voropaev.project.custom_exceptions.TableDoesNotExistException; +import ru.fizteh.fivt.students.pavel_voropaev.project.custom_exceptions.ObjectDoesNotExistException; import java.io.IOException; @@ -22,7 +22,7 @@ public void exec(String[] param) throws IOException { try { state.getDatabase().removeTable(param[0]); - } catch (TableDoesNotExistException e) { + } catch (ObjectDoesNotExistException e) { state.getOutputStream().println(param[0] + " not exists"); return; } diff --git a/src/ru/fizteh/fivt/students/pavel_voropaev/project/commands/table/Get.java b/src/ru/fizteh/fivt/students/pavel_voropaev/project/commands/table/Get.java index 7fd5dd426..14c0bee29 100644 --- a/src/ru/fizteh/fivt/students/pavel_voropaev/project/commands/table/Get.java +++ b/src/ru/fizteh/fivt/students/pavel_voropaev/project/commands/table/Get.java @@ -26,7 +26,7 @@ public void exec(String[] param) { out.println("not found"); } else { out.println("found"); - out.println("(" + Serializer.serialize(table, storeable, ' ', '\"') + ")"); + out.println("(" + Serializer.serialize(storeable, " ", "\"") + ")"); } } } diff --git a/src/ru/fizteh/fivt/students/pavel_voropaev/project/commands/table/Put.java b/src/ru/fizteh/fivt/students/pavel_voropaev/project/commands/table/Put.java index 5ea671464..484758fa8 100644 --- a/src/ru/fizteh/fivt/students/pavel_voropaev/project/commands/table/Put.java +++ b/src/ru/fizteh/fivt/students/pavel_voropaev/project/commands/table/Put.java @@ -52,7 +52,7 @@ public void exec(String[] param) { out.println("new"); } else { out.println("overwrite"); - out.println('(' + Serializer.serialize(table, value, ' ', '\"') + ')'); + out.println('(' + Serializer.serialize(value, " ", "\"") + ')'); } } } diff --git a/src/ru/fizteh/fivt/students/pavel_voropaev/project/custom_exceptions/ObjectDoesNotExistException.java b/src/ru/fizteh/fivt/students/pavel_voropaev/project/custom_exceptions/ObjectDoesNotExistException.java new file mode 100644 index 000000000..97674d679 --- /dev/null +++ b/src/ru/fizteh/fivt/students/pavel_voropaev/project/custom_exceptions/ObjectDoesNotExistException.java @@ -0,0 +1,8 @@ +package ru.fizteh.fivt.students.pavel_voropaev.project.custom_exceptions; + +public class ObjectDoesNotExistException extends IllegalStateException { + + public ObjectDoesNotExistException(String type, String name) { + super(type + " " + name + " not exists"); + } +} diff --git a/src/ru/fizteh/fivt/students/pavel_voropaev/project/custom_exceptions/TableDoesNotExistException.java b/src/ru/fizteh/fivt/students/pavel_voropaev/project/custom_exceptions/TableDoesNotExistException.java deleted file mode 100644 index 32a017c5b..000000000 --- a/src/ru/fizteh/fivt/students/pavel_voropaev/project/custom_exceptions/TableDoesNotExistException.java +++ /dev/null @@ -1,8 +0,0 @@ -package ru.fizteh.fivt.students.pavel_voropaev.project.custom_exceptions; - -public class TableDoesNotExistException extends IllegalStateException { - - public TableDoesNotExistException(String string) { - super("No table: " + string); - } -} diff --git a/src/ru/fizteh/fivt/students/pavel_voropaev/project/database/Database.java b/src/ru/fizteh/fivt/students/pavel_voropaev/project/database/Database.java index 81e4ba3f3..2a960ec7c 100644 --- a/src/ru/fizteh/fivt/students/pavel_voropaev/project/database/Database.java +++ b/src/ru/fizteh/fivt/students/pavel_voropaev/project/database/Database.java @@ -19,12 +19,20 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; -public class Database implements TableProvider { - private Map tables; +public class Database implements TableProvider, AutoCloseable { + private Map tables; private Path databasePath; + private ReadWriteLock lock; + private AtomicBoolean wasClosed; public Database(String dbPath) { + wasClosed = new AtomicBoolean(false); + lock = new ReentrantReadWriteLock(); + try { databasePath = Paths.get(dbPath); if (!Files.exists(databasePath)) { @@ -44,7 +52,7 @@ public Database(String dbPath) { throw new ContainsWrongFilesException(databasePath.toString()); } - Table currentTable = new MultiFileTable(databasePath, entry.getFileName().toString(), this); + MultiFileTable currentTable = new MultiFileTable(databasePath, entry.getFileName().toString(), this); tables.put(entry.getFileName().toString(), currentTable); } } catch (IOException e) { @@ -54,15 +62,23 @@ public Database(String dbPath) { @Override public Table getTable(String name) { - if (!isNameCorrect(name)) { - throw new InputMistakeException("Illegal table name: " + name); + lock.readLock().lock(); + try { + checkClosed(); + if (!isNameCorrect(name)) { + throw new InputMistakeException("Illegal table name: " + name); + } + return tables.get(name); + } finally { + lock.readLock().unlock(); } - return tables.get(name); } @Override public Table createTable(String name, List> columnTypes) { + lock.writeLock().lock(); try { + checkClosed(); if (getTable(name) != null) { return null; } @@ -95,33 +111,40 @@ public Table createTable(String name, List> columnTypes) { throw new IOException("Cannot write table signature", e); } - Table newTable = new MultiFileTable(databasePath, name, this); + MultiFileTable newTable = new MultiFileTable(databasePath, name, this); tables.put(name, newTable); return newTable; } catch (IOException e) { throw new InputMistakeException("Cannot create table: " + e.getMessage()); + } finally { + lock.writeLock().unlock(); } } @Override public void removeTable(String name) { - if (getTable(name) == null) { - throw new TableDoesNotExistException(name); - } - - MultiFileTable table = (MultiFileTable) tables.get(name); - table.destroy(); - - tables.remove(name); + lock.writeLock().lock(); try { - Utils.rm(databasePath.resolve(name)); - } catch (IOException e) { - throw new RuntimeException("Cannot remove directory (" + name + ") from disk: " + e.getMessage(), e); + checkClosed(); + if (getTable(name) == null) { + throw new ObjectDoesNotExistException("Table", name); + } + + tables.get(name).close(); + tables.remove(name); + try { + Utils.rm(databasePath.resolve(name)); + } catch (IOException e) { + throw new RuntimeException("Cannot remove directory (" + name + ") from disk: " + e.getMessage(), e); + } + } finally { + lock.writeLock().unlock(); } } @Override public Storeable deserialize(Table table, String value) throws ParseException { + checkClosed(); if (table == null || value == null) { throw new NullArgumentException("deserialize"); } @@ -142,17 +165,20 @@ public Storeable deserialize(Table table, String value) throws ParseException { @Override public String serialize(Table table, Storeable value) throws ColumnFormatException { - return "[" + Serializer.serialize(table, value, ',', '\"') + "]"; + checkClosed(); + return "[" + Serializer.serialize(value, ",", "\"") + "]"; } @Override public Storeable createFor(Table table) { + checkClosed(); MultiFileTable databaseTable = (MultiFileTable) table; return new TableEntry(databaseTable.signature); } @Override public Storeable createFor(Table table, List values) throws ColumnFormatException, IndexOutOfBoundsException { + checkClosed(); MultiFileTable databaseTable = (MultiFileTable) table; Storeable storeable = new TableEntry(databaseTable.signature); @@ -170,12 +196,48 @@ public Storeable createFor(Table table, List values) throws ColumnFormatExcep @Override public List getTableNames() { - List list = new LinkedList<>(); - list.addAll(tables.keySet()); - return list; + lock.readLock().lock(); + try { + checkClosed(); + List list = new LinkedList<>(); + list.addAll(tables.keySet()); + return list; + } finally { + lock.readLock().unlock(); + } } private boolean isNameCorrect(String name) { return (name != null) && !(name.matches(".*[].*|.*\\.")); } + + private void checkClosed() { + if (wasClosed.get()) { + throw new ObjectDoesNotExistException("Database", databasePath.toString()); + } + } + + void deleteNotify(String name) { + tables.remove(name); + } + + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + databasePath.toAbsolutePath().toString() + "]"; + } + + + @Override + public void close() throws Exception { + lock.writeLock().lock(); + wasClosed.set(true); + try { + for (Map.Entry entry : tables.entrySet()) { + entry.getValue().close(); + } + } finally { + lock.writeLock().unlock(); + } + } } diff --git a/src/ru/fizteh/fivt/students/pavel_voropaev/project/database/DatabaseFactory.java b/src/ru/fizteh/fivt/students/pavel_voropaev/project/database/DatabaseFactory.java index 37298a396..8e3790f0d 100644 --- a/src/ru/fizteh/fivt/students/pavel_voropaev/project/database/DatabaseFactory.java +++ b/src/ru/fizteh/fivt/students/pavel_voropaev/project/database/DatabaseFactory.java @@ -1,17 +1,29 @@ package ru.fizteh.fivt.students.pavel_voropaev.project.database; -import ru.fizteh.fivt.storage.structured.TableProvider; import ru.fizteh.fivt.storage.structured.TableProviderFactory; +import ru.fizteh.fivt.students.pavel_voropaev.project.custom_exceptions.ObjectDoesNotExistException; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; -public class DatabaseFactory implements TableProviderFactory { +public class DatabaseFactory implements TableProviderFactory, AutoCloseable { + private Map databases; + boolean wasClosed; + + public DatabaseFactory() { + databases = new HashMap<>(); + wasClosed = false; + } @Override - public TableProvider create(String path) { + public Database create(String path) { + if (wasClosed) { + throw new ObjectDoesNotExistException("Provider", ""); + } if (path == null) { throw new IllegalArgumentException("Database directory is null"); } @@ -24,6 +36,16 @@ public TableProvider create(String path) { throw new IllegalArgumentException("Cannot create database " + dirPath.toString(), e); } } - return new Database(path); + Database newDatabase = new Database(path); + databases.put(path, newDatabase); + return newDatabase; + } + + @Override + public void close() throws Exception { + wasClosed = true; + for (Map.Entry entry : databases.entrySet()) { + entry.getValue().close(); + } } } diff --git a/src/ru/fizteh/fivt/students/pavel_voropaev/project/database/MultiFileTable.java b/src/ru/fizteh/fivt/students/pavel_voropaev/project/database/MultiFileTable.java index ff21d7855..ce8f9f864 100644 --- a/src/ru/fizteh/fivt/students/pavel_voropaev/project/database/MultiFileTable.java +++ b/src/ru/fizteh/fivt/students/pavel_voropaev/project/database/MultiFileTable.java @@ -3,10 +3,9 @@ import ru.fizteh.fivt.storage.structured.ColumnFormatException; import ru.fizteh.fivt.storage.structured.Storeable; import ru.fizteh.fivt.storage.structured.Table; -import ru.fizteh.fivt.storage.structured.TableProvider; import ru.fizteh.fivt.students.pavel_voropaev.project.custom_exceptions.ContainsWrongFilesException; import ru.fizteh.fivt.students.pavel_voropaev.project.custom_exceptions.NullArgumentException; -import ru.fizteh.fivt.students.pavel_voropaev.project.custom_exceptions.TableDoesNotExistException; +import ru.fizteh.fivt.students.pavel_voropaev.project.custom_exceptions.ObjectDoesNotExistException; import java.io.*; import java.nio.ByteBuffer; @@ -16,24 +15,27 @@ import java.text.ParseException; import java.util.*; import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; -public class MultiFileTable implements Table { +public class MultiFileTable implements Table, AutoCloseable { private static final int FOLDERS = 16; private static final String FOLDERS_REGEXP = "(1[0-5]|[0-9])\\.dir"; private static final int FILES = 16; private static final String FILES_REGEXP = "(1[0-5]|[0-9])\\.dat"; private static final String ENCODING = "UTF-8"; - + List> signature; private Map[] content; - private Map diff; + private ThreadLocal> diff; private Path directory; + private ReadWriteLock lock; private String name; - private TableProvider parent; - private int size; - List> signature; - private boolean wasRemoved; + private Database parent; + private ThreadLocal size; + private AtomicBoolean wasClosed; - public MultiFileTable(Path directory, String name, TableProvider parent) throws IOException { + public MultiFileTable(Path directory, String name, Database parent) throws IOException { if (!Files.exists(directory)) { throw new RuntimeException(directory.toString() + ": database doesn't exist"); } @@ -42,210 +44,214 @@ public MultiFileTable(Path directory, String name, TableProvider parent) throws for (int i = 0; i < FOLDERS * FILES; ++i) { content[i] = new HashMap<>(); } - diff = new HashMap<>(); + diff = ThreadLocal.withInitial(HashMap::new); this.directory = directory.resolve(name); + lock = new ReentrantReadWriteLock(); this.name = name; this.parent = parent; - size = 0; + size = ThreadLocal.withInitial(() -> 0); signature = new ArrayList<>(); loadSignature(); - wasRemoved = false; + wasClosed = new AtomicBoolean(false); if (!Files.exists(directory)) { throw new IOException("Cannot read " + directory.getFileName()); } - load(); } @Override public String getName() { - if (wasRemoved) { - throw new TableDoesNotExistException(name); - } + checkClosed(); return name; } @Override public Storeable get(String key) { - if (wasRemoved) { - throw new TableDoesNotExistException(name); - } - if (key == null) { - throw new NullArgumentException("get"); - } + lock.readLock().lock(); + try { + checkClosed(); + if (key == null) { + throw new NullArgumentException("get"); + } - if (diff.containsKey(key)) { - return diff.get(key); + if (diff.get().containsKey(key)) { + return diff.get().get(key); + } + return content[getPlace(key)].get(key); + } finally { + lock.readLock().unlock(); } - return content[getPlace(key)].get(key); } @Override public Storeable put(String key, Storeable value) throws ColumnFormatException { - if (wasRemoved) { - throw new TableDoesNotExistException(name); - } - if (key == null || value == null) { - throw new NullArgumentException("put"); - } + lock.readLock().lock(); + try { + checkClosed(); + if (key == null || value == null) { + throw new NullArgumentException("put"); + } - checkStoreable(value); - int place = getPlace(key); - Storeable oldValue; - if (!diff.containsKey(key) && content[place].containsKey(key)) { - oldValue = content[place].get(key); - if (oldValue.equals(value)) { + checkStoreable(value); + int place = getPlace(key); + Storeable oldValue; + if (!diff.get().containsKey(key) && content[place].containsKey(key)) { + oldValue = content[place].get(key); + if (oldValue.equals(value)) { + return oldValue; + } + diff.get().put(key, value); return oldValue; } - diff.put(key, value); - return oldValue; - } - oldValue = diff.put(key, value); - if (oldValue == null) { - ++size; + oldValue = diff.get().put(key, value); + if (oldValue == null) { + size.set(size.get() + 1); + } + return oldValue; + } finally { + lock.readLock().unlock(); } - return oldValue; } @Override public Storeable remove(String key) { - if (wasRemoved) { - throw new TableDoesNotExistException(name); - } - if (key == null) { - throw new NullArgumentException("remove"); - } - - int place = getPlace(key); - Storeable oldValue; - if (!diff.containsKey(key)) { - if (!content[place].containsKey(key)) { // No such key. - return null; - } else { // Key is saved on disk. - oldValue = content[place].get(key); - diff.put(key, null); + lock.readLock().lock(); + try { + checkClosed(); + if (key == null) { + throw new NullArgumentException("remove"); } - } else { // Some unsaved changes with this key. - if (!content[place].containsKey(key)) { - oldValue = diff.remove(key); - } else { - oldValue = diff.put(key, null); + + int place = getPlace(key); + Storeable oldValue; + if (!diff.get().containsKey(key)) { + if (!content[place].containsKey(key)) { // No such key. + return null; + } else { // Key is saved on disk. + oldValue = content[place].get(key); + diff.get().put(key, null); + } + } else { // Some unsaved changes with this key. + if (!content[place].containsKey(key)) { + oldValue = diff.get().remove(key); + } else { + oldValue = diff.get().put(key, null); + } } - } - if (oldValue != null) { - --size; + if (oldValue != null) { + size.set(size.get() - 1); + } + return oldValue; + } finally { + lock.readLock().unlock(); } - return oldValue; } @Override public int size() { - if (wasRemoved) { - throw new TableDoesNotExistException(name); - } - return size; + checkClosed(); + return size.get(); } @Override public int commit() throws IOException { - if (wasRemoved) { - throw new TableDoesNotExistException(name); - } - Set changedFiles = new HashSet<>(); - for (Entry entry : diff.entrySet()) { - String key = entry.getKey(); - Storeable value = entry.getValue(); - changedFiles.add(getPlace(key)); - int place = getPlace(key); + lock.writeLock().lock(); + try { + checkClosed(); + Set changedFiles = new HashSet<>(); + for (Entry entry : diff.get().entrySet()) { + String key = entry.getKey(); + Storeable value = entry.getValue(); + changedFiles.add(getPlace(key)); + int place = getPlace(key); + + if (value == null) { + content[place].remove(key); + } else { + content[place].put(key, value); + } + } - if (value == null) { - content[place].remove(key); - } else { - content[place].put(key, value); + try { + save(changedFiles); + } catch (SecurityException e) { + throw new IOException(e); } - } - try { - save(changedFiles); - } catch (SecurityException e) { - throw new IOException(e); + int retVal = diff.get().size(); + diff.get().clear(); + return retVal; + } finally { + lock.writeLock().unlock(); } - - int retVal = diff.size(); - diff.clear(); - return retVal; } @Override public int rollback() { - if (wasRemoved) { - throw new TableDoesNotExistException(name); - } - for (Entry entry : diff.entrySet()) { + checkClosed(); + for (Entry entry : diff.get().entrySet()) { if (content[getPlace(entry.getKey())].containsKey(entry.getKey())) { if (entry.getValue() == null) { - ++size; + size.set(size.get() + 1); } } else { - --size; + size.set(size.get() - 1); } } - int retVal = diff.size(); - diff.clear(); + int retVal = diff.get().size(); + diff.get().clear(); return retVal; } @Override public int getColumnsCount() { - if (wasRemoved) { - throw new TableDoesNotExistException(name); - } + checkClosed(); return signature.size(); } @Override public Class getColumnType(int columnIndex) throws IndexOutOfBoundsException { - if (wasRemoved) { - throw new TableDoesNotExistException(name); - } + checkClosed(); return signature.get(columnIndex); } @Override public List list() { - if (wasRemoved) { - throw new TableDoesNotExistException(name); - } - Set keySet = new HashSet<>(); - for (Map pair : content) { - keySet.addAll(pair.keySet()); - } - for (Entry pair : diff.entrySet()) { - if (pair.getValue() == null) { - keySet.remove(pair.getKey()); - } else { - keySet.add(pair.getKey()); + lock.readLock().lock(); + try { + checkClosed(); + Set keySet = new HashSet<>(); + for (Map pair : content) { + keySet.addAll(pair.keySet()); } + for (Entry pair : diff.get().entrySet()) { + if (pair.getValue() == null) { + keySet.remove(pair.getKey()); + } else { + keySet.add(pair.getKey()); + } + } + List list = new LinkedList<>(); + list.addAll(keySet); + return list; + } finally { + lock.readLock().unlock(); } - List list = new LinkedList<>(); - list.addAll(keySet); - return list; } @Override public int getNumberOfUncommittedChanges() { - if (wasRemoved) { - throw new TableDoesNotExistException(name); - } - return diff.size(); + checkClosed(); + return diff.get().size(); } - protected void destroy() { - wasRemoved = true; + @Override + public String toString() { + return getClass().getSimpleName() + "[" + directory.toAbsolutePath() + "]"; } private void load() throws IOException { @@ -302,7 +308,7 @@ private void readFile(String fileName, int dirNum, int fileNum) throws IOExcepti if (content[dirNum * FILES + fileNum].put(key, deserializedValue) != null) { //The same key. throw new ContainsWrongFilesException(directory.toString()); } - ++size; + size.set(size.get() + 1); } } catch (ParseException | EOFException e) { throw new ContainsWrongFilesException(directory.toString()); @@ -419,4 +425,22 @@ private void checkStoreable(Storeable value) throws ColumnFormatException { } } + private void checkClosed() { + if (wasClosed.get()) { + throw new ObjectDoesNotExistException("Table", name); + } + } + + @Override + public void close() { + checkClosed(); + lock.writeLock().lock(); + try { + rollback(); + wasClosed.set(true); + parent.deleteNotify(name); + } finally { + lock.writeLock().unlock(); + } + } } diff --git a/src/ru/fizteh/fivt/students/pavel_voropaev/project/database/Serializer.java b/src/ru/fizteh/fivt/students/pavel_voropaev/project/database/Serializer.java index 83618710f..28ce9abd2 100644 --- a/src/ru/fizteh/fivt/students/pavel_voropaev/project/database/Serializer.java +++ b/src/ru/fizteh/fivt/students/pavel_voropaev/project/database/Serializer.java @@ -14,8 +14,6 @@ import java.util.function.Function; public class Serializer { - static final String SIGNATURE_FILE_NAME = "signature.tsv"; - public static final Map> SUPPORTED_TYPES; static { @@ -30,6 +28,7 @@ public class Serializer { SUPPORTED_TYPES = Collections.unmodifiableMap(supportedTypes); } + static final String SIGNATURE_FILE_NAME = "signature.tsv"; static final Map, String> TYPES_TO_NAMES; static { @@ -98,28 +97,32 @@ public static void deserialize(Table table, Storeable storeable, Parser parser) } } - public static String serialize(Table table, Storeable value, char delim, char escape) throws ColumnFormatException { - if (table == null || value == null) { + public static String serialize(Storeable value, String delim, String escape) + throws ColumnFormatException { + if (value == null) { throw new NullArgumentException("deserialize"); } StringBuilder serialized = new StringBuilder(); Object object; - for (int i = 0; i < table.getColumnsCount(); ++i) { - object = value.getColumnAt(i); - if (object != null && object.getClass().equals(String.class)) { - serialized.append(escape); - } - serialized.append(object); - if (object != null && object.getClass().equals(String.class)) { - serialized.append(escape); - } - if (i != table.getColumnsCount() - 1) { - serialized.append(delim); - if (!Character.isSpaceChar(delim)) { - serialized.append(' '); + + try { + for (int i = 0; true; ++i) { + object = value.getColumnAt(i); + if (object != null) { + if (object.getClass().equals(String.class)) { + serialized.append(escape); + serialized.append(object); + serialized.append(escape); + } else { + serialized.append(object); + } + serialized.append(delim); } } + } catch (IndexOutOfBoundsException e) { + int left = serialized.lastIndexOf(delim); + serialized.delete(left, left + delim.length()); } return serialized.toString(); diff --git a/src/ru/fizteh/fivt/students/pavel_voropaev/project/database/TableEntry.java b/src/ru/fizteh/fivt/students/pavel_voropaev/project/database/TableEntry.java index 03ece111e..67c767564 100644 --- a/src/ru/fizteh/fivt/students/pavel_voropaev/project/database/TableEntry.java +++ b/src/ru/fizteh/fivt/students/pavel_voropaev/project/database/TableEntry.java @@ -128,4 +128,10 @@ public int hashCode() { } return result; } + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + Serializer.serialize(this, ",", "") + "]"; + } + } diff --git a/src/ru/fizteh/fivt/students/pavel_voropaev/project/proxy/LoggingInvocationHandler.java b/src/ru/fizteh/fivt/students/pavel_voropaev/project/proxy/LoggingInvocationHandler.java new file mode 100644 index 000000000..68713c479 --- /dev/null +++ b/src/ru/fizteh/fivt/students/pavel_voropaev/project/proxy/LoggingInvocationHandler.java @@ -0,0 +1,134 @@ +package ru.fizteh.fivt.students.pavel_voropaev.project.proxy; + +import ru.fizteh.fivt.students.pavel_voropaev.project.custom_exceptions.NullArgumentException; + +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import java.io.Writer; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.IdentityHashMap; + +class LoggingInvocationHandler implements InvocationHandler { + private static final String INVOKE = "invoke"; + private static final String TIMESTAMP = "timestamp"; + private static final String CLASS_STR = "class"; + private static final String NAME = "name"; + private static final String ARGUMENTS = "arguments"; + private static final String ARGUMENT = "argument"; + private static final String NULL_STR = "null"; + private static final String RETURN_STR = "return"; + private static final String THROWN = "thrown"; + private static final String LIST_STR = "list"; + private static final String VALUE_STR = "value"; + private static final String CYCLIC = "cyclic"; + + private Writer writer; + private Object implementation; + + public LoggingInvocationHandler(Writer writer, Object implementation) { + if (writer == null || implementation == null) { + throw new NullArgumentException(getClass().getSimpleName()); + } + this.writer = writer; + this.implementation = implementation; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (isMethodOfObject(method)) { + method.invoke(args); + } + + XMLStreamWriter xmlWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(writer); + try { + xmlWriter.writeStartElement(INVOKE); // + for (Object argument : args) { + xmlWriter.writeStartElement(ARGUMENT); + if (argument == null) { + xmlWriter.writeEmptyElement(NULL_STR); + } else if (argument instanceof Iterable) { + writeIterable((Iterable) argument, xmlWriter, new IdentityHashMap<>()); + } else { + xmlWriter.writeCharacters(argument.toString()); + } + xmlWriter.writeEndElement(); + } + xmlWriter.writeEndElement(); // + } + + try { + Object returned = method.invoke(implementation, args); + if (method.getReturnType() != void.class) { + xmlWriter.writeStartElement(RETURN_STR); + if (returned != null) { + xmlWriter.writeCharacters(returned.toString()); + } else { + xmlWriter.writeEmptyElement(NULL_STR); + } + xmlWriter.writeEndElement(); + } + + xmlWriter.writeEndElement(); + return returned; + } catch (InvocationTargetException e) { + Throwable targetException = e.getTargetException(); + xmlWriter.writeStartElement(THROWN); + xmlWriter.writeCharacters(targetException.getClass().getName() + ": " + targetException.getMessage()); + xmlWriter.writeEndElement(); + + xmlWriter.writeEndElement(); + throw targetException; + } + } catch (XMLStreamException e) { + writer.write("Logging error: " + e.getMessage()); + return null; + } finally { + xmlWriter.flush(); + xmlWriter.close(); + } + } + + private boolean isMethodOfObject(Method method) { + try { + Object.class.getMethod(method.getName(), method.getParameterTypes()); + } catch (NoSuchMethodException e) { + return false; + } + return true; + } + + private void writeIterable(Iterable iterable, XMLStreamWriter xmlWriter, + IdentityHashMap cyclic) throws Throwable { + cyclic.put(iterable, null); + xmlWriter.writeStartElement(LIST_STR); + for (Object value : iterable) { + xmlWriter.writeStartElement(VALUE_STR); + if (value == null) { + xmlWriter.writeEmptyElement(NULL_STR); + xmlWriter.writeEndElement(); + } else if (value instanceof Iterable) { + if (cyclic.containsKey(value)) { + xmlWriter.writeCharacters(CYCLIC); + } else { + writeIterable((Iterable) value, xmlWriter, cyclic); + } + } else { + xmlWriter.writeCharacters(value.toString()); + } + xmlWriter.writeEndElement(); + } + xmlWriter.writeEndElement(); + cyclic.remove(iterable); + } +} diff --git a/src/ru/fizteh/fivt/students/pavel_voropaev/project/proxy/LoggingProxyFactoryImpl.java b/src/ru/fizteh/fivt/students/pavel_voropaev/project/proxy/LoggingProxyFactoryImpl.java new file mode 100644 index 000000000..8173cf38f --- /dev/null +++ b/src/ru/fizteh/fivt/students/pavel_voropaev/project/proxy/LoggingProxyFactoryImpl.java @@ -0,0 +1,21 @@ +package ru.fizteh.fivt.students.pavel_voropaev.project.proxy; + +import ru.fizteh.fivt.proxy.LoggingProxyFactory; +import ru.fizteh.fivt.students.pavel_voropaev.project.custom_exceptions.NullArgumentException; + +import java.io.Writer; +import java.lang.reflect.Proxy; + +public class LoggingProxyFactoryImpl implements LoggingProxyFactory { + + @Override + public Object wrap(Writer writer, Object implementation, Class interfaceClass) { + + if (writer == null || implementation == null || interfaceClass == null + || !interfaceClass.isInterface()) { + throw new NullArgumentException("wrap"); + } + return Proxy.newProxyInstance(implementation.getClass().getClassLoader(), new Class[]{interfaceClass}, + new LoggingInvocationHandler(writer, implementation)); + } +} diff --git a/src/ru/fizteh/fivt/students/pavel_voropaev/project/tests/DatabaseFactoryTest.java b/src/ru/fizteh/fivt/students/pavel_voropaev/project/tests/DatabaseFactoryTest.java index 9b9462ab7..4d05aea44 100644 --- a/src/ru/fizteh/fivt/students/pavel_voropaev/project/tests/DatabaseFactoryTest.java +++ b/src/ru/fizteh/fivt/students/pavel_voropaev/project/tests/DatabaseFactoryTest.java @@ -29,19 +29,21 @@ public void tearDown() throws IOException { } @Test - public void factoryCreateForExistingFolder() throws IOException { - TableProviderFactory test = new DatabaseFactory(); + public void factoryCreateForExistingFolder() throws Exception { + DatabaseFactory test = new DatabaseFactory(); TableProvider provider = test.create(testDirectory.toString()); assertNotNull(provider); + test.close(); } @Test - public void factoryCreateForNotExistingFolder() throws IOException { + public void factoryCreateForNotExistingFolder() throws Exception { Utils.rm(testDirectory); - TableProviderFactory test = new DatabaseFactory(); + DatabaseFactory test = new DatabaseFactory(); test.create(testDirectory.toString()); assertTrue(testDirectory.toFile().exists()); + test.close(); } @Test(expected = IllegalArgumentException.class) diff --git a/src/ru/fizteh/fivt/students/pavel_voropaev/project/tests/DatabaseTest.java b/src/ru/fizteh/fivt/students/pavel_voropaev/project/tests/DatabaseTest.java index a7715bbe6..60cc7ff66 100644 --- a/src/ru/fizteh/fivt/students/pavel_voropaev/project/tests/DatabaseTest.java +++ b/src/ru/fizteh/fivt/students/pavel_voropaev/project/tests/DatabaseTest.java @@ -6,7 +6,7 @@ import org.junit.rules.TemporaryFolder; import ru.fizteh.fivt.storage.structured.Table; import ru.fizteh.fivt.storage.structured.TableProvider; -import ru.fizteh.fivt.students.pavel_voropaev.project.custom_exceptions.TableDoesNotExistException; +import ru.fizteh.fivt.students.pavel_voropaev.project.custom_exceptions.ObjectDoesNotExistException; import ru.fizteh.fivt.students.pavel_voropaev.project.database.Database; import java.io.IOException; @@ -58,9 +58,10 @@ public void databaseInitializationForDirectoryWithGarbage() throws IOException { } @Test - public void databaseInitializationForDirectoryWithTables() throws IOException { - TableProvider test = new Database(databasePath.toString()); + public void databaseInitializationForDirectoryWithTables() throws Exception { + Database test = new Database(databasePath.toString()); test.createTable(tableName, signature); + test.close(); TableProvider testInit = new Database(databasePath.toString()); assertEquals(tableName, testInit.getTable(tableName).getName()); @@ -106,7 +107,7 @@ public void createTableForWrongNamedTable() throws IOException { test.createTable(stringWithBannedSymbols, signature); } - @Test(expected = TableDoesNotExistException.class) + @Test(expected = ObjectDoesNotExistException.class) public void removeTableForExistingTable() throws IOException { TableProvider test = new Database(databasePath.toString()); test.createTable(tableName, signature); diff --git a/src/ru/fizteh/fivt/students/pavel_voropaev/project/tests/LoggingTest.java b/src/ru/fizteh/fivt/students/pavel_voropaev/project/tests/LoggingTest.java new file mode 100644 index 000000000..fcff66acf --- /dev/null +++ b/src/ru/fizteh/fivt/students/pavel_voropaev/project/tests/LoggingTest.java @@ -0,0 +1,175 @@ +package ru.fizteh.fivt.students.pavel_voropaev.project.tests; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import ru.fizteh.fivt.proxy.LoggingProxyFactory; +import ru.fizteh.fivt.students.pavel_voropaev.project.proxy.LoggingProxyFactoryImpl; + +import java.io.StringWriter; +import java.io.Writer; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class LoggingTest { + private static final String EXPECTED_INVOKE = + "") + .append("0") + .append(EXPECTED_END); + + assertEquals(builder.toString(), result); + } + + @Test + public void proxyReturnedNull() { + instance.getNull(); + String result = writer.toString().replaceFirst("timestamp=\"\\d*\"", "T"); + + builder.append(" name=\"getNull\">") + .append("") + .append("") + .append(EXPECTED_END); + + assertEquals(builder.toString(), result); + } + + @Test + public void proxyReturnedString() { + instance.getString(TEST_STRING); + String result = writer.toString().replaceFirst("timestamp=\"\\d*\"", "T"); + + builder.append(" name=\"getString\">") + .append("" + TEST_STRING + "") + .append("" + TEST_STRING + "test") + .append(EXPECTED_END); + + assertEquals(builder.toString(), result); + } + + @Test + public void proxyReturnedInteger() { + instance.getInt(TEST_INT); + String result = writer.toString().replaceFirst("timestamp=\"\\d*\"", "T"); + + builder.append(" name=\"getInt\">") + .append("" + TEST_INT + "") + .append("" + 42 * TEST_INT + "") + .append(EXPECTED_END); + + assertEquals(builder.toString(), result); + } + + @Test + public void proxyReturnedListOfLists() { + List list1 = Arrays.asList("String11", "String12"); + List list2 = Arrays.asList("String21"); + List> lists = Arrays.asList(list1, list2); + instance.getListList(lists); + String result = writer.toString().replaceFirst("timestamp=\"\\d*\"", "T"); + + builder.append(" name=\"getListList\">"); + String listXML = "" + + "" + + "" + "String11" + "String12" + "" + + "" + + "" + + "" + "String21" + "" + + "" + + ""; + builder.append("" + listXML + "") + .append("" + lists.toString() + "") + .append(EXPECTED_END); + + assertEquals(builder.toString(), result); + } + + @Test(expected = Exception.class) + public void proxyExceptionThrown() throws Exception { + try { + instance.throwException(); + } finally { + String result = writer.toString().replaceFirst("timestamp=\"\\d*\"", "T"); + + builder.append(" name=\"throwException\">") + .append("") + .append("java.lang.Exception: Exception") + .append(EXPECTED_END); + + assertEquals(builder.toString(), result); + } + } + + public interface TestInterface { + void getVoid(int a); + + Object getNull(); + + int getInt(int a); + + String getString(String a); + + List> getListList(List> list); + + void throwException() throws Exception; + } + + public class TestInterfaceImpl implements TestInterface { + + @Override + public void getVoid(int a) { + } + + @Override + public Object getNull() { + return null; + } + + @Override + public int getInt(int a) { + return 42 * a; + } + + @Override + public String getString(String a) { + return a + "test"; + } + + @Override + public List> getListList(List> list) { + return list; + } + + @Override + public void throwException() throws Exception { + throw new Exception("Exception"); + } + } +} diff --git a/src/ru/fizteh/fivt/students/pavel_voropaev/project/tests/MultiFileTableTest.java b/src/ru/fizteh/fivt/students/pavel_voropaev/project/tests/MultiFileTableTest.java index 777ce1cff..eb02cb032 100644 --- a/src/ru/fizteh/fivt/students/pavel_voropaev/project/tests/MultiFileTableTest.java +++ b/src/ru/fizteh/fivt/students/pavel_voropaev/project/tests/MultiFileTableTest.java @@ -7,10 +7,9 @@ import org.junit.rules.TemporaryFolder; import ru.fizteh.fivt.storage.structured.Storeable; import ru.fizteh.fivt.storage.structured.Table; -import ru.fizteh.fivt.storage.structured.TableProvider; -import ru.fizteh.fivt.storage.structured.TableProviderFactory; import ru.fizteh.fivt.students.pavel_voropaev.project.custom_exceptions.ContainsWrongFilesException; -import ru.fizteh.fivt.students.pavel_voropaev.project.custom_exceptions.TableDoesNotExistException; +import ru.fizteh.fivt.students.pavel_voropaev.project.custom_exceptions.ObjectDoesNotExistException; +import ru.fizteh.fivt.students.pavel_voropaev.project.database.Database; import ru.fizteh.fivt.students.pavel_voropaev.project.database.DatabaseFactory; import ru.fizteh.fivt.students.pavel_voropaev.project.database.MultiFileTable; @@ -43,8 +42,8 @@ public class MultiFileTableTest { private final String badFileToRead = "1.dat"; private final String stringWithBannedSymbols = "Моя<Папка"; - private static TableProviderFactory factory; - private TableProvider provider; + private static DatabaseFactory factory; + private Database provider; private Table table; @Rule @@ -77,8 +76,9 @@ public void tableInitializationForExistingDirectoryWithCorrectFiles() throws IOE Path fileInTable = directoryInTable.resolve(goodFileToRead); testWriter(fileInTable, keyToRead, valueJSON); - Table table = new MultiFileTable(databasePath, tableName, provider); + MultiFileTable table = new MultiFileTable(databasePath, tableName, provider); assertNotNull(table); + table.close(); } @Test(expected = ContainsWrongFilesException.class) @@ -255,55 +255,55 @@ public void getName() { assertEquals(tableName, table.getName()); } - @Test(expected = TableDoesNotExistException.class) + @Test(expected = ObjectDoesNotExistException.class) public void getColumnsCountWasRemoved() throws IOException { provider.removeTable(tableName); table.getColumnsCount(); } - @Test(expected = TableDoesNotExistException.class) + @Test(expected = ObjectDoesNotExistException.class) public void getWasRemoved() throws IOException { provider.removeTable(tableName); table.get(key); } - @Test(expected = TableDoesNotExistException.class) + @Test(expected = ObjectDoesNotExistException.class) public void getNumberOfUncommittedChangesWasRemoved() throws IOException { provider.removeTable(tableName); table.getNumberOfUncommittedChanges(); } - @Test(expected = TableDoesNotExistException.class) + @Test(expected = ObjectDoesNotExistException.class) public void getNameWasRemoved() throws IOException { provider.removeTable(tableName); table.getName(); } - @Test(expected = TableDoesNotExistException.class) + @Test(expected = ObjectDoesNotExistException.class) public void commitWasRemoved() throws IOException { provider.removeTable(tableName); table.commit(); } - @Test(expected = TableDoesNotExistException.class) + @Test(expected = ObjectDoesNotExistException.class) public void listWasRemoved() throws IOException { provider.removeTable(tableName); table.list(); } - @Test(expected = TableDoesNotExistException.class) + @Test(expected = ObjectDoesNotExistException.class) public void keyWasRemoved() throws IOException { provider.removeTable(tableName); table.put(key, valueStoreable); } - @Test(expected = TableDoesNotExistException.class) + @Test(expected = ObjectDoesNotExistException.class) public void removeWasRemoved() throws IOException { provider.removeTable(tableName); table.remove(key); } - @Test(expected = TableDoesNotExistException.class) + @Test(expected = ObjectDoesNotExistException.class) public void sizeWasRemoved() throws IOException { provider.removeTable(tableName); table.size();