diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/proxy/BadXmlException.java b/src/ru/fizteh/fivt/students/LebedevAleksey/proxy/BadXmlException.java new file mode 100644 index 000000000..2a845e3f6 --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/proxy/BadXmlException.java @@ -0,0 +1,19 @@ +package ru.fizteh.fivt.students.LebedevAleksey.proxy; + +public class BadXmlException extends Exception { + public BadXmlException() { + super(); + } + + public BadXmlException(String s) { + super(s); + } + + public BadXmlException(String message, Throwable cause) { + super(message, cause); + } + + public BadXmlException(Throwable cause) { + super(cause); + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/proxy/ProxyFactory.java b/src/ru/fizteh/fivt/students/LebedevAleksey/proxy/ProxyFactory.java new file mode 100644 index 000000000..6c470cb5c --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/proxy/ProxyFactory.java @@ -0,0 +1,152 @@ +package ru.fizteh.fivt.students.LebedevAleksey.proxy; + +import java.io.IOException; +import java.io.Writer; +import java.lang.reflect.*; +import java.util.Arrays; +import java.util.IdentityHashMap; + +public class ProxyFactory implements ru.fizteh.fivt.proxy.LoggingProxyFactory { + public static final String ITERABLE_VALUE_TAG = "value"; + private long timestamp; + private boolean testMode = false; + + public ProxyFactory() { + } + + public ProxyFactory(int timestamp) { + this.timestamp = timestamp; + testMode = true; + } + + public void finishLog(Writer writer) throws IOException { + writer.append(System.lineSeparator() + ""); + } + + @Override + public Object wrap(Writer writer, Object implementation, Class interfaceClass) { + final XmlWriter xml = new XmlWriter(writer); + try { + xml.writeBeginDocument("log"); + } catch (Exception e) { + // Eating this exception + } + InvocationHandler invocationHandler = new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (Arrays.asList(Object.class.getMethods()).contains(method)) { + if (method.getName().equals("toString")) { + return "Wrapper on " + implementation.toString(); + } + if (method.getName().equals("equals")) { + return proxy == args[0]; + } + if (method.getName().equals("hashCode")) { + return xml.hashCode(); + } + if (method.getName().equals("clone")) { + return wrap(writer, implementation, interfaceClass); + } + } + try { + xml.writeBeginningTag("invoke"); + xml.writeArgument("timestamp", + ((Long) (testMode ? timestamp : System.currentTimeMillis())).toString()); + xml.writeArgument("class", implementation.getClass().getName()); + xml.writeArgument("name", method.getName()); + xml.writeEndingOfTag(); + xml.writeBeginningTag("arguments"); + if (args != null) { + xml.writeEndingOfTag(); + for (int i = 0; i < args.length; i++) { + IdentityHashMap map = new IdentityHashMap(); + xml.writeBeginningTag("argument"); + xml.writeEndingOfTag(); + printValue(args[i], map); + xml.writeEndTag(); + } + xml.writeEndTag(); + } else { + xml.writeClosingEndingTag(); + } + } catch (Exception e) { + // Eating exception + } + try { + Object result = method.invoke(implementation, args); + if (method.getReturnType() != void.class) { + xml.writeBeginningTag("return"); + xml.writeEndingOfTag(); + IdentityHashMap map = new IdentityHashMap(); + printValue(result, map); + xml.writeEndTag(); + } + return result; + } catch (InvocationTargetException e) { + try { + xml.writeBeginningTag("thrown"); + xml.writeEndingOfTag(); + xml.writePlainText(e.getTargetException().toString()); + xml.writeEndTag(); + } catch (Exception ex) { + // Nothing to do with it + } + throw e.getCause(); + } catch (Exception e) { + return null; + } finally { + try { + xml.writeEndTag(); + xml.writeEndDocument(); + } catch (Exception e) { + // Nothing to do with it + } + } + } + + private void printValue(Object item, IdentityHashMap map) + throws IOException, BadXmlException { + if (item == null) { + xml.writeBeginningTagSameLine("null"); + xml.writeClosingEndingTag(); + return; + } + if (map.containsKey(item)) { + xml.writeBeginningTagSameLine("cyclic"); + xml.writeClosingEndingTag(); + return; + } else { + map.put(item, null); + } + if (item instanceof Iterable) { + xml.writeBeginningTag("list"); + xml.writeEndingOfTag(); + for (Object value : (Iterable) item) { + xml.writeBeginningTag(ITERABLE_VALUE_TAG); + xml.writeEndingOfTag(); + printValue(value, map); + xml.writeEndTag(); + } + xml.writeEndTag(); + return; + } + if (item.getClass().isArray()) { + xml.writeBeginningTag("array"); + xml.writeEndingOfTag(); + int length = Array.getLength(item); + for (int i = 0; i < length; ++i) { + xml.writeBeginningTag(ITERABLE_VALUE_TAG); + xml.writeEndingOfTag(); + printValue(Array.get(item, i), map); + xml.writeEndTag(); + } + xml.writeEndTag(); + return; + } + xml.writePlainText(item.toString()); + } + }; + return Proxy.newProxyInstance(interfaceClass.getClassLoader(), + new Class[]{interfaceClass}, invocationHandler); + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/proxy/XmlWriter.java b/src/ru/fizteh/fivt/students/LebedevAleksey/proxy/XmlWriter.java new file mode 100644 index 000000000..6d4fa5cd3 --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/proxy/XmlWriter.java @@ -0,0 +1,116 @@ +package ru.fizteh.fivt.students.LebedevAleksey.proxy; + +import java.io.IOException; +import java.io.Writer; +import java.util.Stack; + +public class XmlWriter { + private Writer writer; + private String initialNodeName; + private Stack lastNodes = new Stack<>(); + private boolean wasPlainText = false; + + public XmlWriter(Writer writer) { + this.writer = writer; + } + + public void writeBeginDocument(String initialNodeName) throws IOException, BadXmlException { + this.initialNodeName = initialNodeName; + write(""); + writeBeginningTag(initialNodeName); + writeEndingOfTag(); + } + + public void writeEndingOfTag() throws IOException, BadXmlException { + write(">"); + } + + public void writeEndTag() throws IOException, BadXmlException { + writeEndTag(1); + } + + private void writeNewLine() throws IOException { + writeln(""); + for (int i = 0; i < lastNodes.size(); i++) { + write(" "); + } + } + + public void writePlainText(String text) throws IOException { + wasPlainText = true; + write(toXmlString(text)); + } + + private void writeEndTag(int minTagCount) throws IOException, BadXmlException { + if (lastNodes.size() <= minTagCount) { + throw new BadXmlException("There is no tags to close"); + } else { + String tag = lastNodes.pop(); + if (!wasPlainText) { + writeNewLine(); + } + wasPlainText = false; + write("", ">").replace("\n", " ").replace("\r", "$#13;"); + } + + private void write(String text) throws IOException { + writer.append(text); + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/proxy/tests/ProxyFactoryTest.java b/src/ru/fizteh/fivt/students/LebedevAleksey/proxy/tests/ProxyFactoryTest.java new file mode 100644 index 000000000..1905e1f4d --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/proxy/tests/ProxyFactoryTest.java @@ -0,0 +1,158 @@ +package ru.fizteh.fivt.students.LebedevAleksey.proxy.tests; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import ru.fizteh.fivt.storage.structured.Table; +import ru.fizteh.fivt.storage.structured.TableProvider; +import ru.fizteh.fivt.students.LebedevAleksey.proxy.ProxyFactory; +import ru.fizteh.fivt.students.LebedevAleksey.storeable.DatabaseFactory; + +import java.io.CharArrayWriter; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; + +interface TestInterface { + void f(int[] a); +} + +public class ProxyFactoryTest { + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + private File dbPath; + private TableProvider database; + + @Before + public void setUp() throws IOException { + dbPath = folder.newFolder("db"); + database = new DatabaseFactory().create(dbPath.getAbsolutePath()); + } + + @Test + public void testWrapOnTableProvider() throws Exception { + ProxyFactory factory = new ProxyFactory(12345); + CharArrayWriter writer = new CharArrayWriter(); + TableProvider provider = (TableProvider) (factory.wrap(writer, database, TableProvider.class)); + Table table = provider.createTable("name", Arrays.asList(Integer.class, String.class)); + Assert.assertNotNull(table); + Assert.assertEquals(table, provider.getTable("name")); + String path = dbPath.toPath().resolve("name").toString(); + Assert.assertEquals(("\n" + + "\n" + + " \n" + + " \n" + + " name\n" + + " \n" + + " \n" + + " class java.lang.Integer\n" + + " class java.lang.String\n" + + " \n" + + " \n" + + " \n" + + " StoreableTable[" + path + "]\n" + + " \n" + + " \n" + + " \n" + + " name\n" + + " \n" + + " StoreableTable[" + path + "]\n" + + " ").replace("\n", System.lineSeparator()), new String(writer.toCharArray())); + try { + provider.getTable(null); + } catch (IllegalArgumentException e) { + // Ok + } + Assert.assertEquals(("\n" + + "\n" + + " \n" + + " \n" + + " name\n" + + " \n" + + " \n" + + " class java.lang.Integer\n" + + " class java.lang.String\n" + + " \n" + + " \n" + + " \n" + + " StoreableTable[" + path + "]\n" + + " \n" + + " \n" + + " \n" + + " name\n" + + " \n" + + " StoreableTable[" + path + "]\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " java.lang.IllegalArgumentException: Argument "name" is null\n" + + " ").replace("\n", System.lineSeparator()), new String(writer.toCharArray())); + } + + @Test + public void testWrapOnTable() throws Exception { + ProxyFactory factory = new ProxyFactory(12345); + CharArrayWriter writer = new CharArrayWriter(); + Table newTable = database.createTable("name", + Arrays.asList(Integer.class, String.class)); + newTable.put("a", database.createFor(newTable)); + newTable.commit(); + Table table = (Table) new ProxyFactory(12345).wrap(writer, newTable, Table.class); + Assert.assertNotNull(table); + Assert.assertEquals(1, table.size()); + table.equals(table); + table.hashCode(); + table.toString(); + factory.finishLog(writer); + Assert.assertEquals(("\n" + + "\n" + + " \n" + + " \n" + + " 1\n" + + " \n" + + "").replace("\n", System.lineSeparator()), new String(writer.toCharArray())); + } + + @Test + public void testWrapOnTestClass() throws Exception { + ProxyFactory factory = new ProxyFactory(1); + CharArrayWriter writer = new CharArrayWriter(); + TestInterface object = (TestInterface) new ProxyFactory(12345).wrap(writer, new TestClass(), + TestInterface.class); + object.f(new int[]{1, 2}); + factory.finishLog(writer); + Assert.assertEquals(("\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " 1\n" + + " 2\n" + + " \n" + + " \n" + + " \n" + + " \n" + + "").replace("\n", System.lineSeparator()), new String(writer.toCharArray())); + } +} + +class TestClass implements TestInterface { + + @Override + public void f(int[] a) { + + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/Database.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/Database.java new file mode 100644 index 000000000..7f72ef53a --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/Database.java @@ -0,0 +1,414 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable; + +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.LebedevAleksey.MultiFileHashMap.DatabaseFileStructureException; +import ru.fizteh.fivt.students.LebedevAleksey.MultiFileHashMap.LoadOrSaveException; +import ru.fizteh.fivt.students.LebedevAleksey.MultiFileHashMap.Pair; +import ru.fizteh.fivt.students.LebedevAleksey.junit.DatabaseException; +import ru.fizteh.fivt.students.LebedevAleksey.storeable.json.BrokenJsonException; +import ru.fizteh.fivt.students.LebedevAleksey.storeable.json.JsonParser; +import ru.fizteh.fivt.students.LebedevAleksey.storeable.json.JsonUnsupportedObjectException; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.text.ParseException; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.BiConsumer; + +public class Database implements TableProvider, AutoCloseable { + public static final String TABLE_SIGNATURE_FILE_NAME = "signature.tsv"; + private static final String INCORRECT_NAME_OF_TABLES = "This name is not correct, folder can't be created"; + private static Map> stringTypesMap = new HashMap<>(); + private static Map typesStringMap = new HashMap<>(); + private AtomicBoolean closed = new AtomicBoolean(false); + + static { + stringTypesMap.put("int", Integer.class); + stringTypesMap.put("long", Long.class); + stringTypesMap.put("byte", Byte.class); + stringTypesMap.put("float", Float.class); + stringTypesMap.put("double", Double.class); + stringTypesMap.put("boolean", Boolean.class); + stringTypesMap.put("String", String.class); + stringTypesMap.forEach(new BiConsumer>() { + @Override + public void accept(String alias, Class type) { + typesStringMap.put(type, alias); + } + }); + } + + private Path directoryPath; + private Map tables = new HashMap<>(); + private ReadWriteLock lock = new ReentrantReadWriteLock(); + + public Database(String directory) throws IOException { + lock.writeLock().lock(); + try { + assertArgumentNotNull(directory, "directory"); + File root = new File(directory); + directoryPath = root.toPath(); + File[] tables = root.listFiles(); + for (File file : tables) { + loadTable(file); + } + } finally { + lock.writeLock().unlock(); + } + } + + public static void throwIOException(Throwable e) throws IOException { + if (e.getCause() != null) { + try { + IOException ioException = (IOException) e.getCause(); + throw ioException; + } catch (ClassCastException wrongClass) { + //Not IOException + } + } + throw new IOException(e.getMessage(), e); + } + + public static List> parseTypes(String[] input) throws ParseException { + List> types = new ArrayList<>(input.length); + for (String item : input) { + Class type = stringTypesMap.get(item); + if (type != null) { + types.add(type); + } else { + throw new ParseException("Wrong type: " + item, -1); + } + } + return types; + } + + private void checkClosed() { + if (closed.get()) { + throw new IllegalStateException("TableProvider is closed"); + } + } + + private void loadTable(File file) throws IOException { + if (file.isDirectory()) { + File signature = file.toPath().resolve(TABLE_SIGNATURE_FILE_NAME).toFile(); + String tablename = file.getName(); + if (signature.exists() && signature.isFile()) { + String signatureString; + try (FileInputStream stream = new FileInputStream(signature.getAbsolutePath())) { + try (DataInputStream signaturedata = new DataInputStream(stream)) { + signatureString = signaturedata.readUTF(); + } + } + String[] tokens = signatureString.split(" "); + List> types = new ArrayList<>(); + if (signatureString.length() != 0) { + for (String item : tokens) { + Class type = stringTypesMap.get(item); + if (type == null) { + throw new IOException("Wrong type name in signature of table " + tablename); + } else { + types.add(type); + } + } + } + StoreableTable table = generateTable(tablename, types); + this.tables.put(tablename, table); + } else { + throw new IOException("Where is not signature file in table " + tablename); + } + } else { + fileFoundInRootDirectory(file); + } + } + + void reloadTable(Table table) throws IOException { + checkClosed(); + lock.writeLock().lock(); + try { + if (tables.get(table.getName()) == table) { + loadTable(getRootDirectoryPath().resolve(table.getName()).toFile()); + } else { + throw new IllegalArgumentException("Table doesn't exist."); + } + } finally { + lock.writeLock().unlock(); + } + } + + protected void fileFoundInRootDirectory(File file) throws IOException { + throw new IOException("There is file " + file.getName() + " in root directory"); + } + + @Override + public Table getTable(String name) { + checkClosed(); + assertArgumentNotNull(name, "name"); + lock.readLock().lock(); + StoreableTable table = tables.get(name); + lock.readLock().unlock(); + return table; + } + + @Override + public Table createTable(String name, List> columnTypes) throws IOException { + checkClosed(); + assertArgumentNotNull(name, "name"); + assertArgumentNotNull(columnTypes, "columnTypes"); + lock.writeLock().lock(); + try { + Table checkExists = getTable(name); + if (checkExists == null) { + Path rootDirectoryPath = getRootDirectoryPath(); + Path path = rootDirectoryPath.resolve(name); + if (path.startsWith(rootDirectoryPath) && path.getParent().equals(rootDirectoryPath)) { + if (columnTypes == null) { + throw new IllegalArgumentException("Argument \"columnTypes\" is null."); + } + String tableSignature = createTableSignature(columnTypes); + try { + Files.createDirectory(path); + try (FileOutputStream stream = new FileOutputStream(path.resolve( + TABLE_SIGNATURE_FILE_NAME).toString())) { + try (DataOutputStream dataStream = new DataOutputStream(stream)) { + dataStream.writeUTF(tableSignature); + dataStream.flush(); + } + } + } catch (Exception ex) { + try { + Files.delete(path); + } catch (Throwable suppressed) { + ex.addSuppressed(suppressed); + } + throw ex; + } + StoreableTable table = generateTable(name, columnTypes); + tables.put(name, table); + return table; + } else { + throw new IllegalArgumentException(INCORRECT_NAME_OF_TABLES); + } + } else { + return null; + } + } finally { + lock.writeLock().unlock(); + } + } + + private String createTableSignature(List> columnTypes) { + StringBuilder tableSignature = new StringBuilder(); + for (Class column : columnTypes) { + if (column == null) { + throw new IllegalArgumentException("Null column"); + } + String type = typesStringMap.get(column); + if (type == null) { + throw new IllegalArgumentException("Type is not supported"); + } + tableSignature.append(type); + tableSignature.append(" "); + } + return tableSignature.deleteCharAt(tableSignature.length() - 1).toString(); + } + + public Path getRootDirectoryPath() { + checkClosed(); + return directoryPath; + } + + private StoreableTable generateTable(String name, List> types) { + return new StoreableTable(name, this, types); + } + + @Override + public void removeTable(String name) throws IOException { + checkClosed(); + assertArgumentNotNull(name, "name"); + lock.writeLock().lock(); + try { + StoreableTable table = (StoreableTable) getTable(name); + if (table == null) { + throw new IllegalStateException("There is no table with name \"" + name + "\""); + } + try { + table.drop(); + } catch (DatabaseFileStructureException | LoadOrSaveException e) { + throwIOException(e); + } + tables.remove(name); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public Storeable deserialize(Table table, String value) throws ParseException { + checkClosed(); + assertArgumentNotNull(table, "table"); + assertArgumentNotNull(value, "value"); + List data; + try { + data = (List) JsonParser.parseJson(value); + } catch (BrokenJsonException e) { + throw new ParseException("Can't parse JSON: " + e.getMessage(), e.getOffsetError()); + } catch (ClassCastException e) { + throw new ParseException("Wrong JSON: not a list", 0); + } + if (data.size() == table.getColumnsCount()) { + Storeable storable = createFor(table); + for (int i = 0; i < data.size(); i++) { + if (data.get(i) == null) { + storable.setColumnAt(i, null); + } else if (data.get(i).getClass() == table.getColumnType(i)) { + storable.setColumnAt(i, data.get(i)); + } else if (!tryCastInteger(table, i, data.get(i), storable) + && !tryCastFloat(table, i, data.get(i), storable)) { + throw new ParseException("Wrong data type in column number " + i, -1); + } + } + return storable; + } else { + throw new ParseException("Wrong size of list: have " + data.size() + ", table have " + + table.getColumnsCount() + " columns.", value.length() - 1); + } + } + + private boolean tryCastInteger(Table table, int column, Object value, Storeable result) { + if (table.getColumnType(column) == Integer.class || table.getColumnType(column) == Byte.class) { + if (value.getClass() == Long.class) { + if (table.getColumnType(column) == Integer.class) { + long val = (long) value; + if (val >= Integer.MIN_VALUE && val <= Integer.MAX_VALUE) { + result.setColumnAt(column, new Integer((int) val)); + return true; + } + } else { + if (table.getColumnType(column) == Byte.class) { + long val = (long) value; + if (val >= Byte.MIN_VALUE && val <= Byte.MAX_VALUE) { + result.setColumnAt(column, new Byte((byte) val)); + return true; + } + } + } + } + } + return false; + } + + private boolean tryCastFloat(Table table, int column, Object value, Storeable result) { + if (table.getColumnType(column) == Float.class) { + if (value.getClass() == Double.class) { + if (table.getColumnType(column) == Float.class) { + double val = (double) value; + if (Math.abs(val) >= Float.MIN_VALUE && val <= Float.MAX_VALUE) { + result.setColumnAt(column, (float) val); + return true; + } + } + } + } + return false; + } + + @Override + public String serialize(Table table, Storeable value) throws ColumnFormatException { + checkClosed(); + assertArgumentNotNull(table, "table"); + assertArgumentNotNull(value, "value"); + List data = new ArrayList<>(); + for (int i = 0; i < table.getColumnsCount(); i++) { + data.add(value.getColumnAt(i)); + } + try { + return JsonParser.getJson(data); + } catch (JsonUnsupportedObjectException e) { + throw new ColumnFormatException("Unknown column format", e); + } + } + + @Override + public Storeable createFor(Table table) { + checkClosed(); + assertArgumentNotNull(table, "table"); + return new ru.fizteh.fivt.students.LebedevAleksey.storeable.Storeable(Arrays.asList( + new Object[table.getColumnsCount()]), table); + } + + @Override + public Storeable createFor(Table table, List values) throws ColumnFormatException, IndexOutOfBoundsException { + checkClosed(); + assertArgumentNotNull(table, "table"); + assertArgumentNotNull(values, "values"); + return new ru.fizteh.fivt.students.LebedevAleksey.storeable.Storeable((List) values, table); + } + + @Override + public List getTableNames() { + checkClosed(); + lock.readLock().lock(); + final List result = new ArrayList<>(tables.size()); + tables.keySet().forEach((String s) -> result.add(s)); + lock.readLock().unlock(); + return result; + } + + private void assertArgumentNotNull(Object argument, String name) { + if (argument == null) { + throw new IllegalArgumentException("Argument \"" + name + "\" is null"); + } + } + + public List> listTables() throws IOException { + checkClosed(); + final List> result = new ArrayList<>(tables.size()); + lock.readLock().lock(); + try { + tables.forEach(new BiConsumer() { + @Override + public void accept(String s, StoreableTable table) { + result.add(new Pair(s, table.size())); + } + }); + } catch (DatabaseException e) { + throwIOException(e.getCause()); + } finally { + lock.readLock().unlock(); + } + return result; + } + + @Override + public void close() throws Exception { + checkClosed(); + final List exceptions = new ArrayList<>(); + tables.forEach((s, table) -> { + try { + table.close(); + } catch (Exception e) { + exceptions.add(e); + } + }); + closed.set(true); + if (exceptions.size() > 0) { + for (int i = 0; i + 1 < exceptions.size(); i++) { + exceptions.get(i + 1).addSuppressed(exceptions.get(i)); + } + throw exceptions.get(0); + } + } + + @Override + public String toString() { + checkClosed(); + return getClass().getSimpleName() + "[" + getRootDirectoryPath().toString() + "]"; + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/DatabaseFactory.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/DatabaseFactory.java new file mode 100644 index 000000000..ecb426a64 --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/DatabaseFactory.java @@ -0,0 +1,50 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable; + +import ru.fizteh.fivt.storage.structured.TableProvider; +import ru.fizteh.fivt.storage.structured.TableProviderFactory; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +public class DatabaseFactory implements TableProviderFactory, AutoCloseable { + final AtomicBoolean closed = new AtomicBoolean(false); + List databases = new ArrayList<>(); + + @Override + public synchronized TableProvider create(String path) throws IOException { + checkClosed(); + Database database = new Database(path); + databases.add(database); + return database; + } + + private void checkClosed() { + if (closed.get()) { + throw new IllegalStateException("TableProvider is closed"); + } + } + + @Override + public void close() throws Exception { + checkClosed(); + final List exceptions = new ArrayList<>(); + for (Database item : databases) { + try { + item.close(); + } catch (IllegalStateException e) { + // Already closed, suppress + } catch (Exception e) { + exceptions.add(e); + } + } + closed.set(true); + if (exceptions.size() > 0) { + for (int i = 0; i + 1 < exceptions.size(); i++) { + exceptions.get(i + 1).addSuppressed(exceptions.get(i)); + } + throw exceptions.get(0); + } + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/DatabaseState.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/DatabaseState.java new file mode 100644 index 000000000..c2718b905 --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/DatabaseState.java @@ -0,0 +1,33 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable; + +import ru.fizteh.fivt.students.LebedevAleksey.MultiFileHashMap.DatabaseFileStructureException; +import ru.fizteh.fivt.students.LebedevAleksey.storeable.interpreter.InterpreterState; + +import java.io.IOException; + +public class DatabaseState extends InterpreterState { + private Database database; + private StoreableTable table; + + public StoreableTable getCurrentTable() { + return table; + } + + public void setCurrentTable(StoreableTable table) { + this.table = table; + } + + public Database getDatabase() { + return database; + } + + + public DatabaseState() throws IOException, DatabaseFileStructureException { + String directoryPath = System.getProperty("fizteh.db.dir"); + if (directoryPath == null) { + throw new DatabaseFileStructureException("Database directory doesn't set"); + } else { + database = new Database(directoryPath); + } + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/Main.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/Main.java new file mode 100644 index 000000000..50d4e40ce --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/Main.java @@ -0,0 +1,230 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable; + +import ru.fizteh.fivt.storage.structured.Storeable; +import ru.fizteh.fivt.storage.structured.Table; +import ru.fizteh.fivt.students.LebedevAleksey.MultiFileHashMap.DatabaseFileStructureException; +import ru.fizteh.fivt.students.LebedevAleksey.MultiFileHashMap.Pair; +import ru.fizteh.fivt.students.LebedevAleksey.junit.DatabaseException; +import ru.fizteh.fivt.students.LebedevAleksey.storeable.interpreter.*; + +import java.io.IOException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class Main { + public static void main(String[] args) throws IOException { + runInterpreter(args, getCommands()); + } + + public static void runInterpreter(String[] args, List commands) { + try { + DatabaseState state = new DatabaseState(); + Interpreter interpreter = new Interpreter(commands, state, new StreamsContainer()); + interpreter.run(args); + } catch (DatabaseFileStructureException | IOException ex) { + System.err.println(ex.getMessage()); + System.exit(3); + } + } + + public static List getCommands() { + List commands = Arrays.asList(new Command[]{new Command("exit", 0) { + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException { + StoreableTable currentTable = toDatabaseState(state).getCurrentTable(); + int changesCount = 0; + if (currentTable != null) { + changesCount = currentTable.changesCount(); + } + if (changesCount == 0) { + state.exit(); + return false; + } else { + streams.getOut().println(changesCount + " unsaved changes"); + } + return true; + } + }, new Command("show", 1) { + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException { + if (arguments[0].equals("tables")) { + List> tables; + try { + tables = toDatabaseState(state).getDatabase().listTables(); + } catch (IOException e) { + streams.getErr().println(e.getMessage()); + return false; + } + for (Pair table : tables) { + streams.getOut().println(table.getKey() + " " + table.getValue()); + } + return true; + } else { + throw new ArgumentException("Unknown argument in show command"); + } + } + }, new Command("create") { + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException { + if (arguments.length >= 2) { + if (arguments[1].startsWith("(") && arguments[arguments.length - 1].endsWith(")")) { + String name = arguments[0]; + try { + String[] types = Arrays.copyOfRange(arguments, 1, arguments.length); + types[0] = types[0].substring(1); + String lastType = types[types.length - 1]; + types[types.length - 1] = lastType.substring(0, lastType.length() - 1); + if (types.length == 1 && types[0].equals("")) { + types = new String[0]; + } + if (toDatabaseState(state).getDatabase(). + createTable(name, Database.parseTypes(types)) == null) { + streams.getOut().println(name + " exists"); + return true; + } + streams.getOut().println("created"); + return true; + } catch (IOException | DatabaseException e) { + streams.getErr().println(e.getMessage()); + return false; + } catch (ParseException e) { + streams.getErr().println("wrong type (" + e.getMessage() + ")"); + return false; + } + } else { + throw new ArgumentException("There is no correct information about columns"); + } + } else { + throw new ArgumentException("Not enough arguments"); + } + } + }, new Command("drop", 1) { + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException { + String name = arguments[0]; + try { + DatabaseState dbState = toDatabaseState(state); + if (dbState.getCurrentTable() != null && dbState.getCurrentTable().getName().equals(name)) { + dbState.setCurrentTable(null); + } + dbState.getDatabase().removeTable(name); + streams.getOut().println("dropped"); + } catch (IllegalStateException ex) { + streams.getOut().println(name + " not exists"); + return false; + } catch (IOException e) { + streams.getErr().println(e.getMessage()); + return false; + } + return true; + } + }, new Command("use", 1) { + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException { + StoreableTable currentTable; + int changesCount = 0; + currentTable = toDatabaseState(state).getCurrentTable(); + if (currentTable != null) { + changesCount = currentTable.changesCount(); + } + if (changesCount == 0) { + String name = arguments[0]; + Table table = toDatabaseState(state).getDatabase().getTable(name); + if (table == null) { + streams.getOut().println(name + " not exists"); + } else { + streams.getOut().println("using " + name); + toDatabaseState(state).setCurrentTable((StoreableTable) table); + } + } else { + streams.getOut().println(changesCount + " unsaved changes"); + } + return true; + } + }}); + List array = new ArrayList<>(); + array.addAll(commands); + array.addAll(getTableCommands()); + return array; + } + + public static DatabaseState toDatabaseState(InterpreterState state) { + return (DatabaseState) state; + } + + private static List getTableCommands() { + return Arrays.asList(new TableCommand("list", 0) { + @Override + protected void mainAction(Table table, String[] arguments, StreamsContainer streams, Database database) + throws IOException { + List result = table.list(); + for (int i = 0; i < result.size(); i++) { + if (i > 0) { + streams.getOut().print(", "); + } + streams.getOut().print(result.get(i)); + } + streams.getOut().println(); + } + }, new TableCommand("put", 2) { + @Override + protected void mainAction(Table table, String[] arguments, StreamsContainer streams, Database database) + throws IOException, ParseException { + Storeable result = table.put(arguments[0], database.deserialize(table, arguments[1])); + if (result == null) { + streams.getOut().println("new"); + } else { + streams.getOut().println("overwrite"); + streams.getOut().println(database.serialize(table, result)); + } + } + }, new TableCommand("get", 1) { + @Override + protected void mainAction(Table table, String[] arguments, StreamsContainer streams, Database database) + throws IOException { + Storeable result = table.get(arguments[0]); + if (result == null) { + streams.getOut().println("not found"); + } else { + streams.getOut().println("found"); + streams.getOut().println(database.serialize(table, result)); + } + } + }, new TableCommand("remove", 1) { + @Override + protected void mainAction(Table table, String[] arguments, StreamsContainer streams, Database database) + throws IOException { + if (table.remove(arguments[0]) != null) { + streams.getOut().println("removed"); + } else { + streams.getOut().println("not found"); + } + } + }, new TableCommand("commit", 0) { + @Override + protected void mainAction(Table table, String[] arguments, StreamsContainer streams, Database database) + throws IOException { + streams.getOut().println(table.commit()); + } + }, new TableCommand("rollback", 0) { + @Override + protected void mainAction(Table table, String[] arguments, StreamsContainer streams, Database database) + throws IOException { + streams.getOut().println(table.rollback()); + } + }, new TableCommand("size", 0) { + @Override + protected void mainAction(Table table, String[] arguments, StreamsContainer streams, Database database) + throws IOException { + streams.getOut().println(table.size()); + } + }); + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/Storeable.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/Storeable.java new file mode 100644 index 000000000..dad5c94e6 --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/Storeable.java @@ -0,0 +1,114 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable; + +import ru.fizteh.fivt.storage.structured.ColumnFormatException; +import ru.fizteh.fivt.storage.structured.Table; + +import java.util.List; + +public class Storeable implements ru.fizteh.fivt.storage.structured.Storeable { + public static final String INCORRECT_TYPE_MESSAGE = "Value type isn't correct."; + private List data; + private Table table; + + public Storeable(List data, Table table) throws ColumnFormatException { + if (data == null) { + throw new IllegalArgumentException("Argument \"data\" is null"); + } + if (table == null) { + throw new IllegalArgumentException("Argument \"table\" is null"); + } + if (data.size() != table.getColumnsCount()) { + throw new IndexOutOfBoundsException("Argument arrays are different sizes"); + } + for (int i = 0; i < data.size(); ++i) { + Object item = data.get(i); + if (item != null) { + if (item.getClass() != table.getColumnType(i)) { + throw new ColumnFormatException("Wrong type of column number " + i); + } + } + } + this.data = data; + this.table = table; + } + + @Override + public void setColumnAt(int columnIndex, Object value) throws ColumnFormatException, IndexOutOfBoundsException { + if (value == null) { + data.set(columnIndex, value); + } else { + if (table.getColumnType(columnIndex) == value.getClass()) { + data.set(columnIndex, value); + } else { + throw new ColumnFormatException(INCORRECT_TYPE_MESSAGE); + } + } + } + + @Override + public Object getColumnAt(int columnIndex) throws IndexOutOfBoundsException { + return data.get(columnIndex); + } + + @Override + public Integer getIntAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException { + return (Integer) assertColumnType(columnIndex, Integer.class); + } + + private Object assertColumnType(int columnIndex, Class type) { + if (table.getColumnType(columnIndex) == type) { + return data.get(columnIndex); + } else { + throw new ColumnFormatException("This column type is not " + type.toString()); + } + } + + @Override + public Long getLongAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException { + return (Long) assertColumnType(columnIndex, Long.class); + } + + @Override + public Byte getByteAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException { + return (byte) assertColumnType(columnIndex, Byte.class); + } + + @Override + public Float getFloatAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException { + return (float) assertColumnType(columnIndex, Float.class); + } + + @Override + public Double getDoubleAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException { + return (double) assertColumnType(columnIndex, Double.class); + } + + @Override + public Boolean getBooleanAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException { + return (boolean) assertColumnType(columnIndex, Boolean.class); + } + + @Override + public String getStringAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException { + return (String) assertColumnType(columnIndex, String.class); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(getClass().getSimpleName()); + builder.append("["); + boolean first = true; + for (Object item : data) { + if (first) { + first = false; + } else { + builder.append(","); + } + if (item != null) { + builder.append(item.toString()); + } + } + builder.append("]"); + return builder.toString(); + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/StoreableTable.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/StoreableTable.java new file mode 100644 index 000000000..d32dae9cd --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/StoreableTable.java @@ -0,0 +1,322 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable; + +import ru.fizteh.fivt.storage.structured.ColumnFormatException; +import ru.fizteh.fivt.storage.structured.Storeable; +import ru.fizteh.fivt.students.LebedevAleksey.MultiFileHashMap.DatabaseFileStructureException; +import ru.fizteh.fivt.students.LebedevAleksey.MultiFileHashMap.LoadOrSaveException; +import ru.fizteh.fivt.students.LebedevAleksey.MultiFileHashMap.Table; +import ru.fizteh.fivt.students.LebedevAleksey.junit.DatabaseException; + +import java.io.IOException; +import java.nio.file.Path; +import java.text.ParseException; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Consumer; + +public class StoreableTable implements ru.fizteh.fivt.storage.structured.Table, AutoCloseable { + private final String name; + private final Database database; + private final ThreadLocal> changedKeys = new ThreadLocal>() { + @Override + protected Map initialValue() { + return new TreeMap<>(); + } + }; + private Table stringTable; + private List> columnTypes; + private ReadWriteLock lock = new ReentrantReadWriteLock(true); + private AtomicBoolean closed = new AtomicBoolean(false); + + public StoreableTable(String name, Database databaseParent, List> types) { + lock.writeLock().lock(); + this.name = name; + database = databaseParent; + columnTypes = types; + stringTable = new Table(name, databaseParent.getRootDirectoryPath()); + lock.writeLock().unlock(); + } + + private void checkKeyNotNull(String key) { + if (key == null) { + throw new IllegalArgumentException("Argument \"key\" is null"); + } + } + + private void checkKeyValueNotNull(String key, Storeable value) { + checkKeyNotNull(key); + if (value == null) { + throw new IllegalArgumentException("Argument \"value\" is null"); + } + } + + private String putStrings(String key, String value) throws DatabaseFileStructureException, LoadOrSaveException { + String oldValue = getString(key); + if (value.equals(stringTable.get(key))) { + changedKeys.get().remove(key); + } else { + changedKeys.get().put(key, value); + } + return oldValue; + } + + private void checkClosed() { + if (closed.get()) { + throw new IllegalStateException("Table is closed"); + } + } + + private String getString(String key) throws LoadOrSaveException, DatabaseFileStructureException { + if (changedKeys.get().containsKey(key)) { + return changedKeys.get().get(key); + } else { + return stringTable.get(key); + } + } + + @Override + public Storeable put(String key, Storeable value) throws ColumnFormatException { + checkClosed(); + checkKeyValueNotNull(key, value); + lock.readLock().lock(); + String result; + try { + result = putStrings(key, database.serialize(this, value)); + } catch (DatabaseFileStructureException | LoadOrSaveException e) { + throw new DatabaseException(e); + } finally { + lock.readLock().unlock(); + } + if (result == null) { + return null; + } else { + try { + return database.deserialize(this, result); + } catch (ParseException e) { + throw new ColumnFormatException(e); + } + } + } + + @Override + public Storeable remove(String key) { + checkClosed(); + checkKeyNotNull(key); + String value; + lock.readLock().lock(); + try { + value = stringTable.get(key); + Storeable oldValue = get(key); + if (value != null) { + if (oldValue != null) { + changedKeys.get().put(key, null); + } + } else { + changedKeys.get().remove(key); + } + return oldValue; + } catch (LoadOrSaveException | DatabaseFileStructureException e) { + throw new DatabaseException(e); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public int size() { + checkClosed(); + try { + int deletedCount = 0; + int addedCount = 0; + for (String key : changedKeys.get().keySet()) { + String value = changedKeys.get().get(key); + if (value == null) { + ++deletedCount; + } else { + if (stringTable.get(key) == null) { + addedCount++; + } + } + } + lock.readLock().lock(); + return stringTable.count() + addedCount - deletedCount; + } catch (LoadOrSaveException | DatabaseFileStructureException e) { + throw new DatabaseException(e); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public int commit() throws IOException { + checkClosed(); + lock.writeLock().lock(); + int changes = changesCount(); + try { + for (String key : changedKeys.get().keySet()) { + String value = changedKeys.get().get(key); + if (value == null) { + stringTable.remove(key); + } else { + stringTable.put(key, value); + } + } + changedKeys.get().clear(); + stringTable.save(); + } catch (DatabaseFileStructureException | LoadOrSaveException e) { + Database.throwIOException(e); + } finally { + lock.writeLock().unlock(); + } + return changes; + } + + public int changesCount() { + checkClosed(); + return changedKeys.get().size(); + } + + @Override + public int rollback() { + checkClosed(); + int changes = changesCount(); + changedKeys.get().clear(); + return changes; + } + + @Override + public int getNumberOfUncommittedChanges() { + return changesCount(); + } + + @Override + public String getName() { + checkClosed(); + lock.readLock().lock(); + String name = this.name; + lock.readLock().unlock(); + return name; + + } + + @Override + public Storeable get(String key) { + checkClosed(); + checkKeyNotNull(key); + lock.readLock().lock(); + String result; + try { + result = getString(key); + } catch (LoadOrSaveException | DatabaseFileStructureException e) { + throw new DatabaseException(e); + } finally { + lock.readLock().unlock(); + } + try { + if (result == null) { + return null; + } else { + return database.deserialize(this, result); + } + } catch (ParseException e) { + throw new DatabaseException(e); + } + } + + @Override + public int getColumnsCount() { + checkClosed(); + lock.readLock().lock(); + int size = columnTypes.size(); + lock.readLock().unlock(); + return size; + } + + @Override + public Class getColumnType(int columnIndex) throws IndexOutOfBoundsException { + checkClosed(); + lock.readLock().lock(); + Class result = columnTypes.get(columnIndex); + lock.readLock().unlock(); + return result; + } + + public void drop() throws DatabaseFileStructureException, LoadOrSaveException { + checkClosed(); + lock.writeLock().lock(); + try { + Path signatureFile = database.getRootDirectoryPath().resolve(getName()). + resolve(Database.TABLE_SIGNATURE_FILE_NAME); + if (!signatureFile.toFile().delete()) { + throw new LoadOrSaveException("Can't delete signature file for table " + getName()); + } + stringTable.drop(); + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public List list() { + checkClosed(); + lock.readLock().lock(); + Set items; + try { + items = new TreeSet<>(stringTable.list()); + } catch (DatabaseFileStructureException | LoadOrSaveException e) { + throw new DatabaseException(e); + } finally { + lock.readLock().unlock(); + } + for (String key : changedKeys.get().keySet()) { + String value = changedKeys.get().get(key); + if (value == null) { + items.remove(key); + } else { + items.add(key); + } + } + final ArrayList result = new ArrayList<>(items.size()); + items.forEach(new Consumer() { + @Override + public void accept(String s) { + result.add(s); + } + }); + return result; + } + + @Override + public void close() throws Exception { + checkClosed(); + Throwable rollbackException = null; + try { + rollback(); + } catch (Throwable e) { + rollbackException = e; + } finally { + try { + database.reloadTable(this); + if (rollbackException != null) { + Exception e = (Exception) rollbackException; + rollbackException = null; + throw e; + } + } catch (Exception e) { + if (rollbackException != null) { + e.addSuppressed(rollbackException); + } + } finally { + closed.set(true); + } + } + } + + @Override + public String toString() { + checkClosed(); + return getClass().getSimpleName() + "[" + database.getRootDirectoryPath().resolve(name).toString() + "]"; + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/TableCommand.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/TableCommand.java new file mode 100644 index 000000000..1d46b6bd0 --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/TableCommand.java @@ -0,0 +1,46 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable; + + +import ru.fizteh.fivt.storage.structured.Table; +import ru.fizteh.fivt.students.LebedevAleksey.MultiFileHashMap.TableNotFoundException; +import ru.fizteh.fivt.students.LebedevAleksey.junit.DatabaseException; +import ru.fizteh.fivt.students.LebedevAleksey.storeable.interpreter.*; + +import java.io.IOException; +import java.text.ParseException; + +public abstract class TableCommand extends Command { + public TableCommand(String commandName, int argCount) { + super(commandName, argCount); + } + + private static Table getCurrentTable(InterpreterState state) throws TableNotFoundException { + Table table = ((DatabaseState) state).getCurrentTable(); + if (table == null) { + System.out.println("no table"); + throw new TableNotFoundException("No table selected"); + } else { + return table; + } + } + + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException { + try { + mainAction(getCurrentTable(state), arguments, streams, ((DatabaseState) state).getDatabase()); + return true; + } catch (DatabaseException | IOException e) { + streams.getErr().println(e.getMessage()); + return false; + } catch (TableNotFoundException e) { + return false; + } catch (ParseException e) { + streams.getErr().println("wrong type (" + e.getMessage() + ")"); + return false; + } + } + + protected abstract void mainAction(Table table, String[] arguments, StreamsContainer streams, Database database) + throws IOException, ParseException; +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/interpreter/ArgumentException.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/interpreter/ArgumentException.java new file mode 100644 index 000000000..e4206d659 --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/interpreter/ArgumentException.java @@ -0,0 +1,11 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable.interpreter; + +public class ArgumentException extends ParserException { + public ArgumentException(String message) { + super(message); + } + + public ArgumentException(String message, Throwable ex) { + super(message, ex); + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/interpreter/Command.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/interpreter/Command.java new file mode 100644 index 000000000..701cfc2fa --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/interpreter/Command.java @@ -0,0 +1,37 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable.interpreter; + +public abstract class Command { + private String name; + private int argumentsCount; + + public Command(String commandName, int argCount) { + name = commandName; + argumentsCount = argCount; + } + + public Command(String commandName) { + name = commandName; + argumentsCount = -1; + } + + public boolean invoke(InterpreterState interpreterState, ParsedCommand command, StreamsContainer streams) + throws ArgumentException, ParserException { + if (!command.getCommandName().equals(name)) { + throw new IllegalArgumentException("Wrong interpreter command: " + command.getCommandName() + + ", but expected " + name); + } + if (argumentsCount >= 0 && command.getArguments().length != argumentsCount) { + throw new ArgumentException("Invalid number of arguments: " + argumentsCount + + " expected, " + command.getArguments().length + " found"); + } else { + return action(interpreterState, command.getArguments(), streams); + } + } + + protected abstract boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException; + + public String getName() { + return name; + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/interpreter/Interpreter.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/interpreter/Interpreter.java new file mode 100644 index 000000000..510aae89d --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/interpreter/Interpreter.java @@ -0,0 +1,343 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable.interpreter; + +import java.util.*; + +public class Interpreter { + public static final String SLASH_SPECIAL_STRING = "#s"; + public static final String QUOTE_SPECIAL_STRING = "#q"; + public static final String SEMICOLON_SPECIAL_STRING = "#d"; + private boolean hasCorrectTerminated = false; + + private InterpreterState currentState; + private StreamsContainer streams; + private List commandsList; + private int exitCode = 0; + + public Interpreter(List commands, InterpreterState interpreterState, StreamsContainer container) { + commandsList = commands; + currentState = interpreterState; + streams = container; + } + + private static ArrayList splitArguments(ArrayList currentCommand) + throws CannotParseCommandException { + ArrayList arguments = new ArrayList<>(); + for (int i = 0; i < currentCommand.size(); ++i) { + CommandToken token = currentCommand.get(i); + if (token.isWasInQuotes()) { + arguments.add(token.getValue()); + if (i + 1 < currentCommand.size() && (!currentCommand.get(i + 1).isWasInQuotes())) { + currentCommand.get(i + 1).setValue(trimOneStartSpace(currentCommand.get(i + 1).getValue())); + } + } else { + if (i + 1 < currentCommand.size() && currentCommand.get(i + 1).isWasInQuotes()) { + currentCommand.get(i).setValue(trimOneEndSpace(currentCommand.get(i).getValue())); + } + if (token.getValue().length() > 0) { + arguments.addAll(Arrays.asList(token.getValue().split("\\s"))); + } + } + } + ArrayList emptyArgs = new ArrayList<>(); + for (String arg : arguments) { + if (arg.equals("")) { + emptyArgs.add(arg); + } + } + arguments.removeAll(emptyArgs); + return arguments; + } + + private static String trimOneEndSpace(String line) throws CannotParseCommandException { + if (line.length() != 0) { + char endChar = line.charAt(line.length() - 1); + if (endChar == ' ' || endChar == '\t') { + return line.substring(0, line.length() - 1); + } else { + throw new CannotParseCommandException("Where is no space between to arguments."); + } + } else { + return line; + } + } + + private static String trimOneStartSpace(String line) throws CannotParseCommandException { + if (line.length() == 0) { + return line; + } else { + char startChar = line.charAt(0); + if (startChar == ' ' || startChar == '\t') { + return line.substring(1); + } else { + throw new CannotParseCommandException("Where is no space between to arguments."); + } + } + } + + private List parseCommand(String input) throws ParserException { + input = replaceSpecialChars(input); + List tokensByQuote = splitCommandByQuote(input); + ArrayList> commandsTokens = splitCommands(tokensByQuote); + return getParsedCommands(commandsTokens); + } + + private String replaceSpecialChars(String input) { + input = input.replace("#", "##"); + input = input.replace("\\\\", SLASH_SPECIAL_STRING); + input = input.replace("\\\"", QUOTE_SPECIAL_STRING); + input = input.replace("\\;", SEMICOLON_SPECIAL_STRING); + return input; + } + + private List parseCommand(String[] input) throws ParserException { + ArrayList> commandsTokens = new ArrayList<>(); + commandsTokens.add(new ArrayList<>()); + for (String arg : input) { + String[] tokens = replaceSpecialChars(arg).split(";", -1); + addAtEnd(commandsTokens, tokens[0]); + for (int i = 1; i < tokens.length; ++i) { + commandsTokens.add(new ArrayList<>()); + addAtEnd(commandsTokens, tokens[i]); + } + } + return getParsedCommands(commandsTokens); + } + + private void addAtEnd(ArrayList> commandsTokens, String arg) { + commandsTokens.get(commandsTokens.size() - 1).add(new CommandToken(arg, false)); + } + + private ArrayList> splitCommands(List tokensByQuote) { + ArrayList> commandsTokens = new ArrayList<>(tokensByQuote.size()); + commandsTokens.add(new ArrayList<>()); + for (CommandToken token : tokensByQuote) { + if (token.isWasInQuotes()) { + commandsTokens.get(commandsTokens.size() - 1).add(token); + } else { + String[] tokens = token.getValue().split(";", -1); + for (int j = 0; j < tokens.length; ++j) { + if (j > 0) { + commandsTokens.add(new ArrayList<>()); + } + addAtEnd(commandsTokens, tokens[j]); + } + } + } + return commandsTokens; + } + + private List splitCommandByQuote(String input) throws CannotParseCommandException { + List result = new ArrayList<>(); + int startIndex = 0; + while (startIndex >= 0) { + int index = input.indexOf('"', startIndex); + if (index < 0) { + result.add(new CommandToken(input.substring(startIndex), false)); + } else { + result.add(new CommandToken(input.substring(startIndex, index), false)); + startIndex = index + 1; + index = input.indexOf('"', startIndex); + if (index < 0) { + throw new CannotParseCommandException("Wrong quote structure."); + } else { + result.add(new CommandToken(input.substring(startIndex, index), true)); + } + } + startIndex = index; + if (startIndex > 0) { + ++startIndex; + } + } + return result; + } + + private List getParsedCommands(ArrayList> commandsTokens) + throws CannotParseCommandException { + List commands = new ArrayList<>(commandsTokens.size()); + for (ArrayList currentCommand : commandsTokens) { + ArrayList arguments = splitArguments(currentCommand); + ParsedCommand result = new ParsedCommand(); + result.setCommandName(((arguments.size() > 0) ? removeSpecialChars(arguments.get(0)) : null)); + String[] realArguments = new String[(arguments.size() > 0) ? arguments.size() - 1 : 0]; + for (int i = 1; i < arguments.size(); ++i) { + realArguments[i - 1] = removeSpecialChars(arguments.get(i)); + } + result.setArguments(realArguments); + commands.add(result); + } + + return commands; + } + + private String removeSpecialChars(String text) { + text = replaceSpecialString(text, SLASH_SPECIAL_STRING, "\\"); + text = replaceSpecialString(text, SEMICOLON_SPECIAL_STRING, ";"); + text = replaceSpecialString(text, QUOTE_SPECIAL_STRING, "\""); + return text.replace("##", "#"); + } + + private String replaceSpecialString(String text, String mask, String maskValue) { + int index = 0; + do { + index = text.indexOf(mask, index); + if (index >= 0) { + int j = index - 1; + while (j >= 0 && text.charAt(j) == '#') { + j--; + } + if ((index - j) % 2 == 0) { + ++index; + } else { + text = text.substring(0, index) + maskValue + text.substring(index + mask.length()); + } + } + } + while (index >= 0); + return text; + } + + public final void run(String[] args) { + if (args.length == 0) { + Scanner scanner = new Scanner(streams.getIn()); + String command; + do { + try { + streams.getOut().print("$ "); + command = scanner.nextLine(); + invokeCommand(command); + } catch (NoSuchElementException ex) { + streams.getErr().println("Error: Can not read"); + break; + } + } while (!isCorrectTerminated()); + } else { + if (invokeCommand(args)) { + hasCorrectTerminated = true; + } + } + if (!isCorrectTerminated()) { + exitCode = 1; + } + } + + public boolean invokeCommand(String input) { + try { + List commands = parseCommand(input); + return invokeCommands(commands); + } catch (CommandInvokeException ex) { + printInvokeError(ex); + } catch (ParserException ex) { + streams.getErr().println("Error: " + ex.getMessage()); + } + streams.getErr().flush(); + return false; + } + + protected void printInvokeError(CommandInvokeException ex) { + streams.getErr().println(new StringBuilder().append(ex.getCommandName()).append(": ").append(ex.getMessage())); + } + + public boolean invokeCommand(String[] input) { + try { + List commands = parseCommand(input); + return invokeCommands(commands); + } catch (CommandInvokeException ex) { + streams.getErr().println(new StringBuilder().append(ex.getCommandName()).append(": ") + ex.getMessage()); + } catch (ParserException ex) { + streams.getErr().println("Error: " + ex.getMessage()); + } + streams.getErr().flush(); + return false; + } + + protected boolean invokeCommands(List commands) throws ParserException { + String lastCommand = ""; + for (ParsedCommand command : commands) { + if (command.getCommandName() != null) { + try { + boolean foundCommand = false; + lastCommand = command.getCommandName(); + for (Command cmd : commandsList) { + if (cmd.getName().equals(command.getCommandName())) { + foundCommand = true; + if (!cmd.invoke(currentState, command, streams)) { + if (currentState.exited()) { + return exit(); + } else { + return false; + } + } + break; + } + } + if (!foundCommand) { + throw new ParserException("This command is unknown: " + command.getCommandName()); + } + } catch (ArgumentException ex) { + streams.getErr().println(lastCommand + ": " + ex.getMessage()); + return false; + } + } + } + return true; + } + + protected final boolean exit() { + this.hasCorrectTerminated = true; + return false; + } + + public boolean isCorrectTerminated() { + return hasCorrectTerminated; + } + + public int getExitCode() { + return exitCode; + } + + private static class CommandToken { + private String value; + private boolean wasInQuotes; + + CommandToken(String value, boolean wasInQuotes) { + this.value = value; + this.wasInQuotes = wasInQuotes; + } + + String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + boolean isWasInQuotes() { + return wasInQuotes; + } + } +} + +class CannotParseCommandException extends ParserException { + CannotParseCommandException(String message) { + super(message); + } +} + +class CommandInvokeException extends ParserException { + private final String commandName; + + CommandInvokeException(String message, String commandName) { + super(message); + this.commandName = commandName; + } + + CommandInvokeException(String message, String commandName, Throwable ex) { + super(message, ex); + this.commandName = commandName; + } + + public String getCommandName() { + return commandName; + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/interpreter/InterpreterState.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/interpreter/InterpreterState.java new file mode 100644 index 000000000..82417b40d --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/interpreter/InterpreterState.java @@ -0,0 +1,13 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable.interpreter; + +public class InterpreterState { + private boolean run = true; + + public boolean exited() { + return !run; + } + + public void exit() { + run = false; + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/interpreter/ParsedCommand.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/interpreter/ParsedCommand.java new file mode 100644 index 000000000..1ae27619b --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/interpreter/ParsedCommand.java @@ -0,0 +1,22 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable.interpreter; + +public class ParsedCommand { + private String[] arguments; + private String commandName; + + public String getCommandName() { + return commandName; + } + + public void setCommandName(String commandName) { + this.commandName = commandName; + } + + public String[] getArguments() { + return arguments; + } + + public void setArguments(String[] arguments) { + this.arguments = arguments; + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/interpreter/ParserException.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/interpreter/ParserException.java new file mode 100644 index 000000000..1a5f49602 --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/interpreter/ParserException.java @@ -0,0 +1,11 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable.interpreter; + +public class ParserException extends Exception { + public ParserException(String message) { + super(message); + } + + public ParserException(String message, Throwable ex) { + super(message, ex); + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/interpreter/StreamsContainer.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/interpreter/StreamsContainer.java new file mode 100644 index 000000000..e65ceef9f --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/interpreter/StreamsContainer.java @@ -0,0 +1,36 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable.interpreter; + +import java.io.InputStream; +import java.io.PrintStream; + +public class StreamsContainer { + + private final PrintStream out; + private final PrintStream err; + private final InputStream in; + + public StreamsContainer(PrintStream out, PrintStream err, InputStream in) { + this.out = out; + this.err = err; + this.in = in; + } + + + public StreamsContainer() { + out = System.out; + err = System.err; + in = System.in; + } + + public PrintStream getOut() { + return out; + } + + public PrintStream getErr() { + return err; + } + + public InputStream getIn() { + return in; + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/json/BrokenJsonException.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/json/BrokenJsonException.java new file mode 100644 index 000000000..ba09a1b0f --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/json/BrokenJsonException.java @@ -0,0 +1,19 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable.json; + +public class BrokenJsonException extends Exception { + private final int offsetError; + + public BrokenJsonException(String message, int offsetError) { + super(message); + this.offsetError = offsetError; + } + + public BrokenJsonException(String message, Throwable cause, int offsetError) { + super(message, cause); + this.offsetError = offsetError; + } + + public int getOffsetError() { + return offsetError; + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/json/JsonParser.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/json/JsonParser.java new file mode 100644 index 000000000..3a1c5de87 --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/json/JsonParser.java @@ -0,0 +1,423 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable.json; + +import ru.fizteh.fivt.students.LebedevAleksey.MultiFileHashMap.Pair; + +import java.util.*; + +public abstract class JsonParser { + public static final String UNEXPECTED_STOP_MESSAGE = "Parsers didn't reach the end."; + private static final ThreadLocal NEED_SHORT_INTEGER_TYPES = new ThreadLocal() { + @Override + protected Boolean initialValue() { + return false; + } + }; + + public static void setNeedShortIntegerTypes(boolean needShortIntegerTypes) { + JsonParser.NEED_SHORT_INTEGER_TYPES.set(needShortIntegerTypes); + } + + public static String getJson(Object data) throws JsonUnsupportedObjectException { + StringBuilder result = new StringBuilder(); + createJson(data, result); + return result.toString(); + } + + public static Object parseJson(String json) throws BrokenJsonException { + json = json.trim(); + Pair result; + try { + result = parseJson(json, 0, null); + } catch (JsonTerminatedException e) { + throw new BrokenJsonException("Wrong JSON", json.length() > 0 ? json.length() - 1 : 0); + } + if (result.getValue() != json.length()) { + throw new BrokenJsonException(UNEXPECTED_STOP_MESSAGE, result.getValue()); + } + return result.getKey(); + } + + public static Pair parseJson(String json, int begin, Character possibleTerminator) + throws BrokenJsonException, JsonTerminatedException { + Character first = null; + List list; + Map map; + int i; + for (i = begin; i < json.length(); i++) { + char symbol = json.charAt(i); + if (possibleTerminator != null && possibleTerminator.equals(symbol)) { + throw new JsonTerminatedException(i); + } + switch (symbol) { + case ' ': + break; + case '"': + return parseStringFromJson(json, i + 1); + case '{': + return parseMapFromJson(json, i + 1); + case '[': + return parseArrayFromJson(json, i + 1); + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return parseNumberFromJson(json, i); + case 'n': + case 't': + case 'f': + try { + switch (json.substring(i, i + 4)) { + case "null": + return new Pair<>(null, i + 4); + case "true": + return new Pair<>(true, i + 4); + case "fals": + if ("false".equals(json.substring(i, i + 5))) { + return new Pair<>(false, i + 5); + } else { + throw new BrokenJsonException("Unexpected symbol in position " + i + 4, i + 4); + } + default: + throw new BrokenJsonException("Wrong token", i); + } + } catch (IndexOutOfBoundsException e) { + throw new BrokenJsonException("Unexpected symbol \"" + symbol + "\" in position " + i, e, i); + } + default: + throw new BrokenJsonException("Unexpected symbol \"" + symbol + "\" in position " + i, i); + } + } + throw new BrokenJsonException(UNEXPECTED_STOP_MESSAGE, i); + } + + private static Pair parseMapFromJson(String json, int begin) throws BrokenJsonException { + Map map = new TreeMap<>(); + int i = begin; + while (i < json.length()) { + Pair result = null; + try { + result = parseJson(json, i, '}'); + } catch (JsonTerminatedException e) { + return searchNextChar(json, map, i, '}'); + } + String key; + try { + key = (String) result.getKey(); + } catch (ClassCastException e) { + throw new BrokenJsonException("Wrong map key", i); + } + i = result.getValue(); + boolean exited = false; + for (; i < json.length() && (!exited); ++i) { + char symbol = json.charAt(i); + switch (symbol) { + case ' ': + break; + case ':': + exited = true; + break; + default: + break; + } + } + try { + result = parseJson(json, i, null); + } catch (JsonTerminatedException e) { + throw new BrokenJsonException("JSON is not correct", e.getOffset()); + } + map.put(key, result.getKey()); + i = result.getValue(); + exited = false; + for (; i < json.length() && (!exited); ++i) { + char symbol = json.charAt(i); + switch (symbol) { + case ' ': + break; + case ',': + exited = true; + break; + case '}': + return new Pair<>(map, i + 1); + default: + break; + } + } + } + throw new BrokenJsonException(UNEXPECTED_STOP_MESSAGE, i); + } + + private static Pair searchNextChar(String json, Object value, int i, char mask) { + while (json.charAt(i) != mask) { + ++i; + } + return new Pair<>(value, i + 1); + } + + + private static Pair parseArrayFromJson(String json, int begin) throws BrokenJsonException { + List list = new ArrayList<>(); + int i = begin; + while (i < json.length()) { + Pair result = null; + try { + result = parseJson(json, i, ']'); + } catch (JsonTerminatedException e) { + return searchNextChar(json, list, i, ']'); + } + list.add(result.getKey()); + i = result.getValue(); + boolean exited = false; + for (; i < json.length() && (!exited); ++i) { + char symbol = json.charAt(i); + switch (symbol) { + case ' ': + break; + case ',': + exited = true; + break; + case ']': + return new Pair<>(list, i + 1); + default: + break; + } + } + } + throw new BrokenJsonException(UNEXPECTED_STOP_MESSAGE, i); + } + + private static Pair parseNumberFromJson(String json, int begin) throws BrokenJsonException { + List posibleCharcters = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + posibleCharcters.add((char) ('0' + i)); + } + posibleCharcters.add('+'); + posibleCharcters.add('-'); + posibleCharcters.add('E'); + posibleCharcters.add('e'); + posibleCharcters.add('.'); + int i; + for (i = begin; i < json.length(); i++) { + char symbol = json.charAt(i); + if (!posibleCharcters.contains(symbol)) { + break; + } + } + String number = json.substring(begin, i); + if (NEED_SHORT_INTEGER_TYPES.get()) { + try { + byte byteValue = Byte.parseByte(number); + return new Pair<>(byteValue, i); + } catch (NumberFormatException e) { + // Next type... + } + try { + int intValue = Integer.parseInt(number); + return new Pair<>(intValue, i); + } catch (NumberFormatException e) { + // Next type... + } + } + try { + long longValue = Long.parseLong(number); + return new Pair<>(longValue, i); + } catch (NumberFormatException e) { + // Next type... + } + try { + double doubleValue = Double.parseDouble(number); + return new Pair<>(doubleValue, i); + } catch (NumberFormatException e) { + // Next type... + } + throw new BrokenJsonException("Wrong number", i); + } + + private static Pair parseStringFromJson(String json, int begin) + throws BrokenJsonException { + StringBuilder builder = new StringBuilder(); + for (int i = begin; i < json.length(); i++) { + char symbol = json.charAt(i); + if (symbol == '\\') { + i = parseSlashSequense(json, builder, i); + } else { + if (symbol == '"') { + return new Pair<>(builder.toString(), i + 1); + } else { + builder.append(symbol); + } + } + } + throw new BrokenJsonException(UNEXPECTED_STOP_MESSAGE, json.length() - 1); + } + + private static int parseSlashSequense(String json, StringBuilder builder, int i) throws BrokenJsonException { + if (i + 1 == json.length()) { + throw new BrokenJsonException("String doesn't finish", i); + } + switch (json.charAt(i + 1)) { + case '"': + builder.append("\""); + return i + 1; + case '\\': + builder.append("\\"); + return i + 1; + case '/': + builder.append("/"); + return i + 1; + case 'b': + builder.append("\b"); + return i + 1; + case 'f': + builder.append("\f"); + return i + 1; + case 'n': + builder.append("\n"); + return i + 1; + case 'r': + builder.append("\r"); + return i + 1; + case 't': + builder.append("\t"); + return i + 1; + case 'u': + if (i + 5 >= json.length()) { + throw new BrokenJsonException("Error in \\u in string", i); + } + try { + int num = Integer.parseInt(json.substring(i + 2, i + 6), 16); + builder.append((char) num); + return i + 5; + } catch (NumberFormatException e) { + throw new BrokenJsonException("Error in \\u in string: can't parse int", e, i + 2); + } + default: + throw new BrokenJsonException("Unknown \\ sequence in char number " + i, i); + } + } + + private static void createJson(Object data, StringBuilder builder) throws JsonUnsupportedObjectException { + if (data == null) { + builder.append("null"); + return; + } + if (containsInterface(data, Map.class)) { + Map map; + try { + map = (Map) data; + } catch (ClassCastException e) { + throw new JsonUnsupportedObjectException("Only Map is supported, when you use map", e); + } + createJson(map, builder); + return; + } + if (containsInterface(data, Iterable.class)) { + createJson((Iterable) data, builder); + return; + } + if (containsInterface(data, Object[].class)) { + createJson((Object[]) data, builder); + return; + } + if (data.getClass() == String.class) { + createJson((String) data, builder); + return; + } + if (data.getClass().getSuperclass() == Number.class) { + builder.append(data.toString()); + return; + } + if (data.getClass() == Boolean.class) { + builder.append(((boolean) data) ? "true" : false); + return; + } + throw new JsonUnsupportedObjectException("This type (" + data.getClass() + ") is unsupported by JSON."); + } + + private static void createJson(String data, StringBuilder builder) { + data = data.replace("\\", "\\\\").replace("\"", "\\\""); + data = data.replace("/", "\\/").replace("\b", "\\b"); + data = data.replace("\f", "\\f").replace("\n", "\\n"); + data = data.replace("\r", "\\r").replace("\t", "\\t"); + builder.append("\""); + builder.append(data); + builder.append("\""); + } + + private static boolean containsInterface(Object data, Class type) { + try { + type.cast(data); + return true; + } catch (ClassCastException e) { + return false; + } + } + + private static void createJson(Iterable data, StringBuilder builder) throws JsonUnsupportedObjectException { + builder.append("["); + boolean first = true; + for (Object item : data) { + if (!first) { + builder.append(","); + } else { + first = false; + } + createJson(item, builder); + } + builder.append("]"); + } + + private static void createJson(Object[] data, StringBuilder builder) throws JsonUnsupportedObjectException { + builder.append("["); + boolean first = true; + for (Object item : data) { + if (!first) { + builder.append(","); + } else { + first = false; + } + createJson(item, builder); + } + builder.append("]"); + } + + private static void createJson(Map data, StringBuilder builder) throws JsonUnsupportedObjectException { + try { + builder.append("{"); + Set keys = data.keySet(); + boolean notFirst = false; + for (String item : keys) { + if (notFirst) { + builder.append(","); + } else { + notFirst = true; + } + createJson(item, builder); + builder.append(":"); + createJson(data.get(item), builder); + } + builder.append("}"); + } catch (ClassCastException e) { + throw new JsonUnsupportedObjectException("Only Map is supported.", e); + } + } +} + +class JsonTerminatedException extends Exception { + private final int offset; + + public JsonTerminatedException(int index) { + super(); + offset = index; + } + + public int getOffset() { + return offset; + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/json/JsonUnsupportedObjectException.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/json/JsonUnsupportedObjectException.java new file mode 100644 index 000000000..0316d7259 --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/json/JsonUnsupportedObjectException.java @@ -0,0 +1,18 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable.json; + +public class JsonUnsupportedObjectException extends Exception { + public JsonUnsupportedObjectException() { + } + + public JsonUnsupportedObjectException(String message) { + super(message); + } + + public JsonUnsupportedObjectException(String message, Throwable cause) { + super(message, cause); + } + + public JsonUnsupportedObjectException(Throwable cause) { + super(cause); + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/CommandTest.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/CommandTest.java new file mode 100644 index 000000000..3df6d47f6 --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/CommandTest.java @@ -0,0 +1,215 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable.tests; + +import org.junit.Assert; +import org.junit.Test; +import ru.fizteh.fivt.students.LebedevAleksey.storeable.interpreter.*; + +public class CommandTest { + + public static final int TEST_COUNT = 10; + public static final String TEST_COMMAND_NAME = "testname"; + private InterpreterState state = new InterpreterState(); + private StreamsContainer streams = new StreamsContainer(); + + @Test + public void testInvoke() throws ParserException { + Command command = new Command(TEST_COMMAND_NAME, 2) { + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) { + return arguments[0].equals(arguments[1]); + } + }; + String[] args = new String[]{"1", "1"}; + ParsedCommand information = new ParsedCommand(); + information.setCommandName(TEST_COMMAND_NAME); + information.setArguments(args); + Assert.assertTrue(command.invoke(state, information, streams)); + args[1] = "2"; + Assert.assertFalse(command.invoke(state, information, streams)); + } + + @Test(expected = ArgumentException.class) + public void testWrongArgumentCountException() throws ParserException { + Command cmd = new Command(TEST_COMMAND_NAME, 1) { + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException { + throw new ParserException("Only for test"); + } + }; + ParsedCommand command = new ParsedCommand(); + command.setCommandName(TEST_COMMAND_NAME); + command.setArguments(new String[0]); + cmd.invoke(state, command, streams); + } + + @Test + public void testAutoCheckArguments() { + ParsedCommand[] arguments = new ParsedCommand[TEST_COUNT]; + int[] results = new int[TEST_COUNT]; + for (int i = 0; i < TEST_COUNT; i++) { + arguments[i] = new ParsedCommand(); + arguments[i].setCommandName(TEST_COMMAND_NAME); + String[] args = new String[i]; + int sum = 0; + for (int j = 0; j < i; j++) { + args[j] = j + ""; + sum += j; + } + results[i] = sum; + arguments[i].setArguments(args); + } + for (int i = 0; i < TEST_COUNT; i++) { + final int[] result = {0}; + Command cmd = new Command(TEST_COMMAND_NAME, i) { + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException { + int sum = 0; + for (String str : arguments) { + sum += Integer.parseInt(str); + } + result[0] = sum; + return true; + } + }; + for (int j = 0; j < TEST_COUNT; j++) { + result[0] = 0; + try { + Assert.assertTrue(cmd.invoke(state, arguments[j], streams)); + Assert.assertEquals(result[0], results[j]); + } catch (ParserException e) { + if (j == i) { + Assert.fail("Don't work with arguments"); + } + } + } + } + } + + @Test + public void testManualCheckArguments() throws ParserException { + testManualArgsCheckingWork(new Command(TEST_COMMAND_NAME) { + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException { + if (arguments.length == 2) { + return true; + } else { + throw new ArgumentException("My message"); + } + } + }); + testManualArgsCheckingWork(new Command(TEST_COMMAND_NAME, -1) { + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException { + if (arguments.length == 2) { + return true; + } else { + throw new ArgumentException("My message"); + } + } + }); + } + + private void testManualArgsCheckingWork(Command cmd) throws ParserException { + Assert.assertEquals(TEST_COMMAND_NAME, cmd.getName()); + ParsedCommand parsedCommand = new ParsedCommand(); + parsedCommand.setCommandName(TEST_COMMAND_NAME); + parsedCommand.setArguments(new String[]{"", ""}); + Assert.assertTrue(cmd.invoke(state, parsedCommand, streams)); + try { + parsedCommand.setArguments(new String[0]); + cmd.invoke(state, parsedCommand, streams); + Assert.fail("Manual check arguments don't work"); + } catch (ArgumentException e) { + Assert.assertEquals("My message", e.getMessage()); + } + } + + @Test + public void testGetName() throws Exception { + Command cmd = new Command(TEST_COMMAND_NAME, 0) { + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException { + return false; + } + }; + Assert.assertEquals(TEST_COMMAND_NAME, cmd.getName()); + cmd = new Command("someText", 1) { + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException { + return false; + } + }; + Assert.assertEquals("someText", cmd.getName()); + } + + @Test(expected = ParserException.class) + public void testCanThrowParserException() throws ParserException { + Command cmd = new Command(TEST_COMMAND_NAME, 0) { + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException { + throw new ParserException("Only for test"); + } + }; + ParsedCommand command = new ParsedCommand(); + command.setCommandName(TEST_COMMAND_NAME); + command.setArguments(new String[0]); + cmd.invoke(state, command, streams); + } + + @Test(expected = IllegalArgumentException.class) + public void testCheckCommandName() throws ParserException { + Command cmd = new Command(TEST_COMMAND_NAME, 0) { + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException { + return true; + } + }; + ParsedCommand parsedCommand = new ParsedCommand(); + parsedCommand.setCommandName("WrongName"); + parsedCommand.setArguments(new String[0]); + cmd.invoke(state, parsedCommand, streams); + } + + @Test(expected = IllegalArgumentException.class) + public void testCheckCommandNameSecondCtor() throws ParserException { + Command cmd = new Command(TEST_COMMAND_NAME) { + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException { + return true; + } + }; + ParsedCommand parsedCommand = new ParsedCommand(); + parsedCommand.setCommandName("WrongName"); + parsedCommand.setArguments(new String[0]); + cmd.invoke(state, parsedCommand, streams); + } + + @Test + public void testStateExit() throws ParserException { + InterpreterState myState = new InterpreterState(); + Assert.assertFalse(myState.exited()); + Command cmd = new Command(TEST_COMMAND_NAME, 0) { + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException { + state.exit(); + return false; + } + }; + ParsedCommand parsedCommand = new ParsedCommand(); + parsedCommand.setCommandName(TEST_COMMAND_NAME); + parsedCommand.setArguments(new String[0]); + Assert.assertFalse(cmd.invoke(myState, parsedCommand, streams)); + Assert.assertTrue(myState.exited()); + } + +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/DatabaseFactoryTest.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/DatabaseFactoryTest.java new file mode 100644 index 000000000..0f8822ffa --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/DatabaseFactoryTest.java @@ -0,0 +1,32 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable.tests; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import ru.fizteh.fivt.storage.structured.TableProvider; +import ru.fizteh.fivt.students.LebedevAleksey.storeable.DatabaseFactory; + +public class DatabaseFactoryTest { + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + @Test + public void testClose() throws Exception { + DatabaseFactory factory = new DatabaseFactory(); + TableProvider database = factory.create(folder.newFolder("qwerty").getPath()); + factory.close(); + try { + factory.create(folder.newFolder("qw").getPath()); + Assert.fail("Should throw exception"); + } catch (IllegalStateException e) { + // Expected control flow + } + try { + database.getTableNames(); + Assert.fail("Should throw exception"); + } catch (IllegalStateException e) { + // Expected control flow + } + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/DatabaseTest.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/DatabaseTest.java new file mode 100644 index 000000000..03f8498fe --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/DatabaseTest.java @@ -0,0 +1,321 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable.tests; + +import org.junit.*; +import org.junit.rules.TemporaryFolder; +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.LebedevAleksey.MultiFileHashMap.DatabaseFileStructureException; +import ru.fizteh.fivt.students.LebedevAleksey.MultiFileHashMap.LoadOrSaveException; +import ru.fizteh.fivt.students.LebedevAleksey.MultiFileHashMap.Pair; +import ru.fizteh.fivt.students.LebedevAleksey.storeable.Database; +import ru.fizteh.fivt.students.LebedevAleksey.storeable.DatabaseFactory; + +import java.io.File; +import java.io.IOException; +import java.text.ParseException; +import java.util.Arrays; +import java.util.List; + +public class DatabaseTest { + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + private File dbPath; + private TableProvider database; + + @Before + public void setUp() throws IOException { + dbPath = folder.newFolder("db"); + database = new DatabaseFactory().create(dbPath.getAbsolutePath()); + } + + @After + public void tearDown() throws Exception { + + } + + @Test + public void testThrowIOException() throws Exception { + try { + Database.throwIOException(new LoadOrSaveException("Text1", new IOException("Text2"))); + Assert.fail("Have to throw exception"); + } catch (IOException e) { + Assert.assertEquals("Text2", e.getMessage()); + } + try { + Database.throwIOException(new LoadOrSaveException("Text1", new IndexOutOfBoundsException("Text2"))); + Assert.fail("Have to throw exception"); + } catch (IOException e) { + Assert.assertEquals("Text1", e.getMessage()); + } + try { + Database.throwIOException(new LoadOrSaveException("Text1")); + Assert.fail("Have to throw exception"); + } catch (IOException e) { + Assert.assertEquals("Text1", e.getMessage()); + } + try { + Database.throwIOException(new DatabaseFileStructureException("Text1", new IOException("Text2"))); + Assert.fail("Have to throw exception"); + } catch (IOException e) { + Assert.assertEquals("Text2", e.getMessage()); + } + try { + Database.throwIOException(new DatabaseFileStructureException("Text1", + new IndexOutOfBoundsException("Text2"))); + Assert.fail("Have to throw exception"); + } catch (IOException e) { + Assert.assertEquals("Text1", e.getMessage()); + } + try { + Database.throwIOException(new DatabaseFileStructureException("Text1")); + Assert.fail("Have to throw exception"); + } catch (IOException e) { + Assert.assertEquals("Text1", e.getMessage()); + } + } + + @Test(expected = IOException.class) + public void testFileFoundInRootDirectory() throws IOException { + new Database(dbPath.getAbsolutePath()) { + void test() throws IOException { + fileFoundInRootDirectory(new File("Test")); + } + }.test(); + } + + @Test + public void testGetAndCreateNonExistentTable() throws Exception { + database.createTable("test", Arrays.asList(String.class)); + Assert.assertNotNull(database.getTable("test")); + Assert.assertEquals("test", database.getTable("test").getName()); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateTableNullName() throws Exception { + database.createTable(null, Arrays.asList(String.class)); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateTableNullTypes() throws Exception { + database.createTable("abc", null); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateTableNullInList() throws Exception { + database.createTable("abc", Arrays.asList(Integer.class, null)); + } + + @Test(expected = IllegalArgumentException.class) + public void testRemoveNull() throws Exception { + database.removeTable(null); + } + + @Test + public void testCreateExistingTable() throws Exception { + database.createTable("table", Arrays.asList(String.class, Integer.class, Boolean.class)); + Assert.assertNull(database.createTable("table", Arrays.asList(String.class, Integer.class, Boolean.class))); + } + + @Test + public void testRemoveExistingTable() throws Exception { + database.createTable("table", Arrays.asList(String.class, Integer.class, Boolean.class)); + database.removeTable("table"); + Assert.assertNull(database.getTable("table")); + Assert.assertNotNull(database.createTable("table", Arrays.asList(String.class, + Long.class, Double.class))); + } + + @Test(expected = IllegalStateException.class) + public void testRemoveNotExistentTable() throws Exception { + database.removeTable("notExist"); + } + + @Test + public void testSerializeDeserialize() throws Exception { + Table table = database.createTable("table", Arrays.asList(String.class, Integer.class, Boolean.class, + Long.class, Double.class, Float.class, Byte.class, Byte.class)); + List expected = Arrays.asList("qwerty", Integer.MIN_VALUE, true, + 23L, 1.23, -3.4E4f, (byte) 127, null); + Storeable original = database.createFor(table, expected); + String serialized = database.serialize(table, original); + Storeable deserialized = database.deserialize(table, serialized); + for (int i = 0; i < expected.size(); i++) { + Assert.assertEquals(expected.get(i), deserialized.getColumnAt(i)); + } + table = database.createTable("name", Arrays.asList(Integer.class, String.class)); + deserialized = database.deserialize(table, "[null,null]"); + Assert.assertNull(deserialized.getColumnAt(0)); + Assert.assertNull(deserialized.getColumnAt(1)); + } + + @Test(expected = ParseException.class) + public void testDeserializeWrongNumberOfColumns() throws ParseException, IOException { + Table table = database.createTable("table", Arrays.asList(String.class, Integer.class)); + database.deserialize(table, "[null]"); + } + + @Test(expected = ParseException.class) + public void testDeserializeWrongNumberOfColumns2() throws ParseException, IOException { + Table table = database.createTable("table", Arrays.asList(String.class, Integer.class)); + database.deserialize(table, "[null,0,0]"); + } + + + @Test + public void testCreateFor() throws Exception { + Table table = database.createTable("table", Arrays.asList( + String.class, Integer.class, Boolean.class, Long.class, Double.class, Float.class, Byte.class)); + List values = Arrays.asList("qwerty", Integer.MAX_VALUE, true, + -23L, -1.23, 3.4E4f, (byte) 127); + Storeable storeable = database.createFor(table, values); + for (int i = 0; i < values.size(); i++) { + Assert.assertEquals(values.get(i), storeable.getColumnAt(i)); + } + } + + @Test + public void testCreateForEmptyAndStoreableSet() throws Exception { + Table table = database.createTable("table", Arrays.asList( + Integer.class, Long.class, String.class, Boolean.class, Double.class, Float.class, Byte.class)); + Storeable actual = database.createFor(table); + try { + actual.setColumnAt(0, 1); + actual.setColumnAt(0, -1); + actual.setColumnAt(1, -3L); + actual.setColumnAt(2, "qwerty"); + actual.setColumnAt(3, false); + actual.setColumnAt(3, true); + actual.setColumnAt(4, 5.5); + actual.setColumnAt(5, 2.3f); + actual.setColumnAt(6, ((byte) 1)); + for (int i = 0; i < 7; i++) { + actual.setColumnAt(i, null); + } + for (int i = 2; i < 7; i++) { + try { + actual.setColumnAt(i, 1); + Assert.fail("Should be exception"); + } catch (ColumnFormatException e) { + // It's ok + } + } + } catch (ColumnFormatException e) { + Assert.fail("Error in setting column " + e); + } + } + + @Test + public void testListTables() throws Exception { + database.createTable("t1", Arrays.asList(String.class, Integer.class, Boolean.class)); + database.createTable("t2", Arrays.asList(Double.class, String.class, Long.class)); + List> real = ((Database) database).listTables(); + Assert.assertEquals(2, real.size()); + Assert.assertEquals(0, (int) real.get(0).getValue()); + Assert.assertEquals(0, (int) real.get(1).getValue()); + String s1 = real.get(0).getKey(); + String s2 = real.get(1).getKey(); + Assert.assertTrue((s1.equals("t1") && s2.equals("t2")) || (s1.equals("t2") && s2.equals("t1"))); + } + + + @Test + public void testGetTableNames() throws Exception { + database.createTable("t1", Arrays.asList(String.class, Integer.class, Boolean.class)); + database.createTable("t2", Arrays.asList(Double.class, String.class, Long.class)); + List real = ((Database) database).getTableNames(); + Assert.assertEquals(2, real.size()); + String s1 = real.get(0); + String s2 = real.get(1); + Assert.assertTrue((s1.equals("t1") && s2.equals("t2")) || (s1.equals("t2") && s2.equals("t1"))); + } + + @Test(expected = IllegalStateException.class) + public void testGetTableThrowsExceptionOnClosedDatabase() throws Exception { + Table table = database.createTable("name", Arrays.asList(Integer.class)); + ((Database) database).close(); + database.getTable("name"); + } + + @Test(expected = IllegalStateException.class) + public void testCreateTableThrowsExceptionOnClosedDatabase() throws Exception { + ((Database) database).close(); + database.createTable("name", Arrays.asList(Integer.class)); + } + + @Test(expected = IllegalStateException.class) + public void testRemoveTableThrowsExceptionOnClosedDatabase() throws Exception { + Table table = database.createTable("name", Arrays.asList(Integer.class)); + ((Database) database).close(); + database.removeTable("name"); + } + + @Test(expected = IllegalStateException.class) + public void testDeserializeThrowsExceptionOnClosedDatabase() throws Exception { + Table table = database.createTable("name", Arrays.asList(Integer.class)); + ((Database) database).close(); + database.deserialize(table, "[1]"); + } + + @Test(expected = IllegalStateException.class) + public void testSerializeThrowsExceptionOnClosedDatabase() throws Exception { + Table table = database.createTable("name", Arrays.asList(Integer.class)); + Storeable sto = database.createFor(table); + ((Database) database).close(); + database.serialize(table, sto); + } + + @Test(expected = IllegalStateException.class) + public void testCreateForThrowsExceptionOnClosedDatabase() throws Exception { + Table table = database.createTable("name", Arrays.asList(Integer.class)); + ((Database) database).close(); + database.createFor(table); + } + + @Test(expected = IllegalStateException.class) + public void testCreateFor1ThrowsExceptionOnClosedDatabase() throws Exception { + Table table = database.createTable("name", Arrays.asList(Integer.class)); + ((Database) database).close(); + database.createFor(table, Arrays.asList(1)); + } + + @Test(expected = IllegalStateException.class) + public void testGetTableNamesThrowsExceptionOnClosedDatabase() throws Exception { + ((Database) database).close(); + database.getTableNames(); + } + + + @Test(expected = IllegalStateException.class) + public void testToStringThrowsExceptionOnClosedDatabase() throws Exception { + ((Database) database).close(); + database.toString(); + } + + @Test(expected = IllegalStateException.class) + public void testListTablesThrowsExceptionOnClosedDatabase() throws Exception { + ((Database) database).close(); + ((Database) database).listTables(); + } + + @Test(expected = IllegalStateException.class) + public void testCloseThrowsExceptionOnClosedDatabase() throws Exception { + ((Database) database).close(); + ((Database) database).close(); + } + + @Test(expected = IllegalStateException.class) + public void testTableClosedAfterProviderClosed() throws Exception { + Table table = database.createTable("name", Arrays.asList(Integer.class)); + ((Database) database).close(); + table.list(); + } + + @Test() + public void testToString() throws Exception { + Assert.assertEquals("Database[" + dbPath.getAbsolutePath() + "]", database.toString()); + + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/DatabaseTestWithWrapper.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/DatabaseTestWithWrapper.java new file mode 100644 index 000000000..198b79cfb --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/DatabaseTestWithWrapper.java @@ -0,0 +1,151 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable.tests; + +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import ru.fizteh.fivt.storage.strings.Table; +import ru.fizteh.fivt.storage.strings.TableProvider; +import ru.fizteh.fivt.students.LebedevAleksey.junit.AdditionalAssert; +import ru.fizteh.fivt.students.LebedevAleksey.junit.DatabaseException; +import ru.fizteh.fivt.students.LebedevAleksey.storeable.Database; +import ru.fizteh.fivt.students.LebedevAleksey.storeable.DatabaseFactory; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; + +public class DatabaseTestWithWrapper { + @ClassRule + public static TemporaryFolder folder = new TemporaryFolder(); + private static File dbPath; + private static TableProvider database; + + @BeforeClass + public static void setUpClass() throws IOException { + dbPath = folder.newFolder("db"); + database = getTableProviderWrapper(); + } + + protected static TableProviderWrapper getTableProviderWrapper() throws IOException { + return new TableProviderWrapper((Database) new DatabaseFactory().create(dbPath.getPath())); + } + + @Test + public void testGetAndCreateTable() { + Assert.assertNull(database.getTable("abc")); + Table table = database.createTable("abc"); + table.put("a", "a"); + table.commit(); + table = database.getTable("abc"); + Assert.assertEquals(1, table.size()); + AdditionalAssert.assertStringArrayContainsSameItemsAsList(new String[]{"a"}, table.list()); + Assert.assertEquals(null, database.getTable("abcd")); + Assert.assertEquals(null, database.getTable("ab")); + Assert.assertEquals(null, database.getTable("a")); + Assert.assertEquals(null, database.getTable("qwerty")); + database.removeTable("abc"); + } + + @Test(expected = IllegalStateException.class) + public void testExceptionThrowsThanDropNotExistingTable() { + database.removeTable("notExist"); + } + + @Test + public void testCanRemoveTable() { + Table table = database.createTable("name"); + table.put("a", "a"); + table.commit(); + database.removeTable("name"); + Assert.assertEquals(null, database.getTable("name")); + } + + @Test + public void testCanRemoveTableWithNoCommits() { + Table table = database.createTable("name"); + database.removeTable("name"); + Assert.assertEquals(null, database.getTable("name")); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateTableThrowsExceptionThanArgumentIsNull() { + database.createTable(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testRemoveTableThrowsExceptionThanArgumentIsNull() { + database.removeTable(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetTableThrowsExceptionThanArgumentIsNull() { + database.getTable(null); + } + + @Test + public void testReturnNullWhenTableExist() { + database.createTable("qwerty"); + Assert.assertEquals(null, database.createTable("qwerty")); + database.removeTable("qwerty"); + } + + @Test + public void testGetTableFromManyTablesAndLoadDatabase() throws IOException { + for (int i = 0; i < 10; i++) { + database.createTable("t" + i); + } + for (int i = 0; i < 10; i++) { + assertCanGetTableTNum(i); + } + database = getTableProviderWrapper(); + for (int i = 0; i < 10; i++) { + database.removeTable("t" + i); + for (int j = 0; j <= i; j++) { + Assert.assertEquals(null, database.getTable("t" + j)); + } + for (int j = i + 1; j < 10; j++) { + assertCanGetTableTNum(j); + } + } + } + + + private void assertCanGetTableTNum(int i) { + Assert.assertEquals("t" + i, database.getTable("t" + i).getName()); + } +} + +class TableProviderWrapper implements ru.fizteh.fivt.storage.strings.TableProvider { + private Database database; + + public TableProviderWrapper(Database tableProvider) { + database = tableProvider; + } + + @Override + public Table getTable(String name) { + return StringTableWrapper.create(database.getTable(name), database); + } + + @Override + public Table createTable(String name) { + try { + return StringTableWrapper.create(database.createTable(name, Arrays.asList(String.class)), database); + } catch (IOException e) { + throw new DatabaseException(e); + } + + } + + @Override + public void removeTable(String name) { + try { + database.removeTable(name); + } catch (IOException e) { + throw new DatabaseException(e); + } + } +} + diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/InterpreterTest.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/InterpreterTest.java new file mode 100644 index 000000000..a2a6f6109 --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/InterpreterTest.java @@ -0,0 +1,322 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable.tests; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import ru.fizteh.fivt.students.LebedevAleksey.storeable.interpreter.*; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; + +public class InterpreterTest { + public static final String TEST_COMMAND_NAME = "test"; + private ByteArrayOutputStream testOutput = new ByteArrayOutputStream(); + private PrintStream testOut = new PrintStream(testOutput); + private ByteArrayOutputStream testError = new ByteArrayOutputStream(); + private PrintStream testErr = new PrintStream(testError); + + private StreamsContainer createStreamContainer(InputStream input) { + return new StreamsContainer(testOut, testErr, input); + } + + @Test + public void testInvokeCommands() { + Interpreter interpreter = createInterpreterWithTestCommand(null); + interpreter.run(new String[]{TEST_COMMAND_NAME}); + Assert.assertEquals("TestMessage" + System.lineSeparator(), testOutput.toString()); + } + + @Test + public void testBatchModeWithOneCommand() { + Interpreter interpreter = createInterpreterWithTestCommand(null); + interpreter.run(new String[]{TEST_COMMAND_NAME + ";", TEST_COMMAND_NAME + ";", TEST_COMMAND_NAME}); + String expected = "TestMessage" + System.lineSeparator() + "TestMessage" + + System.lineSeparator() + "TestMessage" + System.lineSeparator(); + Assert.assertEquals(expected, testOutput.toString()); + } + + @Test + public void testInteractiveModeWithOneCommand() { + List commands = new ArrayList<>(); + commands.add(new Command("exit", 0) { + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException { + streams.getOut().println("TestMessage"); + state.exit(); + return false; + } + }); + Interpreter interpreter = new Interpreter(commands, new InterpreterState(), + createStreamContainer(new ByteArrayInputStream(("exit" + System.lineSeparator()).getBytes()))); + interpreter.run(new String[0]); + String expected = "$ TestMessage" + System.lineSeparator(); + Assert.assertEquals(expected, testOutput.toString()); + } + + @Test + public void testInteractiveModeWithSeveralCommands() { + Interpreter interpreter = createInterpreterWithTestAndExitCommands( + new ByteArrayInputStream((TEST_COMMAND_NAME + System.lineSeparator() + TEST_COMMAND_NAME + + System.lineSeparator() + "exit" + System.lineSeparator()).getBytes())); + interpreter.run(new String[0]); + String expected = "$ TestMessage" + System.lineSeparator() + "$ TestMessage" + System.lineSeparator() + + "$ Terminated" + System.lineSeparator(); + Assert.assertEquals(expected, testOutput.toString()); + } + + @Test + public void testBatchModeWithSeveralCommands() { + Interpreter interpreter = createInterpreterWithTestAndExitCommands(null); + interpreter.run(new String[]{TEST_COMMAND_NAME + ";", TEST_COMMAND_NAME}); + String expected = "TestMessage" + System.lineSeparator() + "TestMessage" + System.lineSeparator(); + Assert.assertEquals(expected, testOutput.toString()); + } + + @Test + public void testExitCommandInBatchMode() { + Interpreter interpreter = createInterpreterWithTestAndExitCommands(null); + interpreter.run(new String[]{TEST_COMMAND_NAME + ";exit;", TEST_COMMAND_NAME}); + String expected = "TestMessage" + System.lineSeparator() + "Terminated" + System.lineSeparator(); + Assert.assertEquals(expected, testOutput.toString()); + } + + @Test + public void testCorrectArgumentsParsingBatchMode() { + List commands = new ArrayList<>(); + commands.add(new Command("print") { + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException { + streams.getOut().print(arguments.length); + for (String str : arguments) { + streams.getOut().print(" " + str); + } + return true; + } + }); + + compareBatch(commands, new String[]{"print\t"}, "0"); + compareBatch(commands, new String[]{"print "}, "0"); + compareBatch(commands, new String[]{"\tprint"}, "0"); + compareBatch(commands, new String[]{" print"}, "0"); + compareBatch(commands, new String[]{"print\t\t"}, "0"); + compareBatch(commands, new String[]{"print "}, "0"); + compareBatch(commands, new String[]{"\t\tprint"}, "0"); + compareBatch(commands, new String[]{" print"}, "0"); + compareBatch(commands, new String[]{"print\t "}, "0"); + compareBatch(commands, new String[]{"print \t"}, "0"); + compareBatch(commands, new String[]{"\t print"}, "0"); + compareBatch(commands, new String[]{" \tprint"}, "0"); + + compareBatch(commands, new String[]{"print", "\t; print", "123"}, "01 123"); + compareBatch(commands, new String[]{"print", "\t; print", "123", "45\t;print ;print"}, "02 123 4500"); + compareBatch(commands, new String[]{"print", "\t; print", "123", "45;\tprint; print"}, "02 123 4500"); + compareBatch(commands, new String[]{"print", "\t; print", "123", "45\t;\tprint ; print"}, "02 123 4500"); + compareBatch(commands, new String[]{"print", "\t; print", "123", "45 ;\tprint ;\tprint"}, "02 123 4500"); + compareBatch(commands, new String[]{"print", "\t; print", "123", "45\t; print\t; print"}, "02 123 4500"); + + compareBatch(commands, new String[]{"\tprint"}, "0"); + compareBatch(commands, new String[]{"\t\tprint"}, "0"); + compareBatch(commands, new String[]{" \tprint"}, "0"); + compareBatch(commands, new String[]{"print "}, "0"); + compareBatch(commands, new String[]{"print\t"}, "0"); + compareBatch(commands, new String[]{"print \t"}, "0"); + compareBatch(commands, new String[]{"print\t "}, "0"); + compareBatch(commands, new String[]{"print "}, "0"); + compareBatch(commands, new String[]{"print\t\t"}, "0"); + compareBatch(commands, new String[]{"print "}, "0"); + compareBatch(commands, new String[]{"\tprint\t"}, "0"); + compareBatch(commands, new String[]{" print \t"}, "0"); + compareBatch(commands, new String[]{"\t\tprint\t "}, "0"); + compareBatch(commands, new String[]{"print "}, "0"); + compareBatch(commands, new String[]{"print\t\t"}, "0"); + + commands.add(new Command("secondCommand") { + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException { + streams.getOut().println("Output"); + return true; + } + }); + compareBatch(commands, new String[]{"print", ";secondCommand;print"}, + "0Output" + System.lineSeparator() + "0"); + } + + @Test + public void testCorrectArgumentsParsingInteractiveMode() { + List commands = getPrintEndExitCommands(); + compareInteractive(commands, "print 345 abc \"de fg\" \"hij\"\t \tklmn;print\t;print\t;exit", + "$ 5 345 abc de fg hij klmn00Terminated" + System.lineSeparator()); + compareInteractive(commands, " print \t\t\t\t\t test \t\t\t " + + System.lineSeparator() + "exit", "$ 1 test$ Terminated" + System.lineSeparator() + ); + compareInteractive(commands, + "\t\t\t\t\t print\t\"qwe rty\tyu\" 1 2 3 \t\t test j;\tprint\t;\tprint; print ;exit", + "$ 6 qwe rty\tyu 1 2 3 test j000Terminated" + System.lineSeparator()); + compareInteractive(commands, "exit\t", "$ Terminated" + System.lineSeparator()); + compareInteractive(commands, "exit ", "$ Terminated" + System.lineSeparator()); + compareInteractive(commands, "\texit", "$ Terminated" + System.lineSeparator()); + compareInteractive(commands, " exit", "$ Terminated" + System.lineSeparator()); + compareInteractive(commands, "exit\t\t", "$ Terminated" + System.lineSeparator()); + compareInteractive(commands, "exit ", "$ Terminated" + System.lineSeparator()); + compareInteractive(commands, "\t\texit", "$ Terminated" + System.lineSeparator()); + compareInteractive(commands, " exit", "$ Terminated" + System.lineSeparator()); + compareInteractive(commands, "exit\t ", "$ Terminated" + System.lineSeparator()); + compareInteractive(commands, "exit \t", "$ Terminated" + System.lineSeparator()); + compareInteractive(commands, "\t exit", "$ Terminated" + System.lineSeparator()); + compareInteractive(commands, " \texit", "$ Terminated" + System.lineSeparator()); + compareInteractive(commands, "print \"[1,\\\"1234\\\"]\"; exit", + "$ 1 [1,\"1234\"]Terminated" + System.lineSeparator()); + compareInteractive(commands, "print \"[1,\\\"1234\\\"]\" \\;\\\\#; exit", + "$ 2 [1,\"1234\"] ;\\#Terminated" + System.lineSeparator()); + compareInteractive(commands, "print \"[1,#\\\"1234\\\"]\" ##\\;\\\\#; exit", + "$ 2 [1,#\"1234\"] ##;\\#Terminated" + System.lineSeparator()); + } + + private List getPrintEndExitCommands() { + List commands = new ArrayList<>(); + commands.add(new Command("print") { + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException { + streams.getOut().print(arguments.length); + for (String str : arguments) { + streams.getOut().print(" " + str); + } + return true; + } + }); + commands.add(new Command("exit", 0) { + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException { + streams.getOut().println("Terminated"); + state.exit(); + return false; + } + }); + return commands; + } + + private void compareInteractive(List commands, String input, String expected) { + compareInteractive(commands, input, expected, 0, ""); + } + + + private void compareInteractive(List commands, String input, String expected, int exitCode, String error) { + Interpreter interpreter; + prepareStreams(); + interpreter = new Interpreter(commands, new InterpreterState(), createStreamContainer( + new ByteArrayInputStream(input.getBytes()))); + interpreter.run(new String[0]); + Assert.assertEquals(expected, testOutput.toString()); + Assert.assertEquals(exitCode, interpreter.getExitCode()); + Assert.assertEquals(error, testError.toString()); + } + + private void compareBatch(List commands, String[] input, String expected) { + Interpreter interpreter; + prepareStreams(); + interpreter = new Interpreter(commands, new InterpreterState(), createStreamContainer(null)); + interpreter.run(input); + Assert.assertEquals(expected, testOutput.toString()); + Assert.assertEquals(0, interpreter.getExitCode()); + Assert.assertEquals("", testError.toString()); + } + + private Interpreter createInterpreterWithTestCommand(InputStream input) { + List commands = new ArrayList<>(); + commands.add(new Command(TEST_COMMAND_NAME, 0) { + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException { + streams.getOut().println("TestMessage"); + return true; + } + }); + return new Interpreter(commands, new InterpreterState(), createStreamContainer(input)); + } + + private Interpreter createInterpreterWithTestAndExitCommands(InputStream input) { + List commands = new ArrayList<>(); + commands.add(new Command(TEST_COMMAND_NAME, 0) { + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException { + streams.getOut().println("TestMessage"); + return true; + } + }); + commands.add(new Command("exit", 0) { + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException { + streams.getOut().println("Terminated"); + state.exit(); + return false; + } + }); + return new Interpreter(commands, new InterpreterState(), createStreamContainer(input)); + } + + @Before + public void prepareStreams() { + testOutput.reset(); + testError.reset(); + } + + @Test + public void testShortInput() { + Interpreter interpreter; + prepareStreams(); + interpreter = new Interpreter(getPrintEndExitCommands(), new InterpreterState(), createStreamContainer( + new ByteArrayInputStream("print 1".getBytes()))); + interpreter.run(new String[0]); + Assert.assertEquals("$ 1 1$ ", testOutput.toString()); + Assert.assertEquals(1, interpreter.getExitCode()); + Assert.assertEquals("Error: Can not read" + System.lineSeparator(), testError.toString()); + } + + @Test + public void testWrongInput() { + List commands = getPrintEndExitCommands(); + compareInteractive(commands, "print \";exit", "$ $ ", 1, "Error: Wrong quote structure." + + System.lineSeparator() + "Error: Can not read" + System.lineSeparator()); + compareInteractive(commands, "pr\"nt; exit", "$ $ ", 1, "Error: Wrong quote structure." + + System.lineSeparator() + "Error: Can not read" + System.lineSeparator()); + compareInteractive(commands, "print \"1\"2; exit", "$ $ ", 1, "Error: Where is no space between to arguments." + + System.lineSeparator() + "Error: Can not read" + System.lineSeparator()); + } + + + @Test + public void testExceptionInCommand() { + List commands = getPrintEndExitCommands(); + commands.add(new Command("tst") { + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException { + throw new ArgumentException("Message"); + } + }); + commands.add(new Command("tst2") { + @Override + protected boolean action(InterpreterState state, String[] arguments, StreamsContainer streams) + throws ArgumentException, ParserException { + throw new ParserException("Message2"); + } + }); + compareInteractive(commands, "tst; exit", "$ $ ", 1, "tst: Message" + + System.lineSeparator() + "Error: Can not read" + System.lineSeparator()); + compareInteractive(commands, "tst2; exit", "$ $ ", 1, "Error: Message2" + + System.lineSeparator() + "Error: Can not read" + System.lineSeparator()); + compareInteractive(commands, "qwerty", "$ $ ", 1, "Error: This command is unknown: qwerty" + + System.lineSeparator() + "Error: Can not read" + System.lineSeparator()); + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/JsonParserTest.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/JsonParserTest.java new file mode 100644 index 000000000..9d68c68a9 --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/JsonParserTest.java @@ -0,0 +1,430 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable.tests; + +import org.junit.Assert; +import org.junit.Test; +import ru.fizteh.fivt.students.LebedevAleksey.storeable.json.BrokenJsonException; +import ru.fizteh.fivt.students.LebedevAleksey.storeable.json.JsonParser; +import ru.fizteh.fivt.students.LebedevAleksey.storeable.json.JsonUnsupportedObjectException; + +import java.io.File; +import java.util.*; + +public class JsonParserTest { + @Test + public void testGetJsonForStrings() throws JsonUnsupportedObjectException { + Assert.assertEquals("\"Qwerty\"", JsonParser.getJson("Qwerty")); + Assert.assertEquals("\"Test text\"", JsonParser.getJson("Test text")); + Assert.assertEquals("\"Text, fore test. Asdf!\"", JsonParser.getJson("Text, fore test. Asdf!")); + } + + @Test + public void testGetJsonForStringsWithCaracters() throws JsonUnsupportedObjectException { + Assert.assertEquals("\"Qwe\\nrty\"", JsonParser.getJson("Qwe\nrty")); + Assert.assertEquals("\"Qwe\\re r\\ty\"", JsonParser.getJson("Qwe\re r\ty")); + Assert.assertEquals("\"This is quote: \\\"test\\\".\"", JsonParser.getJson("This is quote: \"test\".")); + Assert.assertEquals("\"\\b\"", JsonParser.getJson("\b")); + Assert.assertEquals("\"\\f\"", JsonParser.getJson("\f")); + } + + @Test(expected = JsonUnsupportedObjectException.class) + public void testThrowExceptionForUnknownTypes() throws JsonUnsupportedObjectException { + JsonParser.getJson(new File(".")); + } + + + @Test() + public void testCanSerialiseIntegers() throws JsonUnsupportedObjectException { + Assert.assertEquals("1234", JsonParser.getJson(1234)); + Assert.assertEquals("123456789012345", JsonParser.getJson(123456789012345L)); + Assert.assertEquals("-4567", JsonParser.getJson(-4567)); + Assert.assertEquals("-923456789012345", JsonParser.getJson(-923456789012345L)); + Assert.assertEquals("123.45", JsonParser.getJson(123.45)); + Assert.assertEquals("123.45", JsonParser.getJson((float) 123.45)); + Assert.assertEquals("-123.45", JsonParser.getJson(-123.45)); + Assert.assertEquals("-123.45", JsonParser.getJson((float) -123.45)); + Assert.assertEquals("123.45", JsonParser.getJson((double) 123.45)); + Assert.assertEquals("-123.45", JsonParser.getJson((double) -123.45)); + Assert.assertEquals("123", JsonParser.getJson((byte) 123)); + Assert.assertEquals("-123", JsonParser.getJson((byte) -123)); + } + + @Test() + public void testCanSerialiseBools() throws JsonUnsupportedObjectException { + Assert.assertEquals("true", JsonParser.getJson(true)); + Assert.assertEquals("false", JsonParser.getJson(false)); + } + + @Test() + public void testCanSerialiseNull() throws JsonUnsupportedObjectException { + Assert.assertEquals("null", JsonParser.getJson(null)); + } + + @Test + public void testCanGetJsonForArrays() throws JsonUnsupportedObjectException { + Assert.assertEquals("[123,\"QwertY\"]", JsonParser.getJson(Arrays.asList(new Object[]{123, "QwertY"}))); + Assert.assertEquals("[123,\"QwertY\",true]", JsonParser.getJson(new Object[]{123, "QwertY", true})); + } + + + @Test + public void testCanGetJsonForMap() throws JsonUnsupportedObjectException { + checkMap(new TreeMap<>()); + checkMap(new HashMap<>()); + } + + private void checkMap(Map map) throws JsonUnsupportedObjectException { + map.put("b", true); + map.put("c", null); + map.put("dd", 3.1415); + String bString = "\"b\":true"; + String cString = "\"c\":null"; + String dString = "\"dd\":3.1415"; + String[] strings = new String[]{bString, cString, dString}; + int equalsCount = 0; + equalsCount = assertMapJson(map, strings, equalsCount); + strings[2] = cString; + strings[1] = dString; + equalsCount = assertMapJson(map, strings, equalsCount); + strings[0] = cString; + strings[2] = bString; + equalsCount = assertMapJson(map, strings, equalsCount); + strings[2] = dString; + strings[1] = bString; + equalsCount = assertMapJson(map, strings, equalsCount); + strings[0] = dString; + strings[2] = cString; + equalsCount = assertMapJson(map, strings, equalsCount); + strings[1] = cString; + strings[2] = bString; + equalsCount = assertMapJson(map, strings, equalsCount); + Assert.assertEquals(1, equalsCount); + } + + private int assertMapJson(Map map, String[] strings, int equalsCount) + throws JsonUnsupportedObjectException { + if (("{" + strings[0] + "," + strings[1] + "," + strings[2] + "}").equals(JsonParser.getJson(map))) { + ++equalsCount; + } + return equalsCount; + } + + @Test + public void testSubItemJson() throws JsonUnsupportedObjectException { + Map map = new TreeMap<>(); + map.put("a", 1); + Object[] array = new Object[]{1, map, new Object[]{1.2, new Object[]{true}, null}}; + Assert.assertEquals("[1,{\"a\":1},[1.2,[true],null]]", JsonParser.getJson(array)); + } + + @Test + public void testCanJsonSupportedArrays() throws JsonUnsupportedObjectException { + Integer[] array = new Integer[]{1, 2, 3}; + Assert.assertEquals("[1,2,3]", (String) JsonParser.getJson(array)); + } + + + @Test + public void testParseStringJson() throws BrokenJsonException { + Assert.assertEquals("qwertY", JsonParser.parseJson("\"qwertY\"")); + Assert.assertEquals("qwe rtY", JsonParser.parseJson("\"qwe rtY\"")); + Assert.assertEquals("qwe\"rtY", JsonParser.parseJson("\"qwe\\\"rtY\"")); + Assert.assertEquals("\b\n\f\t\r\\/", JsonParser.parseJson("\"\\b\\n\\f\\t\\r\\\\\\/\"")); + Assert.assertEquals("Quick brown fox jump other the lazy dog.", + JsonParser.parseJson("\"Quick brown fox jump other the lazy dog.\"")); + Assert.assertEquals("\u0077cd", JsonParser.parseJson("\"\\u0077cd\"")); + Assert.assertEquals("\u9abc", JsonParser.parseJson("\"\\u9abc\"")); + } + + @Test(expected = BrokenJsonException.class) + public void testParseBrokenStringJson1() throws BrokenJsonException { + JsonParser.parseJson("\"\\u\""); + } + + @Test(expected = BrokenJsonException.class) + public void testParseBrokenStringJson2() throws BrokenJsonException { + JsonParser.parseJson("\"\\u1\""); + } + + @Test(expected = BrokenJsonException.class) + public void testParseBrokenStringJson3() throws BrokenJsonException { + JsonParser.parseJson("\"\\u12\""); + } + + @Test(expected = BrokenJsonException.class) + public void testParseBrokenStringJson4() throws BrokenJsonException { + JsonParser.parseJson("\"\\u123\""); + } + + @Test(expected = BrokenJsonException.class) + public void testParseBrokenStringJson5() throws BrokenJsonException { + JsonParser.parseJson("\"\\u12x3\""); + } + + @Test(expected = BrokenJsonException.class) + public void testParseBrokenStringJson6() throws BrokenJsonException { + JsonParser.parseJson("\"aba"); + } + + @Test(expected = BrokenJsonException.class) + public void testParseBrokenStringJson7() throws BrokenJsonException { + JsonParser.parseJson("\"a\\hba\""); + } + + @Test + public void testIntParse() throws BrokenJsonException { + JsonParser.setNeedShortIntegerTypes(false); + Object value = JsonParser.parseJson("123"); + Assert.assertEquals(Long.class, value.getClass()); + Assert.assertEquals(123L, value); + JsonParser.setNeedShortIntegerTypes(true); + value = JsonParser.parseJson("123"); + Assert.assertEquals(Byte.class, value.getClass()); + Assert.assertEquals((byte) 123, value); + value = JsonParser.parseJson("1234"); + Assert.assertEquals(Integer.class, value.getClass()); + Assert.assertEquals(1234, value); + JsonParser.setNeedShortIntegerTypes(false); + value = JsonParser.parseJson("4565678"); + Assert.assertEquals(4565678L, value); + value = JsonParser.parseJson("-4565678"); + Assert.assertEquals(-4565678L, value); + value = JsonParser.parseJson("-456.5678"); + Assert.assertEquals(-456.5678, value); + value = JsonParser.parseJson("123.45"); + Assert.assertEquals(123.45, value); + value = JsonParser.parseJson("1.23E5"); + Assert.assertEquals(1.23E5, value); + value = JsonParser.parseJson("-1.23E5"); + Assert.assertEquals(-1.23E5, value); + value = JsonParser.parseJson("1.23E-5"); + Assert.assertEquals(1.23E-5, value); + value = JsonParser.parseJson("-1.23E-5"); + Assert.assertEquals(-1.23E-5, value); + value = JsonParser.parseJson("1.23e5"); + Assert.assertEquals(1.23E5, value); + value = JsonParser.parseJson("-1.23e5"); + Assert.assertEquals(-1.23E5, value); + value = JsonParser.parseJson("1.23e-5"); + Assert.assertEquals(1.23E-5, value); + value = JsonParser.parseJson("-1.23e-5"); + Assert.assertEquals(-1.23E-5, value); + } + + @Test(expected = BrokenJsonException.class) + public void testParseBrokenDouble0() throws BrokenJsonException { + JsonParser.parseJson("12.34.56"); + } + + + @Test(expected = BrokenJsonException.class) + public void testParseBrokenDouble1() throws BrokenJsonException { + JsonParser.parseJson("12.34E5e6"); + } + + @Test(expected = BrokenJsonException.class) + public void testParseBrokenDouble2() throws BrokenJsonException { + JsonParser.parseJson("-12.34-56"); + } + + @Test + public void testParseNull() throws BrokenJsonException { + Assert.assertEquals(null, JsonParser.parseJson("null")); + } + + @Test + public void testParseTrue() throws BrokenJsonException { + Assert.assertEquals(true, JsonParser.parseJson("true")); + } + + @Test + public void testParseFalse() throws BrokenJsonException { + Assert.assertEquals(false, JsonParser.parseJson("false")); + } + + @Test(expected = BrokenJsonException.class) + public void testParseNullWithMistakes() throws BrokenJsonException { + Assert.assertEquals(null, JsonParser.parseJson("nul")); + } + + @Test(expected = BrokenJsonException.class) + public void testParseTrueWithMistakes() throws BrokenJsonException { + Assert.assertEquals(true, JsonParser.parseJson("tre")); + } + + @Test(expected = BrokenJsonException.class) + public void testParseFalseWithMistakes() throws BrokenJsonException { + Assert.assertEquals(false, JsonParser.parseJson("fals")); + } + + @Test(expected = BrokenJsonException.class) + public void testParseFalseWithMistakes2() throws BrokenJsonException { + Assert.assertEquals(false, JsonParser.parseJson("fal")); + } + + @Test(expected = BrokenJsonException.class) + public void testParseFalseWithMistakes3() throws BrokenJsonException { + Assert.assertEquals(false, JsonParser.parseJson("falsq")); + } + + @Test(expected = BrokenJsonException.class) + public void testParseNullWithMistakesAdditionalText() throws BrokenJsonException { + Assert.assertEquals(null, JsonParser.parseJson("nuller")); + } + + @Test(expected = BrokenJsonException.class) + public void testParseTrueWithMistakesAdditionalText() throws BrokenJsonException { + Assert.assertEquals(true, JsonParser.parseJson("trueer")); + } + + @Test(expected = BrokenJsonException.class) + public void testParseFalseWithMistakesAdditionalText() throws BrokenJsonException { + Assert.assertEquals(false, JsonParser.parseJson("falseer")); + } + + @Test() + public void testCanParseArraysAndSpacesOk() throws BrokenJsonException { + checkArray(JsonParser.parseJson("[123.45,567,null,true,\"qwerty\"]")); + checkArray(JsonParser.parseJson(" [123.45, 567, null,true,\"qwerty\"]")); + checkArray(JsonParser.parseJson(" [ 123.45 , 567, null, true, \"qwerty\" ]")); + checkArray(JsonParser.parseJson("[ 123.45 , 567 , null , true , \"qwerty\" ] ")); + } + + private void checkArray(Object result) { + List array = (List) result; + Assert.assertEquals(5, array.size()); + Assert.assertEquals(123.45, array.get(0)); + Assert.assertEquals(567L, array.get(1)); + Assert.assertEquals(null, array.get(2)); + Assert.assertEquals(true, array.get(3)); + Assert.assertEquals("qwerty", array.get(4)); + } + + + @Test() + public void testCanParseMaps() throws BrokenJsonException { + checkMap(JsonParser.parseJson( + "{\"a\":1,\"b\":-2.3e2,\"c\":null,\"def\":true,\"false\":false,\"E\":\"Text \\\"with\\n...\"}")); + checkMap(JsonParser.parseJson( + " { \"a\":1,\"b\":-2.3e2 , \"c\":null,\"def\":true, \"false\":false, \"E\"" + + ":\"Text \\\"with\\n...\" } ")); + checkMap(JsonParser.parseJson( + " {\"a\":1,\"b\":-2.3e2 ,\"c\":null,\"def\":true , \"false\":false,\"E\":\"Te" + + "xt \\\"with\\n...\" }")); + checkMap(JsonParser.parseJson( + "{ \"a\":1,\"b\":-2.3e2,\"c\":null,\"def\":true,\"false\":false,\"E\":\"Text \\\"with\\n...\"} ")); + } + + private void checkMap(Object result) { + Map map = (Map) result; + Assert.assertEquals(6, map.size()); + Assert.assertEquals(1L, map.get("a")); + Assert.assertEquals(-230.0, map.get("b")); + Assert.assertEquals(null, map.get("c")); + Assert.assertEquals(true, map.get("def")); + Assert.assertEquals(false, map.get("false")); + Assert.assertEquals("Text \"with\n...", map.get("E")); + } + + @Test() + public void testCanParseMultiObjects() throws BrokenJsonException { + checkMultiArray(JsonParser.parseJson("[\"qw\",{\"a\":\"b\"},[null,12.3,{}],[]]")); + checkMultiArray(JsonParser.parseJson("[\"qw\",{\"a\" : \"b\"},[null,12.3,{ } ],[ ]]")); + checkMultiArray(JsonParser.parseJson("[\"qw\" ,{\"a\" : \"b\" } , [null,12.3,{}], [ ] ] ")); + checkMultiArray(JsonParser.parseJson("[\"qw\",{\"a\":\"b\"},[null ,12.3, {} ], [] ]")); + } + + private void checkMultiArray(Object result) { + List array = (List) result; + Assert.assertEquals(4, array.size()); + Assert.assertEquals("qw", array.get(0)); + Map map = (Map) array.get(1); + Assert.assertEquals(1, map.size()); + Assert.assertEquals("b", map.get("a")); + List subList = (List) array.get(2); + Assert.assertEquals(3, subList.size()); + Assert.assertEquals(null, subList.get(0)); + Assert.assertEquals(12.3, subList.get(1)); + Assert.assertEquals(0, ((Map) subList.get(2)).size()); + Assert.assertEquals(0, ((List) array.get(3)).size()); + } + + @Test(expected = BrokenJsonException.class) + public void testParseBrokenJson0() throws BrokenJsonException { + JsonParser.parseJson("[ "); + } + + @Test(expected = BrokenJsonException.class) + public void testParseBrokenJson1() throws BrokenJsonException { + JsonParser.parseJson("["); + } + + @Test(expected = BrokenJsonException.class) + public void testParseBrokenJson2() throws BrokenJsonException { + JsonParser.parseJson("]"); + } + + @Test(expected = BrokenJsonException.class) + public void testParseBrokenJson3() throws BrokenJsonException { + JsonParser.parseJson("}"); + } + + @Test(expected = BrokenJsonException.class) + public void testParseBrokenJson4() throws BrokenJsonException { + JsonParser.parseJson("["); + } + + @Test(expected = BrokenJsonException.class) + public void testParseBrokenJson5() throws BrokenJsonException { + JsonParser.parseJson("{"); + } + + + @Test(expected = BrokenJsonException.class) + public void testParseBrokenJson6() throws BrokenJsonException { + JsonParser.parseJson("\""); + } + + @Test(expected = BrokenJsonException.class) + public void testParseBrokenJson7() throws BrokenJsonException { + JsonParser.parseJson(""); + } + + @Test(expected = BrokenJsonException.class) + public void testParseBrokenJson8() throws BrokenJsonException { + JsonParser.parseJson(" "); + } + + @Test(expected = BrokenJsonException.class) + public void testParseBrokenJson9() throws BrokenJsonException { + JsonParser.parseJson("nula"); + } + + @Test(expected = BrokenJsonException.class) + public void testWrongMapKey() throws BrokenJsonException { + JsonParser.parseJson("{1:1}"); + } + + @Test(expected = BrokenJsonException.class) + public void testNotTerminatedString() throws BrokenJsonException { + JsonParser.parseJson("\"qwerty"); + } + + + @Test(expected = JsonUnsupportedObjectException.class) + public void testWrongMapSerialise() throws JsonUnsupportedObjectException { + Map map = new TreeMap<>(); + map.put(1, 2); + JsonParser.getJson(map); + } + + @Test + public void testBrokenJSONExceptionCanSaveOffset() { + BrokenJsonException ex = new BrokenJsonException("Test", 7); + Assert.assertEquals("Test", ex.getMessage()); + Assert.assertEquals(7, ex.getOffsetError()); + ex = new BrokenJsonException("Message", 42); + Assert.assertEquals("Message", ex.getMessage()); + Assert.assertEquals(42, ex.getOffsetError()); + } + +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/ParallelTest.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/ParallelTest.java new file mode 100644 index 000000000..d43589539 --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/ParallelTest.java @@ -0,0 +1,134 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable.tests; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +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.students.LebedevAleksey.storeable.DatabaseFactory; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +public class ParallelTest { + final AtomicInteger numberOfAction = new AtomicInteger(0); + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + private File dbPath; + private TableProvider database; + private Table table; + private final ThreadLocal>> columnTypes = new ThreadLocal>>() { + @Override + protected List> initialValue() { + return Arrays.asList(Integer.class); + } + }; + + @Before + public void setUp() throws IOException { + dbPath = temporaryFolder.newFolder("db"); + database = new DatabaseFactory().create(dbPath.getAbsolutePath()); + table = database.createTable("table", columnTypes.get()); + } + + Object lock = new Object(); + + @Test(timeout = 30000) + public void checkParallelWork() throws IOException, InterruptedException { + for (int i = 0; i < 15; i++) { + numberOfAction.set(0); + Storeable value = database.createFor(table); + value.setColumnAt(0, 5); + table.put("a", value); + value.setColumnAt(0, 7); + table.put("b", value); + table.commit(); + Thread t1 = new Thread(() -> { + try { + table.put("c", database.createFor(table)); + Storeable a = table.get("a"); + a.setColumnAt(0, 2); + table.put("a", a); + nextAction(); + waitFor(3); + Assert.assertEquals(3, table.size()); + Assert.assertEquals((Integer) 2, table.get("a").getIntAt(0)); + Assert.assertEquals((Integer) 7, table.get("b").getIntAt(0)); + Assert.assertEquals(null, table.get("c").getIntAt(0)); + nextAction(); + waitFor(5); + table.commit(); + nextAction(); + waitFor(7); + Assert.assertEquals(2, table.size()); + Assert.assertEquals((Integer) 2, table.get("a").getIntAt(0)); + Assert.assertEquals(null, table.get("c").getIntAt(0)); + Assert.assertNotNull(database.getTable("t2")); + Assert.assertNull(database.createTable("t2", columnTypes.get())); + database.removeTable("t2"); + nextAction(); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + Thread t2 = new Thread(() -> { + try { + table.remove("b"); + nextAction(); + waitFor(3); + Assert.assertEquals(1, table.size()); + Assert.assertEquals((Integer) 5, table.get("a").getIntAt(0)); + nextAction(); + waitFor(6); + Assert.assertEquals(2, table.size()); + Assert.assertEquals((Integer) 2, table.get("a").getIntAt(0)); + Assert.assertEquals(null, table.get("c").getIntAt(0)); + table.commit(); + database.createTable("t2", columnTypes.get()); + nextAction(); + waitFor(8); + Assert.assertNull(database.getTable("t2")); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + t1.start(); + t2.start(); + waitFor(2); + Assert.assertEquals(2, table.size()); + Assert.assertEquals((Integer) 5, table.get("a").getIntAt(0)); + Assert.assertEquals((Integer) 7, table.get("b").getIntAt(0)); + nextAction(); + t1.join(); + t2.join(); + table.remove("c"); + table.remove("b"); + table.commit(); + } + } + + private void nextAction() { + numberOfAction.incrementAndGet(); + synchronized (lock) { + lock.notifyAll(); + } + } + + private void waitFor(int value) { + try { + synchronized (lock) { + while (numberOfAction.get() < value) { + lock.wait(); + } + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/StoreableTableTest.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/StoreableTableTest.java new file mode 100644 index 000000000..8c6f6bec3 --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/StoreableTableTest.java @@ -0,0 +1,134 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable.tests; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +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.LebedevAleksey.storeable.DatabaseFactory; +import ru.fizteh.fivt.students.LebedevAleksey.storeable.StoreableTable; + +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class StoreableTableTest { + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + private Table table; + private TableProvider provider; + private TableProviderFactory factory; + private Class[] types = {Integer.class, String.class, Boolean.class}; + private File tempFolder; + + public StoreableTableTest() { + this.factory = new DatabaseFactory(); + } + + @Before + public void setUp() throws Exception { + tempFolder = folder.newFolder(); + provider = factory.create(tempFolder.getAbsolutePath()); + table = provider.createTable("table", Arrays.asList(types)); + } + + @Test + public void testPutGet() throws Exception { + List expected = Arrays.asList(0, "qwerty", false); + table.put("line", provider.createFor(table, expected)); + Storeable actual = table.get("line"); + for (int i = 0; i < expected.size(); i++) { + assertEquals(expected.get(i), actual.getColumnAt(i)); + } + } + + @Test + public void testRemove() throws Exception { + List expected = Arrays.asList(1, "test", true); + table.put("qwerty", provider.createFor(table, expected)); + Storeable actual = table.remove("qwerty"); + for (int i = 0; i < expected.size(); i++) { + assertEquals(expected.get(i), actual.getColumnAt(i)); + } + } + + @Test + public void testSize() throws Exception { + List expected = Arrays.asList(3, "q", false); + table.put("a", provider.createFor(table, expected)); + table.put("b", provider.createFor(table, expected)); + assertEquals(2, table.size()); + } + + @Test + public void testGetColumnsCount() throws Exception { + assertEquals(3, table.getColumnsCount()); + } + + @Test + public void testGetColumnType() throws Exception { + for (int i = 0; i < types.length; i++) { + assertEquals(types[i], table.getColumnType(i)); + } + } + + @Test + public void testGetName() throws Exception { + assertEquals("table", table.getName()); + } + + @Test + public void testToString() { + Assert.assertEquals("StoreableTable[" + tempFolder.toPath().resolve("table") + "]", table.toString()); + } + + @Test + public void testRollabackAfterClosing() throws Exception { + table.put("q", provider.createFor(table)); + table.commit(); + table.put("r", provider.createFor(table)); + ((StoreableTable) table).close(); + table = provider.getTable("table"); + Assert.assertEquals(1, table.size()); + Assert.assertNotNull(table.get("q")); + } + + @Test + public void testAllMethodsThrowsExceptionAfterClose() throws Exception { + StoreableTable myTable = (StoreableTable) table; + myTable.close(); + Method[] declaredMethods = myTable.getClass().getDeclaredMethods(); + for (Method item : declaredMethods) { + if (Modifier.isPublic(item.getModifiers())) { + Object[] args = new Object[item.getParameterTypes().length]; + Storeable storeable = provider.createFor(provider.getTable("table")); + for (int i = 0; i < args.length; i++) { + if (item.getParameterTypes()[i] == String.class) { + args[i] = "qwerty"; + } else { + if (item.getParameterTypes()[i] == Storeable.class) { + args[i] = storeable; + } else { + args[i] = 0; + } + } + } + try { + item.invoke(myTable, args); + Assert.fail("Should be exception invoking method " + item.getName()); + } catch (InvocationTargetException e) { + Assert.assertEquals(IllegalStateException.class, e.getTargetException().getClass()); + } + } + } + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/StoreableTest.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/StoreableTest.java new file mode 100644 index 000000000..4ef13de28 --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/StoreableTest.java @@ -0,0 +1,160 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable.tests; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +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.LebedevAleksey.storeable.DatabaseFactory; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; + +public class StoreableTest { + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + private File dbPath; + private TableProvider database; + private Table table; + private Storeable storeable; + + @Before + public void setUp() throws IOException { + dbPath = folder.newFolder("db"); + database = new DatabaseFactory().create(dbPath.getAbsolutePath()); + table = database.createTable("table", Arrays.asList( + Integer.class, Long.class, String.class, Boolean.class, Double.class, Float.class, Byte.class)); + storeable = database.createFor(table); + storeable.setColumnAt(0, 1); + storeable.setColumnAt(1, -3L); + storeable.setColumnAt(2, "qwerty"); + storeable.setColumnAt(3, true); + storeable.setColumnAt(4, 5.5); + storeable.setColumnAt(5, 2.3f); + storeable.setColumnAt(6, ((byte) 1)); + } + + @Test + public void testCreateEmptyStoreable() { + storeable = database.createFor(table); + for (int i = 0; i < table.getColumnsCount(); i++) { + Assert.assertNull(storeable.getColumnAt(i)); + } + } + + @Test + public void testGetIntAt() { + Assert.assertEquals((Object) 1, storeable.getIntAt(0)); + for (int i = 0; i < table.getColumnsCount(); i++) { + if (i != 0) { + try { + storeable.getIntAt(i); + Assert.fail("Should be exception"); + } catch (ColumnFormatException e) { + // Ok. + } + } + } + } + + @Test + public void testGetLongAt() throws Exception { + Assert.assertEquals((Object) (-3L), storeable.getLongAt(1)); + for (int i = 0; i < table.getColumnsCount(); i++) { + if (i != 1) { + try { + storeable.getLongAt(i); + Assert.fail("Should be exception"); + } catch (ColumnFormatException e) { + // Ok. + } + } + } + } + + @Test + public void testGetByteAt() throws Exception { + Assert.assertEquals((Object) ((byte) 1), storeable.getByteAt(6)); + for (int i = 0; i < table.getColumnsCount(); i++) { + if (i != 6) { + try { + storeable.getByteAt(i); + Assert.fail("Should be exception"); + } catch (ColumnFormatException e) { + // Ok. + } + } + } + } + + @Test + public void testGetFloatAt() throws Exception { + Assert.assertEquals((Object) 2.3f, storeable.getFloatAt(5)); + for (int i = 0; i < table.getColumnsCount(); i++) { + if (i != 5) { + try { + storeable.getFloatAt(i); + Assert.fail("Should be exception"); + } catch (ColumnFormatException e) { + // Ok. + } + } + } + } + + @Test + public void testGetDoubleAt() throws Exception { + Assert.assertEquals((Object) 5.5, storeable.getDoubleAt(4)); + for (int i = 0; i < table.getColumnsCount(); i++) { + if (i != 4) { + try { + storeable.getDoubleAt(i); + Assert.fail("Should be exception"); + } catch (ColumnFormatException e) { + // Ok. + } + } + } + } + + @Test + public void testGetBooleanAt() throws Exception { + Assert.assertEquals((Object) true, storeable.getBooleanAt(3)); + for (int i = 0; i < table.getColumnsCount(); i++) { + if (i != 3) { + try { + storeable.getBooleanAt(i); + Assert.fail("Should be exception"); + } catch (ColumnFormatException e) { + // Ok. + } + } + } + } + + @Test + public void testGetStringAt() throws Exception { + Assert.assertEquals("qwerty", storeable.getStringAt(2)); + for (int i = 0; i < table.getColumnsCount(); i++) { + if (i != 2) { + try { + storeable.getStringAt(i); + Assert.fail("Should be exception"); + } catch (ColumnFormatException e) { + // Ok. + } + } + } + } + + @Test + public void testToString() throws Exception { + storeable.setColumnAt(2, null); + Assert.assertEquals("Storeable[1,-3,,true,5.5,2.3,1]", storeable.toString()); + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/StringTableTest.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/StringTableTest.java new file mode 100644 index 000000000..6415164f8 --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/StringTableTest.java @@ -0,0 +1,475 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable.tests; + +import org.junit.*; +import org.junit.rules.TemporaryFolder; +import ru.fizteh.fivt.storage.strings.Table; +import ru.fizteh.fivt.storage.structured.TableProvider; +import ru.fizteh.fivt.students.LebedevAleksey.junit.AdditionalAssert; +import ru.fizteh.fivt.students.LebedevAleksey.storeable.Database; +import ru.fizteh.fivt.students.LebedevAleksey.storeable.DatabaseFactory; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Arrays; + +public class StringTableTest { + public static final String TEST_TABLE_NAME = "TestTable"; + @ClassRule + public static TemporaryFolder folder = new TemporaryFolder(); + private static File dbPath; + private static TableProvider database; + private Table table; + + @BeforeClass + public static void setUpClass() throws IOException { + dbPath = folder.newFolder("db"); + database = new DatabaseFactory().create(dbPath.getPath()); + } + + @Before + public void setUp() throws Exception { + ru.fizteh.fivt.storage.structured.Table tableInstance = database.getTable(TEST_TABLE_NAME); + if (tableInstance != null) { + database.removeTable(TEST_TABLE_NAME); + } + createTable(); + } + + protected void createTable() throws IOException { + table = StringTableWrapper.create(database.createTable(TEST_TABLE_NAME, Arrays.asList(String.class)), + (Database) database); + } + + + protected void getTable() throws IOException { + database = new DatabaseFactory().create(dbPath.getPath()); + table = StringTableWrapper.create(database.getTable(TEST_TABLE_NAME), + (Database) database); + } + + @After + public void tearDown() throws Exception { + database.removeTable(TEST_TABLE_NAME); + } + + @Test + public void testGetName() throws Exception { + Assert.assertEquals(TEST_TABLE_NAME, table.getName()); + Assert.assertEquals("Qwerty", + database.createTable("Qwerty", Arrays.asList(Integer.class, Boolean.class)).getName()); + database.removeTable("Qwerty"); + } + + @Test + public void testGetAndPutCommands() throws Exception { + Assert.assertEquals(null, table.get("a")); + Assert.assertEquals(null, table.get("b")); + Assert.assertEquals(null, table.get("c")); + Assert.assertEquals(null, table.get("d")); + + Assert.assertEquals(0, table.list().size()); + Assert.assertEquals(null, table.put("a", "b")); + AdditionalAssert.assertArrayAndListEquals(new String[]{"a"}, table.list()); + + Assert.assertEquals("b", table.get("a")); + Assert.assertEquals(null, table.get("b")); + Assert.assertEquals(null, table.get("c")); + Assert.assertEquals(null, table.get("d")); + + AdditionalAssert.assertArrayAndListEquals(new String[]{"a"}, table.list()); + Assert.assertEquals(null, table.put("c", "d")); + AdditionalAssert.assertArrayAndListEquals(new String[]{"a", "c"}, table.list()); + + Assert.assertEquals("b", table.get("a")); + Assert.assertEquals(null, table.get("b")); + Assert.assertEquals("d", table.get("c")); + Assert.assertEquals(null, table.get("d")); + + AdditionalAssert.assertArrayAndListEquals(new String[]{"a", "c"}, table.list()); + Assert.assertEquals("b", table.put("a", "d")); + AdditionalAssert.assertArrayAndListEquals(new String[]{"a", "c"}, table.list()); + + Assert.assertEquals("d", table.get("a")); + Assert.assertEquals(null, table.get("b")); + Assert.assertEquals("d", table.get("c")); + Assert.assertEquals(null, table.get("d")); + + Assert.assertEquals(null, table.get("qwerty")); + AdditionalAssert.assertArrayAndListEquals(new String[]{"a", "c"}, table.list()); + Assert.assertEquals(null, table.put("qwerty", "asdfg")); + AdditionalAssert.assertArrayAndListEquals(new String[]{"a", "c", "qwerty"}, table.list()); + Assert.assertEquals("asdfg", table.get("qwerty")); + AdditionalAssert.assertArrayAndListEquals(new String[]{"a", "c", "qwerty"}, table.list()); + Assert.assertEquals("asdfg", table.put("qwerty", "test")); + AdditionalAssert.assertArrayAndListEquals(new String[]{"a", "c", "qwerty"}, table.list()); + Assert.assertEquals("test", table.get("qwerty")); + } + + @Test + public void testRemoveAndListAndSizeCommands() throws Exception { + Assert.assertEquals(0, table.list().size()); + Assert.assertEquals(0, table.size()); + + table.put("a", "ab"); + table.put("b", "bb"); + table.put("c", "cb"); + + Assert.assertEquals(3, table.size()); + AdditionalAssert.assertStringArrayContainsSameItemsAsList(new String[]{"a", "b", "c"}, table.list()); + + Assert.assertEquals(null, table.remove("d")); + + Assert.assertEquals(3, table.size()); + AdditionalAssert.assertStringArrayContainsSameItemsAsList(new String[]{"a", "b", "c"}, table.list()); + + Assert.assertEquals("bb", table.remove("b")); + + Assert.assertEquals(2, table.size()); + AdditionalAssert.assertStringArrayContainsSameItemsAsList(new String[]{"a", "c"}, table.list()); + + table.put("name", "surname"); + + Assert.assertEquals(3, table.size()); + AdditionalAssert.assertStringArrayContainsSameItemsAsList(new String[]{"a", "c", "name"}, table.list()); + + Assert.assertEquals("cb", table.remove("c")); + + Assert.assertEquals(2, table.size()); + AdditionalAssert.assertStringArrayContainsSameItemsAsList(new String[]{"a", "name"}, table.list()); + Assert.assertEquals("ab", table.get("a")); + Assert.assertEquals("surname", table.get("name")); + + Assert.assertEquals("surname", table.remove("name")); + + Assert.assertEquals(1, table.size()); + AdditionalAssert.assertStringArrayContainsSameItemsAsList(new String[]{"a"}, table.list()); + Assert.assertEquals("ab", table.get("a")); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetIncorrectArgument() { + table.get(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testRemoveIncorrectArgument() { + table.remove(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testPutFirstIncorrectArgument() { + table.put(null, "a"); + } + + @Test(expected = IllegalArgumentException.class) + public void testPutSecondIncorrectArgument() { + table.put("a", null); + } + + @Test(expected = IllegalArgumentException.class) + public void testPutBothIncorrectArguments() { + table.put(null, null); + } + + @Test + public void testIncorrectPutDoesntChangeAnything() { + checkEmpty(); + try { + table.put("a", null); + Assert.fail("Should be exception"); + } catch (IllegalArgumentException e) { + //We are waiting this exception + } + checkEmpty(); + try { + table.put(null, "a"); + Assert.fail("Should be exception"); + } catch (IllegalArgumentException e) { + //We are waiting this exception + } + checkEmpty(); + try { + table.put(null, null); + Assert.fail("Should be exception"); + } catch (IllegalArgumentException e) { + //We are waiting this exception + } + checkEmpty(); + try { + table.get(null); + Assert.fail("Should be exception"); + } catch (IllegalArgumentException e) { + //We are waiting this exception + } + checkEmpty(); + try { + table.remove(null); + Assert.fail("Should be exception"); + } catch (IllegalArgumentException e) { + //We are waiting this exception + } + checkEmpty(); + table.put("a", "a"); + Assert.assertEquals(1, table.size()); + AdditionalAssert.assertStringArrayContainsSameItemsAsList(new String[]{"a"}, table.list()); + Assert.assertEquals("a", table.get("a")); + } + + private void checkEmpty() { + Assert.assertEquals(0, table.size()); + AdditionalAssert.assertStringArrayContainsSameItemsAsList(new String[0], table.list()); + Assert.assertEquals(null, table.get("a")); + } + + private void make1Change1() { + table.put("a", "a"); + } + + private void make1ChangeRemove() { + table.remove("a"); + } + + private void make2Change() { + table.put("a", "a"); + table.put("b", "b"); + table.put("c", "c"); + table.remove("c"); + } + + + private void make3Change() { + table.put("a", "ab"); + table.put("b", "bb"); + table.put("c", "cb"); + } + + private void make4Change() { + table.put("a", "a"); + table.remove("c"); + table.put("qwerty", "1234567"); + table.put("qw", "12"); + } + + @Test + public void testCommitCounts() throws Exception { + make1Change1(); + Assert.assertEquals(1, table.commit()); + Assert.assertEquals(0, table.commit()); + make1ChangeRemove(); + Assert.assertEquals(1, table.commit()); + Assert.assertEquals(0, table.commit()); + make2Change(); + Assert.assertEquals(2, table.rollback()); + make2Change(); + Assert.assertEquals(2, table.commit()); + make3Change(); + Assert.assertEquals(3, table.rollback()); + make3Change(); + Assert.assertEquals(3, table.commit()); + make3Change(); + Assert.assertEquals(0, table.rollback()); + make4Change(); + Assert.assertEquals(4, table.rollback()); + } + + @Test + public void testRollbackCount() throws Exception { + make1Change1(); + Assert.assertEquals(1, table.rollback()); + Assert.assertEquals(0, table.rollback()); + make1ChangeRemove(); + Assert.assertEquals(0, table.rollback()); + Assert.assertEquals(0, table.rollback()); + make2Change(); + Assert.assertEquals(2, table.commit()); + make3Change(); + Assert.assertEquals(3, table.commit()); + make3Change(); + Assert.assertEquals(0, table.commit()); + make4Change(); + Assert.assertEquals(4, table.commit()); + } + + private boolean hasFlag(int position, int value) { + return (((value >> position) % 2) == 1); + } + + + @Test + public void testCommit() throws IOException { + for (int i = 0; i < 1 << 5; i++) { + database.removeTable(TEST_TABLE_NAME); + createTable(); + Assert.assertEquals(null, table.get("a")); + Assert.assertEquals(null, table.get("b")); + Assert.assertEquals(null, table.get("c")); + Assert.assertEquals(null, table.get("d")); + + Assert.assertEquals(0, table.list().size()); + Assert.assertEquals(null, table.put("a", "b")); + checkCommit(i, 0); + AdditionalAssert.assertArrayAndListEquals(new String[]{"a"}, table.list()); + + Assert.assertEquals("b", table.get("a")); + Assert.assertEquals(null, table.get("b")); + Assert.assertEquals(null, table.get("c")); + Assert.assertEquals(null, table.get("d")); + + AdditionalAssert.assertArrayAndListEquals(new String[]{"a"}, table.list()); + Assert.assertEquals(null, table.put("c", "d")); + checkCommit(i, 1); + AdditionalAssert.assertArrayAndListEquals(new String[]{"a", "c"}, table.list()); + + Assert.assertEquals("b", table.get("a")); + Assert.assertEquals(null, table.get("b")); + Assert.assertEquals("d", table.get("c")); + Assert.assertEquals(null, table.get("d")); + + AdditionalAssert.assertArrayAndListEquals(new String[]{"a", "c"}, table.list()); + Assert.assertEquals("b", table.put("a", "d")); + checkCommit(i, 2); + AdditionalAssert.assertArrayAndListEquals(new String[]{"a", "c"}, table.list()); + + Assert.assertEquals("d", table.get("a")); + Assert.assertEquals(null, table.get("b")); + Assert.assertEquals("d", table.get("c")); + Assert.assertEquals(null, table.get("d")); + + Assert.assertEquals(null, table.get("qwerty")); + AdditionalAssert.assertArrayAndListEquals(new String[]{"a", "c"}, table.list()); + Assert.assertEquals(null, table.put("qwerty", "asdfg")); + checkCommit(i, 3); + AdditionalAssert.assertArrayAndListEquals(new String[]{"a", "c", "qwerty"}, table.list()); + Assert.assertEquals("asdfg", table.get("qwerty")); + AdditionalAssert.assertArrayAndListEquals(new String[]{"a", "c", "qwerty"}, table.list()); + Assert.assertEquals("asdfg", table.put("qwerty", "test")); + checkCommit(i, 4); + AdditionalAssert.assertArrayAndListEquals(new String[]{"a", "c", "qwerty"}, table.list()); + Assert.assertEquals("test", table.get("qwerty")); + } + for (int i = 0; i < 1 << 6; i++) { + database.removeTable(TEST_TABLE_NAME); + createTable(); + + Assert.assertEquals(0, table.list().size()); + Assert.assertEquals(0, table.size()); + table.put("a", "ab"); + table.put("b", "bb"); + table.put("c", "cb"); + checkCommit(i, 0); + + Assert.assertEquals(3, table.size()); + AdditionalAssert.assertStringArrayContainsSameItemsAsList(new String[]{"a", "b", "c"}, table.list()); + + Assert.assertEquals(null, table.remove("d")); + checkCommit(i, 1); + + Assert.assertEquals(3, table.size()); + AdditionalAssert.assertStringArrayContainsSameItemsAsList(new String[]{"a", "b", "c"}, table.list()); + + Assert.assertEquals("bb", table.remove("b")); + checkCommit(i, 2); + + Assert.assertEquals(2, table.size()); + AdditionalAssert.assertStringArrayContainsSameItemsAsList(new String[]{"a", "c"}, table.list()); + + table.put("name", "surname"); + checkCommit(i, 3); + + Assert.assertEquals(3, table.size()); + AdditionalAssert.assertStringArrayContainsSameItemsAsList(new String[]{"a", "c", "name"}, table.list()); + + Assert.assertEquals("cb", table.remove("c")); + checkCommit(i, 4); + + Assert.assertEquals(2, table.size()); + AdditionalAssert.assertStringArrayContainsSameItemsAsList(new String[]{"a", "name"}, table.list()); + Assert.assertEquals("ab", table.get("a")); + Assert.assertEquals("surname", table.get("name")); + + Assert.assertEquals("surname", table.remove("name")); + checkCommit(i, 5); + + Assert.assertEquals(1, table.size()); + AdditionalAssert.assertStringArrayContainsSameItemsAsList(new String[]{"a"}, table.list()); + Assert.assertEquals("ab", table.get("a")); + } + } + + private void checkCommit(int value, int position) throws IOException { + if (hasFlag(position, value)) { + checkCommit(); + } + } + + private int checkCommit() throws IOException { + int changes = table.commit(); + getTable(); + Path tablePath = dbPath.toPath().resolve(TEST_TABLE_NAME); + File[] directories = tablePath.toFile().listFiles(); + for (File dir : directories) { + if (dir.isDirectory()) { + File[] files = dir.listFiles(); + Assert.assertTrue(files.length > 0); + for (File file : files) { + Assert.assertTrue(file.length() > 0); + } + } else { + Assert.assertEquals("signature.tsv", dir.getName()); + } + } + return changes; + } + + @Test + public void testCommitWithCollisions() throws IOException { + table.put("Q1", "text"); + table.put("r2", "text"); + Assert.assertEquals(2, checkCommit()); + Assert.assertEquals(2, table.size()); + AdditionalAssert.assertStringArrayContainsSameItemsAsList(new String[]{"Q1", "r2"}, table.list()); + table.remove("Q1"); + table.put("test", "text"); + Assert.assertEquals(2, checkCommit()); + Assert.assertEquals(2, table.size()); + AdditionalAssert.assertStringArrayContainsSameItemsAsList(new String[]{"test", "r2"}, table.list()); + table.put("Q1", "text"); + table.put("r2", "text"); + Assert.assertEquals(1, checkCommit()); + Assert.assertEquals(3, table.size()); + AdditionalAssert.assertStringArrayContainsSameItemsAsList(new String[]{"Q1", "r2", "test"}, table.list()); + table.remove("Q1"); + table.remove("r2"); + Assert.assertEquals(2, checkCommit()); + Assert.assertEquals(1, table.size()); + AdditionalAssert.assertStringArrayContainsSameItemsAsList(new String[]{"test"}, table.list()); + } + + @Test + public void testRollback() { + table.put("a", "a"); + table.put("b", "b"); + table.put("c", "c"); + table.commit(); + Assert.assertEquals("a", table.get("a")); + Assert.assertEquals("b", table.get("b")); + Assert.assertEquals("c", table.get("c")); + Assert.assertEquals(3, table.size()); + AdditionalAssert.assertStringArrayContainsSameItemsAsList(new String[]{"a", "c", "b"}, table.list()); + table.put("a", "e"); + table.remove("b"); + table.put("d", "d"); + Assert.assertEquals("e", table.get("a")); + Assert.assertEquals("c", table.get("c")); + Assert.assertEquals("d", table.get("d")); + Assert.assertEquals(3, table.size()); + AdditionalAssert.assertStringArrayContainsSameItemsAsList(new String[]{"a", "c", "d"}, table.list()); + table.rollback(); + Assert.assertEquals("a", table.get("a")); + Assert.assertEquals("b", table.get("b")); + Assert.assertEquals("c", table.get("c")); + Assert.assertEquals(3, table.size()); + AdditionalAssert.assertStringArrayContainsSameItemsAsList(new String[]{"a", "c", "b"}, table.list()); + } +} diff --git a/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/StringTableWrapper.java b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/StringTableWrapper.java new file mode 100644 index 000000000..c122189e8 --- /dev/null +++ b/src/ru/fizteh/fivt/students/LebedevAleksey/storeable/tests/StringTableWrapper.java @@ -0,0 +1,87 @@ +package ru.fizteh.fivt.students.LebedevAleksey.storeable.tests; + + +import ru.fizteh.fivt.storage.structured.Storeable; +import ru.fizteh.fivt.students.LebedevAleksey.junit.DatabaseException; +import ru.fizteh.fivt.students.LebedevAleksey.storeable.Database; +import ru.fizteh.fivt.students.LebedevAleksey.storeable.StoreableTable; + +import java.io.IOException; +import java.text.ParseException; +import java.util.List; + +public class StringTableWrapper implements ru.fizteh.fivt.storage.strings.Table { + + private StoreableTable table; + private Database database; + + private StringTableWrapper(StoreableTable table, Database database) { + this.table = table; + this.database = database; + } + + public static StringTableWrapper create(ru.fizteh.fivt.storage.structured.Table table, Database database) { + if (table == null) { + return null; + } else { + return new StringTableWrapper((StoreableTable) table, database); + } + } + + + @Override + public String getName() { + return table.getName(); + } + + @Override + public String get(String key) { + Storeable storeable = table.get(key); + return (storeable == null ? null : (String) storeable.getColumnAt(0)); + } + + @Override + public String put(String key, String value) { + try { + Storeable result; + if (value == null) { + result = table.put(key, null); + } else { + result = table.put(key, database.deserialize(table, "[\"" + value + "\"]")); + } + return result == null ? null : (String) result.getColumnAt(0); + } catch (ParseException e) { + throw new DatabaseException(e); + } + } + + @Override + public String remove(String key) { + Storeable result = table.remove(key); + return result == null ? null : (String) result.getColumnAt(0); + } + + @Override + public int size() { + return table.size(); + } + + @Override + public int commit() { + try { + return table.commit(); + } catch (IOException e) { + throw new DatabaseException(e); + } + } + + @Override + public int rollback() { + return table.rollback(); + } + + @Override + public List list() { + return table.list(); + } +}