diff --git a/README.md b/README.md index 718085524..9508b8ea4 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ## Семинары В папке [tasks](tasks) находятся описания заданий на семинары. Для написания программ необходимо руководствоваться [соглашениями -по оформлению кода](http://www.oracle.com/technetwork/java/codeconv-138413.html) +по оформлению кода](http://www.oracle.com/technetwork/java/codeconvtoc-136057.html) от Oracle (Code Conventions). В документе [github-workflow](github-workflow.md) описаны @@ -14,9 +14,22 @@ [checkstyle](http://checkstyle.sourceforge.net/), которая проверяет код на соответствие Code Conventions. +## Лекции +1. [Java, JVM, примитивные типы](https://yadi.sk/d/_ZgbGf9NbXraZ) +2. [Object, String, исключения](https://yadi.sk/d/MCu6krbtbXrgZ) +3. [IO Stream, инициализация объектов, enum](https://yadi.sk/d/MinWJhG0bteEr) +4. [Лямбды, аннотации, обобщения, коллекции](https://yadi.sk/d/hxkoP81rbteTp) +5. [Reflection, Proxy, JUnit](https://yadi.sk/i/Ku-C6VYOc4icJ) +6. [Сериализация, XML, JSON](https://yadi.sk/d/8upuEG2ecRknU) +7. [Многопоточность](https://yadi.sk/i/FsBB-AXVcRkoH) +8. [Многопоточность](https://yadi.sk/i/IY2kKLoacgaBr) + ## Список литературы * Брюс Эккель — Философия Java (Thinking in Java) * Brian Goetz — Java Concurrency in Practive (продвинутая книжка по многопоточности в Java) * [Спецификации по JVM и JLS](http://docs.oracle.com/javase/specs/index.html) * [Code Conventions](http://www.oracle.com/technetwork/java/codeconv-138413.html) + +## Успеваемость +[Таблица на Google Docs](https://docs.google.com/spreadsheet/ccc?key=0Ag7dwrMmZBKedGk1Q1RfSXh1RzZyeEJhaE94dW1CaGc&usp=sharing) diff --git a/build.xml b/build.xml index f949c9245..e1a79f0f2 100644 --- a/build.xml +++ b/build.xml @@ -1,4 +1,4 @@ - + @@ -18,7 +18,7 @@ - + @@ -29,18 +29,22 @@ + + + - + + diff --git a/github-workflow.md b/github-workflow.md index e1ae4cab3..41a464c2c 100644 --- a/github-workflow.md +++ b/github-workflow.md @@ -13,4 +13,4 @@ Чтобы сократить количество итераций на проверку задания, полезно самостоятельно удостовериться, что Code Conventions соблюдены. Для этого нужно запустить сборку проекта с помощью ```ant checkstyle```. Вместо этого можно поставить -соответствующий плагин в IDE и настроить этот плагин на конфигурационный файл ```bin/checkstyle-rules.xml```. +соответствующий плагин в IDE и настроить этот плагин на конфигурационный файл ```lib/checkstyle-rules.xml```. diff --git a/lib/checkstyle-rules.xml b/lib/checkstyle-rules.xml new file mode 100644 index 000000000..c73c18923 --- /dev/null +++ b/lib/checkstyle-rules.xml @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/checkstyle-suppress.xml b/lib/checkstyle-suppress.xml new file mode 100644 index 000000000..2608395c6 --- /dev/null +++ b/lib/checkstyle-suppress.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/database/ColumnFormatException.java b/src/ru/fizteh/fivt/students/andrewzhernov/database/ColumnFormatException.java new file mode 100644 index 000000000..7bb1cc858 --- /dev/null +++ b/src/ru/fizteh/fivt/students/andrewzhernov/database/ColumnFormatException.java @@ -0,0 +1,25 @@ +package ru.fizteh.fivt.students.andrewzhernov.database; + +/** + * Бросается при попытке извлечь из колонки {@link Storeable} значение не соответствующего типа, + * либо подставить в колонку значение несоответствующего типа. + */ +public class ColumnFormatException extends IllegalArgumentException { + + private static final long serialVersionUID = 1L; + + public ColumnFormatException() { + } + + public ColumnFormatException(String s) { + super(s); + } + + public ColumnFormatException(String message, Throwable cause) { + super(message, cause); + } + + public ColumnFormatException(Throwable cause) { + super(cause); + } +} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/junit/Command.java b/src/ru/fizteh/fivt/students/andrewzhernov/database/Command.java similarity index 56% rename from src/ru/fizteh/fivt/students/andrewzhernov/junit/Command.java rename to src/ru/fizteh/fivt/students/andrewzhernov/database/Command.java index b9249d1d8..4f7f043be 100644 --- a/src/ru/fizteh/fivt/students/andrewzhernov/junit/Command.java +++ b/src/ru/fizteh/fivt/students/andrewzhernov/database/Command.java @@ -1,11 +1,11 @@ -package ru.fizteh.fivt.students.andrewzhernov.junit; +package ru.fizteh.fivt.students.andrewzhernov.database; public class Command { private String name; private int numArgs; - private HandlerInterface processor; + private Handler processor; - public Command(String name, int numArgs, HandlerInterface processor) { + public Command(String name, int numArgs, Handler processor) { this.name = name; this.numArgs = numArgs; this.processor = processor; @@ -17,10 +17,10 @@ public String getName() { public void execute(TableProvider database, String[] params) throws Exception { if (params.length != numArgs) { - throw new Exception(String.format("Invalid number of arguments: %d expected, %d found.", + throw new IllegalArgumentException(String.format(name + ": invalid number of arguments: %d expected, %d found.", numArgs, params.length)); } else { - processor.handle(processor.execute(database, params)); + processor.handle(database, processor.execute(database, params)); } } } diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/database/ConnectionInterruptException.java b/src/ru/fizteh/fivt/students/andrewzhernov/database/ConnectionInterruptException.java new file mode 100644 index 000000000..30c8804db --- /dev/null +++ b/src/ru/fizteh/fivt/students/andrewzhernov/database/ConnectionInterruptException.java @@ -0,0 +1,7 @@ +package ru.fizteh.fivt.students.andrewzhernov.database; + +public class ConnectionInterruptException extends Exception { + public ConnectionInterruptException(String message) { + super(message); + } +} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/database/Handler.java b/src/ru/fizteh/fivt/students/andrewzhernov/database/Handler.java new file mode 100644 index 000000000..14ec003fc --- /dev/null +++ b/src/ru/fizteh/fivt/students/andrewzhernov/database/Handler.java @@ -0,0 +1,6 @@ +package ru.fizteh.fivt.students.andrewzhernov.database; + +public interface Handler { + Object execute(TableProvider database, String[] args) throws Exception; + void handle(TableProvider database, Object object) throws Exception; +} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/database/HashMapTable.java b/src/ru/fizteh/fivt/students/andrewzhernov/database/HashMapTable.java new file mode 100644 index 000000000..c7834f629 --- /dev/null +++ b/src/ru/fizteh/fivt/students/andrewzhernov/database/HashMapTable.java @@ -0,0 +1,261 @@ +package ru.fizteh.fivt.students.andrewzhernov.database; + +import static java.util.EnumSet.of; +import static ru.fizteh.fivt.students.andrewzhernov.database.PermissionsValidator.Permissions.*; + +import java.io.*; +import java.nio.file.Paths; +import java.io.RandomAccessFile; +import java.util.List; +import java.util.LinkedList; +import java.util.Map; +import java.util.HashMap; + +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class HashMapTable implements Table { + private static final int DIRECTORIES_COUNT = 16; + private static final int FILES_COUNT = 16; + private static final String DIR_EXTENSION = ".dir"; + private static final String FILE_EXTENSION = ".dat"; + private static final String ENCODING = "UTF-8"; + + private TableProvider provider; + private String name; + private int size; + private Map disk; + private ThreadLocal> diff; + private List> columnTypes; + + private ReadWriteLock rwlLock = new ReentrantReadWriteLock(); + + public HashMapTable(TableProvider tableProvider, String tableName, List> columnTypes) { + PermissionsValidator.validateTableName(tableName, of(NOT_NULL)); + provider = tableProvider; + name = tableName; + columnTypes = columnTypes; + size = 0; + disk = new HashMap<>(); + diff = ThreadLocal.withInitial(() -> new HashMap()); + } + + @Override + public String getName() { + return name; + } + + @Override + public int getNumberOfUncommittedChanges() { + return diff.get().size(); + } + + @Override + public int size() { + return size; + } + + @Override + public Storeable put(String key, Storeable value) throws IllegalArgumentException { + if (key == null || value == null) { + throw new IllegalArgumentException("Invalid key/value"); + } + if (!value.equals(disk.get(key))) { + Storeable diffValue = diff.get().put(key, value); + Storeable result = diffValue != null ? diffValue : disk.get(key); + if (result == null) { + ++size; + } + return result; + } + return value; + } + + @Override + public Storeable get(String key) throws IllegalArgumentException { + if (key == null) { + throw new IllegalArgumentException("Invalid key"); + } + Storeable diffValue = diff.get().get(key); + return diffValue != null ? diffValue : disk.get(key); + } + + @Override + public Storeable remove(String key) throws IllegalArgumentException { + if (key == null) { + throw new IllegalArgumentException("Invalid key"); + } + Storeable result = null; + Storeable diskValue = disk.get(key); + if (diskValue != null) { + Storeable diffValue = diff.get().put(key, null); + result = diffValue != null ? diffValue : diskValue; + } else { + result = diff.get().remove(key); + } + if (result != null) { + --size; + } + return result; + } + + @Override + public List list() { + List list = new LinkedList<>(); + for (String key : disk.keySet()) { + if (!diff.get().containsKey(key)) { + list.add(key); + } + } + for (String key : diff.get().keySet()) { + if (diff.get().get(key) != null) { + list.add(key); + } + } + return list; + } + + @Override + public int commit() throws Exception { + int amount = diff.get().size(); + for (String key : diff.get().keySet()) { + Storeable value = diff.get().get(key); + if (value != null) { + disk.put(key, value); + } else { + disk.remove(key); + } + } + diff.get().clear(); + rwlLock.writeLock().lock(); + try { + saveTable(); + } finally { + rwlLock.writeLock().unlock(); + } + return amount; + } + + @Override + public int rollback() { + int amount = diff.get().size(); + diff.get().clear(); + size = disk.size(); + return amount; + } + + @Override + public String getTableFolder() { + return Paths.get(provider.getDbFolder(), name).toString(); + } + + public String getTableBucket(int bucket) { + return Paths.get(getTableFolder(), new Integer(bucket).toString() + DIR_EXTENSION).toString(); + } + + public String getTableSubBucket(String bucket, int subBucket) { + return Paths.get(bucket, new Integer(subBucket).toString() + FILE_EXTENSION).toString(); + } + + private static String readItem(RandomAccessFile file) throws Exception { + int wordSize = file.readInt(); + byte[] word = new byte[wordSize]; + file.read(word, 0, wordSize); + return new String(word, ENCODING); + } + + private void loadSubBucket(String subBucket) throws Exception { + try (RandomAccessFile file = new RandomAccessFile(subBucket, "r")) { + while (file.getFilePointer() < file.length()) { + String key = readItem(file); + String value = readItem(file); + disk.put(key, provider.deserialize(provider.getTable(name), value)); + } + } catch (Exception | OutOfMemoryError e) { + throw new ConnectionInterruptException(subBucket + ": invalid file format"); + } + } + + private void loadBucket(String bucket) throws Exception { + for (int subBucketIndex = 0; subBucketIndex < FILES_COUNT; ++subBucketIndex) { + String subBucket = getTableSubBucket(bucket, subBucketIndex); + if (PermissionsValidator.validate(subBucket, of(NOT_NULL, EXISTS, CAN_READ, IS_FILE))) { + loadSubBucket(subBucket); + } + } + } + + @Override + public void loadTable() throws Exception { + PermissionsValidator.validate(getTableFolder(), of(NOT_NULL, EXISTS, CAN_READ, IS_DIRECTORY)); + disk.clear(); + for (int bucketIndex = 0; bucketIndex < DIRECTORIES_COUNT; ++bucketIndex) { + String bucket = getTableBucket(bucketIndex); + if (PermissionsValidator.validate(bucket, of(NOT_NULL, EXISTS, CAN_READ, IS_DIRECTORY))) { + loadBucket(bucket); + } + } + size = disk.size(); + } + + private static void writeItem(RandomAccessFile file, String word) throws Exception { + byte[] byteWord = word.getBytes(ENCODING); + file.writeInt(byteWord.length); + file.write(byteWord); + } + + private boolean saveSubBucket(String subBucket, int bucketIndex, int subBucketIndex) throws Exception { + boolean isWritten = false; + RandomAccessFile file = new RandomAccessFile(subBucket, "rw"); + for (String key : disk.keySet()) { + int hashcode = key.hashCode(); + int directoryNumber = hashcode % DIRECTORIES_COUNT; + int fileNumber = hashcode / DIRECTORIES_COUNT % FILES_COUNT; + if (bucketIndex == directoryNumber && subBucketIndex == fileNumber) { + String value = provider.serialize(provider.getTable(name), disk.get(key)); + writeItem(file, key); + writeItem(file, value); + isWritten = true; + } + } + file.close(); + return isWritten; + } + + private void saveBucket(String bucket, int bucketIndex) throws Exception { + int usedFiles = FILES_COUNT; + for (int subBucketIndex = 0; subBucketIndex < FILES_COUNT; ++subBucketIndex) { + String subBucket = getTableSubBucket(bucket, subBucketIndex); + if (PermissionsValidator.validate(subBucket, of(NOT_NULL, CREATE_FILE_IF_NOT_EXISTS, CAN_WRITE, IS_FILE))) { + if (!saveSubBucket(subBucket, bucketIndex, subBucketIndex)) { + new File(subBucket).delete(); + --usedFiles; + } + } + } + if (usedFiles == 0) { + new File(bucket).delete(); + } + } + + private void saveTable() throws Exception { + PermissionsValidator.validate(getTableFolder(), of(NOT_NULL, EXISTS, CAN_WRITE, IS_DIRECTORY)); + for (int bucketIndex = 0; bucketIndex < DIRECTORIES_COUNT; ++bucketIndex) { + String bucket = getTableBucket(bucketIndex); + if (PermissionsValidator.validate(bucket, + of(NOT_NULL, CREATE_DIRECTORY_IF_NOT_EXISTS, CAN_WRITE, IS_DIRECTORY))) { + saveBucket(bucket, bucketIndex); + } + } + } + + @Override + public int getColumnsCount() { + return columnTypes.size(); + } + + @Override + public Class getColumnType(int columnIndex) throws IndexOutOfBoundsException { + return columnTypes.get(columnIndex); + } +} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/database/HashMapTableProvider.java b/src/ru/fizteh/fivt/students/andrewzhernov/database/HashMapTableProvider.java new file mode 100644 index 000000000..79c5a5f1d --- /dev/null +++ b/src/ru/fizteh/fivt/students/andrewzhernov/database/HashMapTableProvider.java @@ -0,0 +1,308 @@ +package ru.fizteh.fivt.students.andrewzhernov.database; + +import static java.util.EnumSet.of; +import static ru.fizteh.fivt.students.andrewzhernov.database.PermissionsValidator.Permissions.CAN_READ; +import static ru.fizteh.fivt.students.andrewzhernov.database.PermissionsValidator.Permissions.CAN_WRITE; +import static ru.fizteh.fivt.students.andrewzhernov.database.PermissionsValidator.Permissions.CREATE_DIRECTORY_IF_NOT_EXISTS; +import static ru.fizteh.fivt.students.andrewzhernov.database.PermissionsValidator.Permissions.EXISTS; +import static ru.fizteh.fivt.students.andrewzhernov.database.PermissionsValidator.Permissions.IS_DIRECTORY; +import static ru.fizteh.fivt.students.andrewzhernov.database.PermissionsValidator.Permissions.IS_FILE; +import static ru.fizteh.fivt.students.andrewzhernov.database.PermissionsValidator.Permissions.NOT_NULL; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Paths; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class HashMapTableProvider implements TableProvider { + private static final String[] invalidCharacters = {".", "|", "\\", "*", "\"", "\'", ":", "/", "?", "<", ">"}; + + private static final String SIGNATURE_FILE = "signature.tsv"; + + private String dbFolder; + private Map nameToTableMap; + private Map>> nameToSignatureMap; + private String currentTable; + + private ReadWriteLock rwlLock = new ReentrantReadWriteLock(); + + public HashMapTableProvider(String folder) throws Exception { + PermissionsValidator.validateDbFolder(folder, of(NOT_NULL)); + PermissionsValidator.validate(folder, of(CREATE_DIRECTORY_IF_NOT_EXISTS, CAN_READ, IS_DIRECTORY)); + dbFolder = folder; + nameToTableMap = new HashMap<>(); + nameToSignatureMap = new HashMap<>(); + currentTable = null; + loadDatabase(); + } + + private boolean isValidName(String tableName) { + for (String character : invalidCharacters) { + if (tableName.contains(character)) { + return false; + } + } + return true; + } + + private String getSignatureFilename() { + return dbFolder + File.separator + SIGNATURE_FILE; + } + + private void loadSignature() throws Exception { + String signatureFilename = getSignatureFilename(); + if (!PermissionsValidator.validate(signatureFilename, of(NOT_NULL, EXISTS, IS_FILE))) { + File signatureFile = new File(signatureFilename); + signatureFile.createNewFile(); + return; + } + + if (PermissionsValidator.validateFileEmpty(signatureFilename)) { + return; + } + + BufferedReader in = new BufferedReader(new FileReader(signatureFilename)); + + while (in.ready()) { + String s = in.readLine(); + String[] tokens = s.split("\t"); + + if (tokens.length != 2) { + throw new Exception("Wrong line in signatures.tsv: " + s + "\n"); + } + nameToSignatureMap.put(tokens[0], Utils.parseSignature(tokens[1])); + } + in.close(); + } + + public void saveSignature() { + try (PrintWriter out = new PrintWriter(getSignatureFilename())) { + for (String tableName : nameToSignatureMap.keySet()) { + String signature = Utils.makeSignature(nameToSignatureMap.get(tableName)); + out.printf("%s\t%s", tableName, signature); + } + } catch (FileNotFoundException e) { + System.err.println("Coutldn't save signature: " + e.getMessage()); + } + } + + private void loadDatabase() throws Exception { + loadSignature(); + for (String tableName : new File(dbFolder).list()) { + if (tableName.equals(SIGNATURE_FILE)) { + continue; + } + if (!isValidName(tableName)) { + throw new IllegalArgumentException(tableName + ": incorrect table name"); + } + Table table = new HashMapTable(this, tableName, nameToSignatureMap.get(tableName)); + nameToTableMap.put(tableName, table); + rwlLock.readLock().lock(); + try { + table.loadTable(); + } finally { + rwlLock.readLock().unlock(); + } + } + } + + private void checkUnsavedChanges() throws IllegalStateException { + int count = getCurrentTable().getNumberOfUncommittedChanges(); + if (count > 0) { + throw new IllegalStateException(Integer.toString(count) + " unsaved changes"); + } + } + + @Override + public String getDbFolder() { + return dbFolder; + } + + @Override + public Table getTable(String name) throws IllegalArgumentException { + if (name == null) { + throw new IllegalArgumentException("no table"); + } + return nameToTableMap.get(name); + } + + @Override + public Table getCurrentTable() throws IllegalArgumentException { + return getTable(currentTable); + } + + @Override + public Table createTable(String name, List> columnTypes) throws RuntimeException, IOException { + if (name == null || !isValidName(name)) { + throw new IllegalArgumentException(name + ": incorrect table name"); + } else if (nameToTableMap.containsKey(name)) { + throw new IllegalArgumentException(name + " exists"); + } + + Table table = new HashMapTable(this, name, columnTypes); + nameToTableMap.put(name, table); + nameToSignatureMap.put(name, columnTypes); + PermissionsValidator.validate(table.getTableFolder(), + of(NOT_NULL, CREATE_DIRECTORY_IF_NOT_EXISTS, CAN_WRITE, IS_DIRECTORY)); + return table; + } + + @Override + public void removeTable(String name) throws IllegalArgumentException, IllegalStateException { + if (name == null || !isValidName(name)) { + throw new IllegalArgumentException(name + ": incorrect table name"); + } else if (!nameToTableMap.containsKey(name)) { + throw new IllegalStateException(name + " not exist"); + } else if (name.equals(currentTable)) { + currentTable = null; + } + rwlLock.writeLock().lock(); + try { + Utils.removeDir(Paths.get(dbFolder, name)); + } finally { + rwlLock.writeLock().unlock(); + } + nameToTableMap.remove(name); + } + + @Override + public String useTable(String name) throws IllegalArgumentException, IllegalStateException { + if (name == null || !isValidName(name)) { + throw new IllegalArgumentException(name + ": incorrect table name"); + } else if (!nameToTableMap.containsKey(name)) { + throw new IllegalArgumentException(name + " not exist"); + } else if (currentTable != null && !name.equals(currentTable)) { + checkUnsavedChanges(); + } + currentTable = name; + return currentTable; + } + + @Override + public List getTableNames() { + return new LinkedList<>(nameToTableMap.keySet()); + } + + @Override + public void exit() throws IllegalStateException { + if (currentTable != null) { + checkUnsavedChanges(); + } + saveSignature(); + } + + @Override + public Storeable deserialize(Table table, String value) throws ParseException { + if (value.length() < 2) { + throw new ParseException(value + ": wrong value length", 0); + } + if (value.charAt(0) != '(') { + throw new ParseException(value + ": value must start with '('", 0); + } + if (value.charAt(value.length() - 1) != ')') { + throw new ParseException(value + ": value must end with ')'", value.length() - 1); + } + + value = value.substring(1, value.length() - 1); + String[] elements = value.split(","); + + List expectedTypes = nameToSignatureMap.get(table.getName()); + if (elements.length != expectedTypes.size()) { + throw new ParseException( + String.format("%s: value must contain %d element(s)", value, expectedTypes.size()), 0); + } + + List objects = new ArrayList<>(); + + int i = 0; + for (String element : elements) { + try { + if (expectedTypes.get(i).equals(Integer.class)) { + objects.add(Integer.parseInt(element)); + } else if (expectedTypes.get(i).equals(Long.class)) { + objects.add(Long.parseLong(element)); + } else if (expectedTypes.get(i).equals(Byte.class)) { + objects.add(Byte.parseByte(element)); + } else if (expectedTypes.get(i).equals(Float.class)) { + objects.add(Float.parseFloat(element)); + } else if (expectedTypes.get(i).equals(Double.class)) { + objects.add(Double.parseDouble(element)); + } else if (expectedTypes.get(i).equals(Boolean.class)) { + objects.add(Boolean.parseBoolean(element)); + } else if (expectedTypes.get(i).equals(String.class)) { + objects.add(element); + } else { + throw new ParseException("Wrong element type: " + element, 0); + } + } catch (NumberFormatException e) { + throw new ParseException( + String.format("Couldn't parse value: %s", e.getMessage()), 0); + } + i++; + } + + Storeable result = new JsonStoreable(objects); + + return result; + } + + @Override + public String serialize(Table table, Storeable value) throws ColumnFormatException { + List> expectedTypes = nameToSignatureMap.get(table.getName()); + + String result = "("; + + List types = new LinkedList<>(); + int i = 0; + for (Class type : expectedTypes) { + if (type.equals(Integer.class)) { + types.add(value.getIntAt(i).toString()); + } else if (type.equals(Long.class)) { + types.add(value.getLongAt(i).toString()); + } else if (type.equals(Byte.class)) { + types.add(value.getByteAt(i).toString()); + } else if (type.equals(Float.class)) { + types.add(value.getFloatAt(i).toString()); + } else if (type.equals(Double.class)) { + types.add(value.getDoubleAt(i).toString()); + } else if (type.equals(Boolean.class)) { + types.add(value.getBooleanAt(i).toString()); + } else if (type.equals(String.class)) { + types.add(value.getStringAt(i)); + } + i++; + } + result += String.join(",", types) + ")"; + + return result; + } + + @Override + public Storeable createFor(Table table, List values) throws ColumnFormatException, + IndexOutOfBoundsException { + return null; + } + + @Override + public Storeable createFor(Table table) { + return null; + } + + @Override + public Object showTables() { + return nameToTableMap; + } +} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/database/Index.java b/src/ru/fizteh/fivt/students/andrewzhernov/database/Index.java new file mode 100644 index 000000000..c986fd97d --- /dev/null +++ b/src/ru/fizteh/fivt/students/andrewzhernov/database/Index.java @@ -0,0 +1,26 @@ +package ru.fizteh.fivt.students.andrewzhernov.database; + +/** + * Представляет базу для интерфейс для работы с таблицей, содержащей ключи-значения. + * Используется также для доступа к поисковому индексу. + */ +public interface Index { + /** + * Возвращает название таблицы или индекса. + * + * @return Название таблицы. + */ + String getName(); + + /** + * Получает значение по указанному ключу. + * + * @param key Ключ для поиска значения. Не может быть null. + * Для индексов по не-строковым полям аргумент представляет собой сериализованное значение колонки. + * Его потребуется распарсить. + * @return Значение. Если не найдено, возвращает null. + * + * @throws IllegalArgumentException Если значение параметра key является null. + */ + Storeable get(String key); +} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/database/IndexProvider.java b/src/ru/fizteh/fivt/students/andrewzhernov/database/IndexProvider.java new file mode 100644 index 000000000..8eff65218 --- /dev/null +++ b/src/ru/fizteh/fivt/students/andrewzhernov/database/IndexProvider.java @@ -0,0 +1,30 @@ +package ru.fizteh.fivt.students.andrewzhernov.database; + +/** + * Дополнительный интерфейс к {@link ru.fizteh.fivt.storage.structured.TableProvider}, позволяющий создавать индексы на + * основе имеющейся таблицы. + */ +public interface IndexProvider extends TableProvider { + /** + * + * Возвращает индекс с таким именем, если он существует. + * + * @param name Имя индекса. Не должно быть null. + * @return Объект индекса + * @throws IllegalArgumentException Если имя индекса невалидно. + */ + Index getIndex(String name) throws IllegalArgumentException; + + /** + * Создает индекс. + * + * @param table Таблица, для которой нужно создать индекс. + * @param column Номер колонки. + * @param name Имя для будущего индекса. + * @return Свежесозданный индекс. null, если индекс уже был создан. + * @throws IllegalArgumentException Если таблица невалидна, указана неверная колонка или имя индекса, + * а также, если сущность с таким именем уже существует. + * @throws IllegalStateException Если индекс содержит невалидные элементы. + */ + Index createIndex(Table table, int column, String name) throws IllegalArgumentException, IllegalStateException; +} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/database/JsonStoreable.java b/src/ru/fizteh/fivt/students/andrewzhernov/database/JsonStoreable.java new file mode 100644 index 000000000..ae94fd2aa --- /dev/null +++ b/src/ru/fizteh/fivt/students/andrewzhernov/database/JsonStoreable.java @@ -0,0 +1,85 @@ +package ru.fizteh.fivt.students.andrewzhernov.database; + +import java.util.List; + +public class JsonStoreable implements Storeable { + private Object[] columnValues; + + public JsonStoreable(List values) { + columnValues = values.toArray(); + } + + @Override + public void setColumnAt(int columnIndex, Object value) throws ColumnFormatException, IndexOutOfBoundsException { + if (value.getClass() != columnValues[columnIndex].getClass()) { + throw new ColumnFormatException("Invalid column format: expected " + + columnValues[columnIndex].getClass().getName() + ", got " + value.getClass().getName()); + } + columnValues[columnIndex] = value; + } + + @Override + public Object getColumnAt(int columnIndex) throws IndexOutOfBoundsException { + return columnValues[columnIndex]; + } + + @Override + public Integer getIntAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException { + if (!(columnValues[columnIndex] instanceof Integer)) { + throw new ColumnFormatException("Column is not Integer"); + } + return (Integer) columnValues[columnIndex]; + } + + @Override + public Long getLongAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException { + if (!(columnValues[columnIndex] instanceof Long)) { + throw new ColumnFormatException("Column is not Long"); + } + return (Long) columnValues[columnIndex]; + } + + @Override + public Byte getByteAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException { + if (!(columnValues[columnIndex] instanceof Byte)) { + throw new ColumnFormatException("Column is not Byte"); + } + return (Byte) columnValues[columnIndex]; + } + + @Override + public Float getFloatAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException { + if (!(columnValues[columnIndex] instanceof Float)) { + throw new ColumnFormatException("Column is not Float"); + } + return (Float) columnValues[columnIndex]; + } + + @Override + public Double getDoubleAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException { + if (!(columnValues[columnIndex] instanceof Double)) { + throw new ColumnFormatException("Column is not Double"); + } + return (Double) columnValues[columnIndex]; + } + + @Override + public Boolean getBooleanAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException { + if (!(columnValues[columnIndex] instanceof Boolean)) { + throw new ColumnFormatException("Column is not Boolean"); + } + return (Boolean) columnValues[columnIndex]; + } + + @Override + public String getStringAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException { + if (!(columnValues[columnIndex] instanceof String)) { + throw new ColumnFormatException("Column is not String"); + } + return (String) columnValues[columnIndex]; + } + + public int size() { + return columnValues.length; + } +} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/database/Main.java b/src/ru/fizteh/fivt/students/andrewzhernov/database/Main.java new file mode 100644 index 000000000..c935351b0 --- /dev/null +++ b/src/ru/fizteh/fivt/students/andrewzhernov/database/Main.java @@ -0,0 +1,164 @@ +package ru.fizteh.fivt.students.andrewzhernov.database; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class Main { + public static void main(String[] args) { + try { + TableProvider provider = new HashMapTableProvider(System.getProperty("fizteh.db.dir")); + Shell shell = new Shell(provider, new Command[] { + new Command("size", 1, new Handler() { + @Override + public Object execute(TableProvider provider, String[] args) throws Exception { + return provider.getCurrentTable().size(); + } + @Override + public void handle(TableProvider provider, Object object) throws Exception { + System.out.println(object); + } + }), + new Command("put", 3, new Handler() { + @Override + public Object execute(TableProvider provider, String[] args) throws Exception { + Table table = provider.getCurrentTable(); + return table.put(args[1], provider.deserialize(table, args[2])); + } + @Override + public void handle(TableProvider provider, Object object) throws Exception { + if (object == null) { + System.out.println("new"); + } else { + System.out.println("overwrite"); + System.out.println(provider.serialize( + provider.getCurrentTable(), (Storeable) object)); + } + } + }), + new Command("get", 2, new Handler() { + @Override + public Object execute(TableProvider provider, String[] args) throws Exception { + return provider.getCurrentTable().get(args[1]); + } + @Override + public void handle(TableProvider provider, Object object) throws Exception { + if (object == null) { + System.out.println("not found"); + } else { + System.out.println("found"); + System.out.println(provider.serialize( + provider.getCurrentTable(), (Storeable) object)); + } + } + }), + new Command("remove", 2, new Handler() { + @Override + public Object execute(TableProvider provider, String[] args) throws Exception { + return provider.getCurrentTable().remove(args[1]); + } + @Override + public void handle(TableProvider provider, Object object) throws Exception { + if (object == null) { + System.out.println("not found"); + } else { + System.out.println("removed"); + } + } + }), + new Command("list", 1, new Handler() { + @Override + public Object execute(TableProvider provider, String[] args) throws Exception { + return provider.getCurrentTable().list(); + } + @Override + public void handle(TableProvider provider, Object object) throws Exception { + @SuppressWarnings("unchecked") + List list = (List) object; + System.out.println(Utils.join(", ", list.toArray(new String[list.size()]))); + } + }), + new Command("commit", 1, new Handler() { + @Override + public Object execute(TableProvider provider, String[] args) throws Exception { + return provider.getCurrentTable().commit(); + } + @Override + public void handle(TableProvider provider, Object object) throws Exception { + System.out.println(object); + } + }), + new Command("rollback", 1, new Handler() { + @Override + public Object execute(TableProvider provider, String[] args) throws Exception { + return provider.getCurrentTable().rollback(); + } + @Override + public void handle(TableProvider provider, Object object) throws Exception { + System.out.println(object); + } + }), + new Command("create", 3, new Handler() { + @Override + public Object execute(TableProvider provider, String[] args) throws Exception { + return provider.createTable(args[1], Utils.parseSignature(args[2])); + } + @Override + public void handle(TableProvider provider, Object object) throws Exception { + System.out.println("created"); + } + }), + new Command("drop", 2, new Handler() { + @Override + public Object execute(TableProvider provider, String[] args) throws Exception { + provider.removeTable(args[1]); + return null; + } + @Override + public void handle(TableProvider provider, Object object) throws Exception { + System.out.println("dropped"); + } + }), + new Command("use", 2, new Handler() { + @Override + public Object execute(TableProvider provider, String[] args) throws Exception { + return provider.useTable(args[1]); + } + @Override + public void handle(TableProvider provider, Object object) throws Exception { + System.out.println("using " + (String) object); + } + }), + new Command("show tables", 2, new Handler() { + @Override + public Object execute(TableProvider provider, String[] args) throws Exception { + return provider.showTables(); + } + @Override + public void handle(TableProvider provider, Object object) throws Exception { + @SuppressWarnings("unchecked") + Map map = (Map) object; + for (String tablename : map.keySet()) { + System.out.println(tablename); + } + } + }), + new Command("exit", 1, new Handler() { + @Override + public Object execute(TableProvider provider, String[] args) throws Exception { + provider.exit(); + return null; + } + @Override + public void handle(TableProvider provider, Object object) throws Exception { + System.exit(0); + } + }) + }); + shell.run(args); + } catch (Exception e) { + System.err.println(e.getMessage()); + System.exit(1); + } + } +} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/database/PermissionsValidator.java b/src/ru/fizteh/fivt/students/andrewzhernov/database/PermissionsValidator.java new file mode 100644 index 000000000..6790a67c2 --- /dev/null +++ b/src/ru/fizteh/fivt/students/andrewzhernov/database/PermissionsValidator.java @@ -0,0 +1,81 @@ +package ru.fizteh.fivt.students.andrewzhernov.database; + +import static ru.fizteh.fivt.students.andrewzhernov.database.PermissionsValidator.Permissions.*; + +import java.util.EnumSet; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.nio.file.Files; +import java.io.IOException; + +public class PermissionsValidator { + public enum Permissions { + NOT_NULL, + EXISTS, + CREATE_FILE_IF_NOT_EXISTS, + CREATE_DIRECTORY_IF_NOT_EXISTS, + CAN_READ, + CAN_WRITE, + IS_FILE, + IS_DIRECTORY, + IS_FILE_EMPTY; + } + + public static boolean validateTableName(String tableName, EnumSet perms) + throws RuntimeException { + if (perms.contains(NOT_NULL) && tableName == null) { + throw new RuntimeException("Table name hasn't been specified"); + } + return true; + } + + public static boolean validateDbFolder(String dbFolder, EnumSet perms) + throws RuntimeException { + if (perms.contains(NOT_NULL) && dbFolder == null) { + throw new RuntimeException("Database name hasn't been specified"); + } + return true; + } + + public static boolean validateFileEmpty(String fileName) throws IOException { + BufferedReader br = new BufferedReader(new FileReader(fileName)); + return br.readLine() == null; + } + + public static boolean validate(String fileName, + EnumSet perms) throws RuntimeException, IOException { + if (perms.contains(NOT_NULL) && fileName == null) { + throw new RuntimeException("File name hasn't been specified"); + } + File file = new File(fileName); + if (perms.contains(EXISTS) && !file.exists()) { + return false; + } + if ((perms.contains(CREATE_FILE_IF_NOT_EXISTS) || perms.contains(CREATE_DIRECTORY_IF_NOT_EXISTS)) + && !file.exists()) { + File parentFile = file.getCanonicalFile().getParentFile(); + if (!parentFile.canWrite()) { + throw new RuntimeException(fileName + ": don't have permission to create the file"); + } + if (perms.contains(CREATE_FILE_IF_NOT_EXISTS)) { + Files.createFile(file.toPath()); + } else { + Files.createDirectory(file.toPath()); + } + } + if (perms.contains(CAN_READ) && !file.canRead()) { + throw new RuntimeException(file.getPath() + ": don't have permission to read the file"); + } + if (perms.contains(CAN_WRITE) && !file.canWrite()) { + throw new RuntimeException(file.getPath() + ": don't have permission to write the file"); + } + if (perms.contains(IS_FILE) && !file.isFile()) { + throw new RuntimeException(file.getPath() + ": isn't a normal file"); + } + if (perms.contains(IS_DIRECTORY) && !file.isDirectory()) { + throw new RuntimeException(file.getPath() + ": isn't a directory"); + } + return true; + } +} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/junit/Shell.java b/src/ru/fizteh/fivt/students/andrewzhernov/database/Shell.java similarity index 71% rename from src/ru/fizteh/fivt/students/andrewzhernov/junit/Shell.java rename to src/ru/fizteh/fivt/students/andrewzhernov/database/Shell.java index 5e58be33b..f7bae00c0 100644 --- a/src/ru/fizteh/fivt/students/andrewzhernov/junit/Shell.java +++ b/src/ru/fizteh/fivt/students/andrewzhernov/database/Shell.java @@ -1,19 +1,19 @@ -package ru.fizteh.fivt.students.andrewzhernov.junit; +package ru.fizteh.fivt.students.andrewzhernov.database; -import java.util.Scanner; -import java.util.Map; import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; public class Shell { private static final String PROMPT = "$ "; private static final String STATEMENT_DELIMITER = ";"; private static final String PARAM_DELIMITER = "\\s+"; - private TableProvider database; + private TableProvider manager; private Map commands; - public Shell(TableProvider database, Command[] commands) throws Exception { - this.database = database; + public Shell(TableProvider manager, Command[] commands) throws Exception { + this.manager = manager; this.commands = new HashMap<>(); for (Command command : commands) { this.commands.put(command.getName(), command); @@ -31,7 +31,7 @@ public void run(String[] args) throws Exception { } } - public void interactiveMode() throws Exception { + public void interactiveMode() { Scanner input = new Scanner(System.in); while (true) { System.out.print(PROMPT); @@ -48,7 +48,7 @@ public void interactiveMode() throws Exception { } public void batchMode(String[] args) throws Exception { - executeLine(String.join(";", args)); + executeLine(Utils.join(";", args)); } public void executeLine(String line) throws Exception { @@ -56,18 +56,18 @@ public void executeLine(String line) throws Exception { for (String statement : statements) { String[] params = statement.trim().split(PARAM_DELIMITER); - String cmdName = null; - if (params.length > 0) { - cmdName = params[0]; - if (cmdName.equals("show") && params.length > 1) { - cmdName += (" " + params[1]); - } + String cmdName = params[0]; + for (int i = 1; i < params.length && commands.get(cmdName) == null; ++i) { + cmdName += " " + params[i]; } + Command command = commands.get(cmdName); if (command == null) { - throw new Exception("Command not found: " + cmdName); + if (!cmdName.isEmpty()) { + throw new IllegalArgumentException(cmdName + ": command not found"); + } } else { - command.execute(database, params); + command.execute(manager, params); } } } diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/database/Storeable.java b/src/ru/fizteh/fivt/students/andrewzhernov/database/Storeable.java new file mode 100644 index 000000000..658e8b8fb --- /dev/null +++ b/src/ru/fizteh/fivt/students/andrewzhernov/database/Storeable.java @@ -0,0 +1,96 @@ +package ru.fizteh.fivt.students.andrewzhernov.database; + +/** + * Список фиксированной структуры, строка таблицы {@link Table}. + * + * Нумерация колонок с нуля. Позиция в списке соответствует колонке таблицы под тем же номером. + * + * С помощью {@link TableProvider} может быть сериализован или десериализован. + * + * Для получения объекта из нужной колонки воспользуйтесь соответствующим геттером. + * Для установки объекта а колонку воспользуйтесь {@link #setColumnAt(int, Object)} . + */ +public interface Storeable { + + /** + * Установить значение в колонку + * @param columnIndex - индекс колонки в таблице, начиная с нуля + * @param value - значение, которое нужно установить. + * Может быть null. + * Тип значения должен соответствовать декларированному типу колонки. + * @throws ColumnFormatException - Тип значения не соответствует типу колонки. + * @throws IndexOutOfBoundsException - Неверный индекс колонки. + */ + void setColumnAt(int columnIndex, Object value) throws ColumnFormatException, IndexOutOfBoundsException; + + /** + * Возвращает значение из данной колонки, не приводя его к конкретному типу. + * @param columnIndex - индекс колонки в таблице, начиная с нуля + * @return - значение в этой колонке, без приведения типа. Может быть null. + * @throws IndexOutOfBoundsException - Неверный индекс колонки. + */ + Object getColumnAt(int columnIndex) throws IndexOutOfBoundsException; + + /** + * Возвращает значение из данной колонки, приведя его к Integer. + * @param columnIndex - индекс колонки в таблице, начиная с нуля + * @return - значение в этой колонке, приведенное к Integer. Может быть null. + * @throws ColumnFormatException - Запрошенный тип не соответствует типу колонки. + * @throws IndexOutOfBoundsException - Неверный индекс колонки. + */ + Integer getIntAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException; + + /** + * Возвращает значение из данной колонки, приведя его к Long. + * @param columnIndex - индекс колонки в таблице, начиная с нуля + * @return - значение в этой колонке, приведенное к Long. Может быть null. + * @throws ColumnFormatException - Запрошенный тип не соответствует типу колонки. + * @throws IndexOutOfBoundsException - Неверный индекс колонки. + */ + Long getLongAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException; + + /** + * Возвращает значение из данной колонки, приведя его к Byte. + * @param columnIndex - индекс колонки в таблице, начиная с нуля + * @return - значение в этой колонке, приведенное к Byte. Может быть null. + * @throws ColumnFormatException - Запрошенный тип не соответствует типу колонки. + * @throws IndexOutOfBoundsException - Неверный индекс колонки. + */ + Byte getByteAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException; + + /** + * Возвращает значение из данной колонки, приведя его к Float. + * @param columnIndex - индекс колонки в таблице, начиная с нуля + * @return - значение в этой колонке, приведенное к Float. Может быть null. + * @throws ColumnFormatException - Запрошенный тип не соответствует типу колонки. + * @throws IndexOutOfBoundsException - Неверный индекс колонки. + */ + Float getFloatAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException; + + /** + * Возвращает значение из данной колонки, приведя его к Double. + * @param columnIndex - индекс колонки в таблице, начиная с нуля + * @return - значение в этой колонке, приведенное к Double. Может быть null. + * @throws ColumnFormatException - Запрошенный тип не соответствует типу колонки. + * @throws IndexOutOfBoundsException - Неверный индекс колонки. + */ + Double getDoubleAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException; + + /** + * Возвращает значение из данной колонки, приведя его к Boolean. + * @param columnIndex - индекс колонки в таблице, начиная с нуля + * @return - значение в этой колонке, приведенное к Boolean. Может быть null. + * @throws ColumnFormatException - Запрошенный тип не соответствует типу колонки. + * @throws IndexOutOfBoundsException - Неверный индекс колонки. + */ + Boolean getBooleanAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException; + + /** + * Возвращает значение из данной колонки, приведя его к String. + * @param columnIndex - индекс колонки в таблице, начиная с нуля + * @return - значение в этой колонке, приведенное к String. Может быть null. + * @throws ColumnFormatException - Запрошенный тип не соответствует типу колонки. + * @throws IndexOutOfBoundsException - Неверный индекс колонки. + */ + String getStringAt(int columnIndex) throws ColumnFormatException, IndexOutOfBoundsException; +} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/database/Table.java b/src/ru/fizteh/fivt/students/andrewzhernov/database/Table.java new file mode 100644 index 000000000..1a2665293 --- /dev/null +++ b/src/ru/fizteh/fivt/students/andrewzhernov/database/Table.java @@ -0,0 +1,100 @@ +package ru.fizteh.fivt.students.andrewzhernov.database; + +import java.io.IOException; +import java.util.List; + +/** + * Представляет интерфейс для работы с таблицей, содержащей ключи-значения. Ключи должны быть уникальными. + * + * Транзакционность: изменения фиксируются или откатываются с помощью методов {@link #commit()} или {@link #rollback()}, + * соответственно. Предполагается, что между вызовами этих методов никаких операций ввода-вывода не происходит. + * + * Данный интерфейс не является потокобезопасным. + */ +public interface Table extends Index { + + /** + * Устанавливает значение по указанному ключу. + * + * @param key Ключ для нового значения. Не может быть null. + * @param value Новое значение. Не может быть null. + * @return Значение, которое было записано по этому ключу ранее. Если ранее значения не было записано, + * возвращает null. + * + * @throws IllegalArgumentException Если значение параметров key или value является null. + * @throws ColumnFormatException - при попытке передать Storable с колонками другого типа. + */ + Storeable put(String key, Storeable value) throws ColumnFormatException; + + /** + * Удаляет значение по указанному ключу. + * + * @param key Ключ для поиска значения. Не может быть null. + * @return Предыдущее значение. Если не найдено, возвращает null. + * + * @throws IllegalArgumentException Если значение параметра key является null. + */ + Storeable remove(String key); + + /** + * Возвращает количество ключей в таблице. Возвращает размер текущей версии, с учётом незафиксированных изменений. + * + * @return Количество ключей в таблице. + */ + int size(); + + /** + * Выводит список ключей таблицы, с учётом незафиксированных изменений. + * + * @return Список ключей. + */ + List list(); + + /** + * Выполняет фиксацию изменений. + * + * @return Число записанных изменений. + * + * @throws java.io.IOException если произошла ошибка ввода/вывода. Целостность таблицы не гарантируется. + */ + int commit() throws Exception; + + /** + * Выполняет откат изменений с момента последней фиксации. + * + * @return Число откаченных изменений. + */ + int rollback(); + + /** + * Возвращает количество изменений, ожидающих фиксации. + * + * @return Количество изменений, ожидающих фиксации. + */ + int getNumberOfUncommittedChanges(); + + /** + * Возвращает количество колонок в таблице. + * + * @return Количество колонок в таблице. + */ + int getColumnsCount(); + + /** + * Возвращает тип значений в колонке. + * + * @param columnIndex Индекс колонки. Начинается с нуля. + * @return Класс, представляющий тип значения. + * + * @throws IndexOutOfBoundsException - неверный индекс колонки + */ + Class getColumnType(int columnIndex) throws IndexOutOfBoundsException; + + + String getTableFolder(); + + /** + * + */ + void loadTable() throws Exception; +} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/database/TableProvider.java b/src/ru/fizteh/fivt/students/andrewzhernov/database/TableProvider.java new file mode 100644 index 000000000..6ecb6cc59 --- /dev/null +++ b/src/ru/fizteh/fivt/students/andrewzhernov/database/TableProvider.java @@ -0,0 +1,118 @@ +package ru.fizteh.fivt.students.andrewzhernov.database; + +import java.io.IOException; +import java.text.ParseException; +import java.util.List; + +/** + * Управляющий класс для работы с {@link Table таблицами} + * + * Предполагает, что актуальная версия с устройства хранения, сохраняется при создании + * экземпляра объекта. Далее ввод-вывод выполняется только в момент создания и удаления + * таблиц. + * + * Данный интерфейс не является потокобезопасным. + */ +public interface TableProvider { + + /** + * Возвращает таблицу с указанным названием. + * + * Последовательные вызовы метода с одинаковыми аргументами должны возвращать один и тот же объект таблицы, + * если он не был удален с помощью {@link #removeTable(String)}. + * + * @param name Название таблицы. + * @return Объект, представляющий таблицу. Если таблицы с указанным именем не существует, возвращает null. + * + * @throws IllegalArgumentException Если название таблицы null или имеет недопустимое значение. + */ + Table getTable(String name); + + /** + * Создаёт таблицу с указанным названием. + * Создает новую таблицу. Совершает необходимые дисковые операции. + * + * @param name Название таблицы. + * @param columnTypes Типы колонок таблицы. Не может быть пустой. + * @return Объект, представляющий таблицу. Если таблица с указанным именем существует, возвращает null. + * + * @throws IllegalArgumentException Если название таблицы null или имеет недопустимое значение. Если список типов + * колонок null или содержит недопустимые значения. + * @throws java.io.IOException При ошибках ввода/вывода. + */ + Table createTable(String name, List> columnTypes) throws IOException; + + /** + * Удаляет существующую таблицу с указанным названием. + * + * Объект удаленной таблицы, если был кем-то взят с помощью {@link #getTable(String)}, + * с этого момента должен бросать {@link IllegalStateException}. + * + * @param name Название таблицы. + * + * @throws IllegalArgumentException Если название таблицы null или имеет недопустимое значение. + * @throws IllegalStateException Если таблицы с указанным названием не существует. + * @throws java.io.IOException - при ошибках ввода/вывода. + */ + void removeTable(String name) throws IOException; + + /** + * Преобразовывает строку в объект {@link Storeable}, соответствующий структуре таблицы. + * + * @param table Таблица, которой должен принадлежать {@link Storeable}. + * @param value Строка, из которой нужно прочитать {@link Storeable}. + * @return Прочитанный {@link Storeable}. + * + * @throws ParseException - при каких-либо несоответстиях в прочитанных данных. + */ + Storeable deserialize(Table table, String value) throws ParseException; + + /** + * Преобразовывает объект {@link Storeable} в строку. + * + * @param table Таблица, которой должен принадлежать {@link Storeable}. + * @param value {@link Storeable}, который нужно записать. + * @return Строка с записанным значением. + * + * @throws ColumnFormatException При несоответствии типа в {@link Storeable} и типа колонки в таблице. + */ + String serialize(Table table, Storeable value) throws ColumnFormatException; + + /** + * Создает новый пустой {@link Storeable} для указанной таблицы. + * + * @param table Таблица, которой должен принадлежать {@link Storeable}. + * @return Пустой {@link Storeable}, нацеленный на использование с этой таблицей. + */ + Storeable createFor(Table table); + + /** + * Создает новый {@link Storeable} для указанной таблицы, подставляя туда переданные значения. + * + * @param table Таблица, которой должен принадлежать {@link Storeable}. + * @param values Список значений, которыми нужно проинициализировать поля Storable. + * @return {@link Storeable}, проинициализированный переданными значениями. + * @throws ColumnFormatException При несоответствии типа переданного значения и колонки. + * @throws IndexOutOfBoundsException При несоответствии числа переданных значений и числа колонок. + */ + Storeable createFor(Table table, List values) throws ColumnFormatException, IndexOutOfBoundsException; + + /** + * Возвращает имена существующих таблиц, которые могут быть получены с помощью {@link #getTable(String)}. + * + * @return Имена существующих таблиц. + */ + List getTableNames(); + + Table getCurrentTable(); + + String getDbFolder(); + + String useTable(String string); + + void exit(); + + Object showTables(); + + void saveSignature(); +} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/database/Utils.java b/src/ru/fizteh/fivt/students/andrewzhernov/database/Utils.java new file mode 100644 index 000000000..311975727 --- /dev/null +++ b/src/ru/fizteh/fivt/students/andrewzhernov/database/Utils.java @@ -0,0 +1,108 @@ +package ru.fizteh.fivt.students.andrewzhernov.database; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.DirectoryStream; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.HashMap; + +public class Utils { + private static Map, String> ctsMap = new HashMap<>(); + private static Map> stcMap = new HashMap<>(); + + static { + ctsMap.put(Integer.class, "int"); + ctsMap.put(Long.class, "long"); + ctsMap.put(Float.class, "float"); + ctsMap.put(Double.class, "double"); + ctsMap.put(Byte.class, "byte"); + ctsMap.put(Boolean.class, "boolean"); + ctsMap.put(String.class, "String"); + + stcMap.put("int", Integer.class); + stcMap.put("long", Long.class); + stcMap.put("float", Float.class); + stcMap.put("double", Double.class); + stcMap.put("byte", Byte.class); + stcMap.put("boolean", Boolean.class); + stcMap.put("String", String.class); + } + + public static void removeDir(Path directory) throws IllegalStateException { + try { + if (Files.isDirectory(directory)) { + try (DirectoryStream stream = Files.newDirectoryStream(directory)) { + for (Path entry : stream) { + removeDir(entry); + } + } + } + if (!directory.toFile().delete()) { + throw new IllegalStateException("Can't remove " + directory.toString()); + } + } catch (IOException e) { + System.err.println(e.getMessage()); + System.exit(1); + } + } + + // for java 7 + public static String join(String join, String... strings) { + if (strings == null || strings.length == 0) { + return ""; + } else if (strings.length == 1) { + return strings[0]; + } else { + StringBuilder sb = new StringBuilder(); + sb.append(strings[0]); + for (int i = 1; i < strings.length; i++) { + sb.append(join).append(strings[i]); + } + return sb.toString(); + } + } + + public static List> parseSignature(String signature) throws Exception { + if (signature.length() < 2) { + throw new Exception("Wrong signature length"); + } + if (signature.charAt(0) != '(' || signature.charAt(signature.length() - 1) != ')') { + throw new Exception("Signature must start and end with paranthesis"); + } + + signature = signature.substring(1, signature.length() - 1); + + String[] types = signature.split(","); + + List> result = new LinkedList<>(); + for (String type : types) { + if (stcMap.containsKey(type)) { + result.add(stcMap.get(type)); + } else { + throw new Exception("Wrong type (" + type + ")"); + } + } + + return result; + } + + public static String makeSignature(List> types) { + String result = "("; + + List typeList = new LinkedList<>(); + int i = 0; + for (Class type : types) { + if (ctsMap.containsKey(type)) { + typeList.add(ctsMap.get(type)); + } + i++; + } + + result += String.join(",", typeList) + ")"; + return result; + } + +} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/database/tests/TableTest.java b/src/ru/fizteh/fivt/students/andrewzhernov/database/tests/TableTest.java new file mode 100644 index 000000000..add9fe320 --- /dev/null +++ b/src/ru/fizteh/fivt/students/andrewzhernov/database/tests/TableTest.java @@ -0,0 +1,126 @@ +package ru.fizteh.fivt.students.andrewzhernov.database.tests; + +import ru.fizteh.fivt.students.andrewzhernov.database.*; + +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.After; +import org.junit.Test; + +import java.util.List; +import java.util.LinkedList; + +public class TableTest { + TableManager manager; + Table table; + + @Before + public void setUp() throws Exception { + manager = new TableManager("data"); + table = manager.createTable("test_table"); + } + + @After + public void tearDown() throws Exception { + manager.removeTable("test_table"); + } + + @Test(expected = IllegalArgumentException.class) + public void testTableConstructorThrowsExceptionLoadedInvalidDirectory() { + new Table(manager, null); + } + + @Test(expected = IllegalArgumentException.class) + public void testTablePutInvalidKey() { + table.put(null, ""); + } + + @Test(expected = IllegalArgumentException.class) + public void testTablePutInvalidValue() { + table.put("", null); + } + + @Test(expected = IllegalArgumentException.class) + public void testTableGetInvalidKey() { + table.get(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testTableRemoveInvalidKey() { + table.remove(null); + } + + @Test + public void testTableSize() { + table.put("1", "xyz"); + assertEquals(1, table.size()); + table.put("1", "abc"); + assertEquals(1, table.size()); + table.put("2", "bca"); + assertEquals(2, table.size()); + table.remove("1"); + assertEquals(1, table.size()); + table.remove("2"); + assertEquals(0, table.size()); + } + + @Test + public void testTablePut() { + table.put("1", "xyz"); + assertNull(table.put("2", "abc")); + assertEquals("xyz", table.put("1", "zyx")); + assertEquals("abc", table.put("2", "cba")); + } + + @Test + public void testTableGet() { + table.put("1", "xyz"); + assertEquals("xyz", table.get("1")); + assertNull(table.get("2")); + table.put("2", "abc"); + assertEquals("abc", table.get("2")); + } + + @Test + public void testTableRemove() { + table.put("1", "xyz"); + table.put("2", "abc"); + assertEquals("abc", table.remove("2")); + assertEquals("xyz", table.remove("1")); + assertNull("1-error", table.remove("1")); + assertNull("2-error", table.remove("2")); + } + + @Test + public void testTableList() { + table.put("1", "xyz"); + List list = new LinkedList<>(); + list.add("1"); + assertEquals(list, table.list()); + table.put("2", "abc"); + list.add("2"); + assertEquals(list, table.list()); + } + + @Test + public void testTableCommit() { + table.put("1", "abc"); + assertEquals(1, table.commit()); + table.put("2", "efg"); + table.put("1", "bca"); + table.remove("1"); + assertEquals(2, table.commit()); + } + + @Test + public void testTableRollback() { + table.put("1", "xyz"); + table.commit(); + table.put("1", "abc"); + assertEquals(1, table.rollback()); + + table.put("2", "efg"); + table.remove("1"); + assertEquals(2, table.rollback()); + } +} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/filemap/DataBase.java b/src/ru/fizteh/fivt/students/andrewzhernov/filemap/DataBase.java deleted file mode 100644 index 090404832..000000000 --- a/src/ru/fizteh/fivt/students/andrewzhernov/filemap/DataBase.java +++ /dev/null @@ -1,100 +0,0 @@ -package ru.fizteh.fivt.students.andrewzhernov.filemap; - -import java.io.File; -import java.io.RandomAccessFile; -import java.util.Map; -import java.util.HashMap; - -public class DataBase { - private Map dataBase; - private String dbPath; - - public DataBase(String name) throws Exception { - if (name == null) { - throw new Exception("Usage: java -Ddb.file= ..."); - } - dataBase = new HashMap(); - File dbFile = openFile(name); - if (dbFile.isDirectory()) { - throw new Exception("Can't create file, " + name + " is a directory"); - } else { - dbPath = dbFile.getCanonicalPath(); - if (dbFile.exists()) { - loadFromDisk(); - } - } - } - - private static File openFile(String name) throws Exception { - if (name.charAt(0) == File.separatorChar) { - return new File(name); - } else { - return new File(System.getProperty("user.dir") + File.separator + name); - } - } - - private String readFromDataBase(RandomAccessFile file) throws Exception { - int wordSize = file.readInt(); - byte[] word = new byte[wordSize]; - file.read(word, 0, wordSize); - return new String(word); - } - - private void writeToDataBase(RandomAccessFile file, String word) throws Exception { - file.writeInt(word.getBytes("UTF-8").length); - file.write(word.getBytes("UTF-8")); - } - - public void loadFromDisk() throws Exception { - RandomAccessFile file = new RandomAccessFile(dbPath, "r"); - while (file.getFilePointer() < file.length()) { - String key = readFromDataBase(file); - String value = readFromDataBase(file); - dataBase.put(key, value); - } - file.close(); - } - - public void saveToDisk() throws Exception { - RandomAccessFile file = new RandomAccessFile(dbPath, "rw"); - for (String key : dataBase.keySet()) { - writeToDataBase(file, key); - writeToDataBase(file, dataBase.get(key)); - } - file.close(); - } - - public void put(String key, String value) { - if (dataBase.containsKey(key)) { - System.out.println("overwrite"); - System.out.println(dataBase.get(key)); - dataBase.remove(key); - dataBase.put(key, value); - } else { - System.out.println("new"); - dataBase.put(key, value); - } - } - - public void get(String key) { - if (dataBase.containsKey(key)) { - System.out.println("found"); - System.out.println(dataBase.get(key)); - } else { - System.out.println("not found"); - } - } - - public void remove(String key) { - if (dataBase.containsKey(key)) { - System.out.println("removed"); - dataBase.remove(key); - } else { - System.out.println("not found"); - } - } - - public void list() { - System.out.println(String.join(", ", dataBase.keySet())); - } -} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/filemap/FileMap.java b/src/ru/fizteh/fivt/students/andrewzhernov/filemap/FileMap.java deleted file mode 100644 index 1f24b8fe4..000000000 --- a/src/ru/fizteh/fivt/students/andrewzhernov/filemap/FileMap.java +++ /dev/null @@ -1,93 +0,0 @@ -package ru.fizteh.fivt.students.andrewzhernov.filemap; - -import java.util.Scanner; - -public class FileMap { - public static void main(String[] args) { - try { - DataBase dataBase = new DataBase(System.getProperty("db.file")); - if (args.length == 0) { - interactiveMode(dataBase); - } else { - batchMode(args, dataBase); - } - } catch (Exception e) { - System.err.println(e.getMessage()); - System.exit(1); - } - } - - public static void interactiveMode(DataBase dataBase) throws Exception { - Scanner input = new Scanner(System.in); - System.out.print("$ "); - while (input.hasNextLine()) { - try { - executeCommand(parseCommand(input.nextLine()), dataBase); - } catch (Exception e) { - System.err.println(e.getMessage()); - } - System.out.print("$ "); - } - dataBase.saveToDisk(); - input.close(); - } - - public static void batchMode(String[] args, DataBase dataBase) throws Exception { - String[] input = parseInput(args); - for (String cmd : input) { - executeCommand(parseCommand(cmd), dataBase); - } - dataBase.saveToDisk(); - } - - private static String[] parseInput(String[] args) throws Exception { - StringBuilder input = new StringBuilder(); - for (String cmd : args) { - input.append(cmd).append(' '); - } - return input.toString().split("\\s*;\\s*"); - } - - private static String[] parseCommand(String cmd) throws Exception { - return cmd.trim().split("\\s+"); - } - - public static void executeCommand(String[] cmd, DataBase dataBase) throws Exception { - if (cmd.length > 0 && cmd[0].length() > 0) { - if (cmd[0].equals("put")) { - if (cmd.length != 3) { - throw new Exception("Usage: put "); - } - dataBase.put(cmd[1], cmd[2]); - } else if (cmd[0].equals("get")) { - if (cmd.length != 2) { - throw new Exception("Usage: get "); - } - dataBase.get(cmd[1]); - } else if (cmd[0].equals("remove")) { - if (cmd.length != 2) { - throw new Exception("Usage: remove "); - } - dataBase.remove(cmd[1]); - } else if (cmd[0].equals("list")) { - if (cmd.length != 1) { - throw new Exception("Usage: list"); - } - dataBase.list(); - } else if (cmd[0].equals("exit")) { - if (cmd.length != 1) { - throw new Exception("Usage: exit"); - } - try { - dataBase.saveToDisk(); - System.exit(0); - } catch (Exception e) { - System.err.println(e.getMessage()); - System.exit(1); - } - } else { - throw new Exception(cmd[0] + ": no such command"); - } - } - } -} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/junit/AbstractHandler.java b/src/ru/fizteh/fivt/students/andrewzhernov/junit/AbstractHandler.java deleted file mode 100644 index d8dd9ec8a..000000000 --- a/src/ru/fizteh/fivt/students/andrewzhernov/junit/AbstractHandler.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.fizteh.fivt.students.andrewzhernov.junit; - -interface AbstractHandler { - Object exec(TableProvider database, String[] args) throws Exception; - void print(Object returnValue) throws Exception; -} - diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/junit/HandlerInterface.java b/src/ru/fizteh/fivt/students/andrewzhernov/junit/HandlerInterface.java deleted file mode 100644 index e82c9c9b7..000000000 --- a/src/ru/fizteh/fivt/students/andrewzhernov/junit/HandlerInterface.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.fizteh.fivt.students.andrewzhernov.junit; - -public interface HandlerInterface { - Object execute(TableProvider database, String[] args) throws Exception; - void handle(Object object) throws Exception; -} - diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/junit/Main.java b/src/ru/fizteh/fivt/students/andrewzhernov/junit/Main.java deleted file mode 100644 index 62ea4a85a..000000000 --- a/src/ru/fizteh/fivt/students/andrewzhernov/junit/Main.java +++ /dev/null @@ -1,162 +0,0 @@ -package ru.fizteh.fivt.students.andrewzhernov.junit; - -import java.util.List; -import java.util.Map; - -public class Main { - public static void main(String[] args) { - try { - TableProvider database = new TableProvider(System.getProperty("fizteh.db.dir")); - Shell shell = new Shell(database, new Command[] { - new Command("size", 1, new HandlerInterface() { - @Override - public Object execute(TableProvider database, String[] args) throws Exception { - return database.getCurrentTable().size(); - } - @Override - public void handle(Object object) throws Exception { - System.out.println((Integer) object); - } - }), - new Command("put", 3, new HandlerInterface() { - @Override - public Object execute(TableProvider database, String[] args) throws Exception { - return database.getCurrentTable().put(args[1], args[2]); - } - @Override - public void handle(Object object) throws Exception { - String value = (String) object; - if (value == null) { - System.out.println("new"); - } else { - System.out.println("overwrite"); - System.out.println(value); - } - } - }), - new Command("get", 2, new HandlerInterface() { - @Override - public Object execute(TableProvider database, String[] args) throws Exception { - return database.getCurrentTable().get(args[1]); - } - @Override - public void handle(Object object) throws Exception { - String value = (String) object; - if (value == null) { - System.out.println("not found"); - } else { - System.out.println("found"); - System.out.println(value); - } - } - }), - new Command("remove", 2, new HandlerInterface() { - @Override - public Object execute(TableProvider database, String[] args) throws Exception { - return database.getCurrentTable().remove(args[1]); - } - @Override - public void handle(Object object) throws Exception { - if ((String) object == null) { - System.out.println("not found"); - } else { - System.out.println("removed"); - } - } - }), - new Command("list", 1, new HandlerInterface() { - @Override - public Object execute(TableProvider database, String[] args) throws Exception { - return database.getCurrentTable().list(); - } - @Override - public void handle(Object object) throws Exception { - @SuppressWarnings("unchecked") - List list = (List) object; - System.out.println(String.join(", ", list)); - } - }), - new Command("commit", 1, new HandlerInterface() { - @Override - public Object execute(TableProvider database, String[] args) throws Exception { - return database.getCurrentTable().commit(); - } - @Override - public void handle(Object object) throws Exception { - System.out.println((Integer) object); - } - }), - new Command("rollback", 1, new HandlerInterface() { - @Override - public Object execute(TableProvider database, String[] args) throws Exception { - return database.getCurrentTable().rollback(); - } - @Override - public void handle(Object object) throws Exception { - System.out.println((Integer) object); - } - }), - new Command("create", 2, new HandlerInterface() { - @Override - public Object execute(TableProvider database, String[] args) throws Exception { - return database.createTable(args[1]); - } - @Override - public void handle(Object object) throws Exception { - System.out.println("created"); - } - }), - new Command("drop", 2, new HandlerInterface() { - @Override - public Object execute(TableProvider database, String[] args) throws Exception { - database.removeTable(args[1]); - return null; - } - @Override - public void handle(Object object) throws Exception { - System.out.println("dropped"); - } - }), - new Command("use", 2, new HandlerInterface() { - @Override - public Object execute(TableProvider database, String[] args) throws Exception { - return database.useTable(args[1]); - } - @Override - public void handle(Object object) throws Exception { - System.out.println("using tablename"); - } - }), - new Command("show tables", 2, new HandlerInterface() { - @Override - public Object execute(TableProvider database, String[] args) throws Exception { - return database.showTables(); - } - @Override - public void handle(Object object) throws Exception { - @SuppressWarnings("unchecked") - Map tables = (Map) object; - for (String tablename : tables.keySet()) { - System.out.printf("%s %d\n", tablename, tables.get(tablename)); - } - } - }), - new Command("exit", 1, new HandlerInterface() { - @Override - public Object execute(TableProvider database, String[] args) throws Exception { - database.exit(); - return null; - } - @Override - public void handle(Object object) throws Exception { - System.exit(0); - } - }) - }); - shell.run(args); - } catch (Exception e) { - System.err.println(e.getMessage()); - System.exit(1); - } - } -} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/junit/Table.java b/src/ru/fizteh/fivt/students/andrewzhernov/junit/Table.java deleted file mode 100644 index 466006b7e..000000000 --- a/src/ru/fizteh/fivt/students/andrewzhernov/junit/Table.java +++ /dev/null @@ -1,255 +0,0 @@ -package ru.fizteh.fivt.students.andrewzhernov.junit; - -import java.io.*; -import java.nio.file.Files; -import java.nio.file.Path; -import java.io.RandomAccessFile; -import java.util.List; -import java.util.LinkedList; -import java.util.Map; -import java.util.HashMap; - -public class Table implements TableInterface { - private static final int DIRECTORIES_COUNT = 16; - private static final int FILES_COUNT = 16; - - private Path path; - private int size; - private Map disk; - private Map diff; - - public Table(Path tablePath) throws IllegalArgumentException { - if (tablePath == null) { - throw new IllegalArgumentException("The table directory wasn't specified"); - } - disk = new HashMap<>(); - diff = new HashMap<>(); - path = tablePath; - try { - if (Files.notExists(path)) { - if (!path.getParent().toFile().canWrite()) { - throw new Exception(path.toString() + ": don't have permission to create the directory"); - } - Files.createDirectory(path); - } else if (!path.toFile().canRead()) { - throw new Exception(path.toString() + ": don't have permission to read the directory"); - } else if (!Files.isDirectory(path)) { - throw new Exception(path.toString() + ": isn't a directory"); - } else { - loadTable(); - } - } catch (Exception e) { - System.err.println(e.getMessage()); - System.exit(1); - } - } - - public String getName() { - return path.toFile().getName(); - } - - public int unsavedSize() { - return diff.size(); - } - - public int size() { - return size; - } - - public String put(String key, String value) throws IllegalArgumentException { - if (key == null || value == null) { - throw new IllegalArgumentException("Invalid key/value"); - } - String diffValue = diff.put(key, value); - String result = diffValue != null ? diffValue : disk.get(key); - if (result == null) { - ++size; - } - return result; - } - - public String get(String key) throws IllegalArgumentException { - if (key == null) { - throw new IllegalArgumentException("Invalid key"); - } - String diffValue = diff.get(key); - return diffValue != null ? diffValue : disk.get(key); - } - - public String remove(String key) throws IllegalArgumentException { - if (key == null) { - throw new IllegalArgumentException("Invalid key"); - } - String result = null; - String diskValue = disk.get(key); - if (diskValue != null) { - String diffValue = diff.put(key, null); - result = diffValue != null ? diffValue : diskValue; - } else { - result = diff.remove(key); - } - if (result != null) { - --size; - } - return result; - } - - public List list() { - List list = new LinkedList(); - for (String key : disk.keySet()) { - if (!diff.containsKey(key)) { - list.add(key); - } - } - for (String key : diff.keySet()) { - if (diff.get(key) != null) { - list.add(key); - } - } - return list; - } - - public int commit() { - int amount = diff.size(); - for (String key : diff.keySet()) { - String value = diff.get(key); - if (value != null) { - disk.put(key, value); - } else { - disk.remove(key); - } - } - diff.clear(); - try { - saveTable(); - } catch (Exception e) { - System.err.println(e.getMessage()); - System.exit(1); - } - return amount; - } - - public int rollback() { - int amount = diff.size(); - diff.clear(); - size = disk.size(); - return amount; - } - - private static String readItem(RandomAccessFile file) throws Exception { - int wordSize = file.readInt(); - byte[] word = new byte[wordSize]; - file.read(word, 0, wordSize); - return new String(word, "UTF-8"); - } - - private void loadFile(String filename) throws Exception { - RandomAccessFile file = new RandomAccessFile(filename, "r"); - while (file.getFilePointer() < file.length()) { - try { - String key = readItem(file); - String value = readItem(file); - disk.put(key, value); - } catch (Exception | OutOfMemoryError e) { - throw new Exception(filename + ": invalid file format"); - } - } - file.close(); - } - - private void loadDirectory(Path dir) throws Exception { - for (int i = 0; i < FILES_COUNT; ++i) { - Path file = dir.resolve(Integer.toString(i) + ".dat"); - if (Files.exists(file)) { - if (!file.toFile().canRead()) { - throw new Exception(file.toString() + ": don't have permission to read the file"); - } else if (!file.toFile().isFile()) { - throw new Exception(file.toString() + ": isn't a normal file"); - } else { - loadFile(file.toString()); - } - } - } - } - - private void loadTable() throws Exception { - disk.clear(); - for (int i = 0; i < DIRECTORIES_COUNT; ++i) { - Path dir = path.resolve(Integer.toString(i) + ".dir"); - if (Files.exists(dir)) { - if (!dir.toFile().canRead()) { - throw new Exception(dir.toString() + ": don't have permission to read the directory"); - } else if (!Files.isDirectory(dir)) { - throw new Exception(dir.toString() + ": isn't a directory"); - } else { - loadDirectory(dir); - } - } - } - size = disk.size(); - } - - private static void writeItem(RandomAccessFile file, String word) throws Exception { - byte[] byteWord = word.getBytes("UTF-8"); - file.writeInt(byteWord.length); - file.write(byteWord); - } - - private boolean saveFile(String filename, int dirIndex, int fileIndex) throws Exception { - boolean hasWritten = false; - RandomAccessFile file = new RandomAccessFile(filename, "rw"); - for (String key : disk.keySet()) { - int hashcode = key.hashCode(); - int dirNumber = hashcode % DIRECTORIES_COUNT; - int fileNumber = hashcode / DIRECTORIES_COUNT % FILES_COUNT; - if (dirIndex == dirNumber && fileIndex == fileNumber) { - writeItem(file, key); - writeItem(file, disk.get(key)); - hasWritten = true; - } - } - file.close(); - return hasWritten; - } - - private void saveDirectory(Path dir, int dirIndex) throws Exception { - int usedFiles = FILES_COUNT; - for (int i = 0; i < FILES_COUNT; ++i) { - Path file = dir.resolve(Integer.toString(i) + ".dat"); - if (Files.notExists(file)) { - if (!file.getParent().toFile().canWrite()) { - throw new Exception(file.toString() + ": don't have permission to create the file"); - } - Files.createFile(file); - } else if (!file.toFile().canWrite()) { - throw new Exception(file.toString() + ": don't have permission to write the file"); - } else if (!file.toFile().isFile()) { - throw new Exception(file.toString() + ": isn't a normal file"); - } - if (!saveFile(file.toString(), dirIndex, i)) { - Files.deleteIfExists(file); - --usedFiles; - } - } - if (usedFiles == 0) { - Files.deleteIfExists(dir); - } - } - - private void saveTable() throws Exception { - for (int i = 0; i < DIRECTORIES_COUNT; ++i) { - Path dir = path.resolve(Integer.toString(i) + ".dir"); - if (Files.notExists(dir)) { - if (!dir.getParent().toFile().canWrite()) { - throw new Exception(dir.toString() + ": don't have permission to create the directory"); - } - Files.createDirectory(dir); - } else if (!dir.toFile().canWrite()) { - throw new Exception(dir.toString() + ": don't have permission to write the directory"); - } else if (!Files.isDirectory(dir)) { - throw new Exception(dir.toString() + ": isn't a directory"); - } - saveDirectory(dir, i); - } - } -} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/junit/TableInterface.java b/src/ru/fizteh/fivt/students/andrewzhernov/junit/TableInterface.java deleted file mode 100644 index 447d98b61..000000000 --- a/src/ru/fizteh/fivt/students/andrewzhernov/junit/TableInterface.java +++ /dev/null @@ -1,71 +0,0 @@ -package ru.fizteh.fivt.students.andrewzhernov.junit; - -import java.util.List; - -public interface TableInterface { - - /** - * Возвращает название базы данных. - */ - String getName(); - - /** - * Возвращает количество ключей в таблице. - * - * @return Количество ключей в таблице. - */ - int size(); - - /** - * Устанавливает значение по указанному ключу. - * - * @param key Ключ. - * @param value Значение. - * @return Значение, которое было записано по этому ключу ранее. Если ранее значения не было записано, - * возвращает null. - * - * @throws IllegalArgumentException Если значение параметров key или value является null. - */ - String put(String key, String value); - - /** - * Получает значение по указанному ключу. - * - * @param key Ключ. - * @return Значение. Если не найдено, возвращает null. - * - * @throws IllegalArgumentException Если значение параметра key является null. - */ - String get(String key); - - /** - * Удаляет значение по указанному ключу. - * - * @param key Ключ. - * @return Значение. Если не найдено, возвращает null. - * - * @throws IllegalArgumentException Если значение параметра key является null. - */ - String remove(String key); - - /** - * Выводит список ключей таблицы - * - * @return Список ключей. - */ - List list(); - - /** - * Выполняет фиксацию изменений. - * - * @return Количество сохранённых ключей. - */ - int commit(); - - /** - * Выполняет откат изменений с момента последней фиксации. - * - * @return Количество отменённых ключей. - */ - int rollback(); -} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/junit/TableProvider.java b/src/ru/fizteh/fivt/students/andrewzhernov/junit/TableProvider.java deleted file mode 100644 index 604152863..000000000 --- a/src/ru/fizteh/fivt/students/andrewzhernov/junit/TableProvider.java +++ /dev/null @@ -1,116 +0,0 @@ -package ru.fizteh.fivt.students.andrewzhernov.junit; - -import java.io.*; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Map; -import java.util.HashMap; - -public class TableProvider implements TableProviderInterface { - private Path path; - private Map database; - private String currentTable; - - private void load() throws Exception { - for (String tablename : path.toFile().list()) { - if (tablename.indexOf('/') != -1) { - throw new Exception(tablename + ": incorrect tablename"); - } - database.put(tablename, new Table(path.resolve(tablename))); - } - } - - private void checkUnsavedChanges() throws IllegalStateException { - int count = getCurrentTable().unsavedSize(); - if (count > 0) { - throw new IllegalStateException(Integer.toString(count) + " unsaved changes"); - } - } - - public TableProvider(String dir) throws IllegalArgumentException { - if (dir == null) { - throw new IllegalArgumentException("The database directory wasn't specified"); - } - currentTable = null; - database = new HashMap(); - path = Paths.get(dir); - try { - if (Files.notExists(path)) { - if (!path.toFile().getCanonicalFile().getParentFile().canWrite()) { - throw new Exception(path.toString() + ": don't have permission to create the directory"); - } - Files.createDirectory(path); - } else if (!path.toFile().canRead()) { - throw new Exception(path.toString() + ": don't have permission to read the directory"); - } else if (!Files.isDirectory(path)) { - throw new IllegalArgumentException(dir + ": isn't a directory"); - } else { - load(); - } - } catch (Exception e) { - System.err.println(e.getMessage()); - System.exit(1); - } - } - - public Table getTable(String name) throws IllegalArgumentException { - if (name == null) { - throw new IllegalArgumentException("no table"); - } - return database.get(name); - } - - public Table getCurrentTable() throws IllegalArgumentException { - return getTable(currentTable); - } - - public Table createTable(String name) throws IllegalArgumentException { - if (name == null || name.indexOf('/') != -1) { - throw new IllegalArgumentException(name + ": incorrect tablename"); - } else if (database.containsKey(name)) { - throw new IllegalArgumentException("tablename exists"); - } - Table newTable = new Table(path.resolve(name)); - database.put(name, newTable); - return newTable; - } - - public void removeTable(String name) throws IllegalArgumentException, IllegalStateException { - if (name == null || name.indexOf('/') != -1) { - throw new IllegalArgumentException(name + ": incorrect tablename"); - } else if (!database.containsKey(name)) { - throw new IllegalStateException("tablename not exist"); - } else if (name.equals(currentTable)) { - currentTable = null; - } - Utils.removeDir(path.resolve(name)); - database.remove(name); - } - - public String useTable(String name) throws IllegalArgumentException, IllegalStateException { - if (name == null || name.indexOf('/') != -1) { - throw new IllegalArgumentException(name + ": incorrect tablename"); - } else if (!database.containsKey(name)) { - throw new IllegalArgumentException("tablename not exist"); - } else if (currentTable != null && !name.equals(currentTable)) { - checkUnsavedChanges(); - } - currentTable = name; - return currentTable; - } - - public Map showTables() { - Map tables = new HashMap<>(); - for (String tablename : database.keySet()) { - tables.put(tablename, database.get(tablename).size()); - } - return tables; - } - - public void exit() throws IllegalStateException { - if (currentTable != null) { - checkUnsavedChanges(); - } - } -} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/junit/TableProviderInterface.java b/src/ru/fizteh/fivt/students/andrewzhernov/junit/TableProviderInterface.java deleted file mode 100644 index adf089a3c..000000000 --- a/src/ru/fizteh/fivt/students/andrewzhernov/junit/TableProviderInterface.java +++ /dev/null @@ -1,57 +0,0 @@ -package ru.fizteh.fivt.students.andrewzhernov.junit; - -import java.util.Map; - -public interface TableProviderInterface { - - /** - * Возвращает таблицу с указанным названием. - * - * @param name Название таблицы. - * @return Объект, представляющий таблицу. Если таблицы с указанным именем не существует, возвращает null. - * @throws IllegalArgumentException Если название таблицы null или имеет недопустимое значение. - */ - Table getTable(String name); - - /** - * Создаёт таблицу с указанным названием. - * - * @param name Название таблицы. - * @return Объект, представляющий таблицу. Если таблица уже существует, возвращает null. - * @throws IllegalArgumentException Если название таблицы null или имеет недопустимое значение. - */ - Table createTable(String name); - - /** - * Удаляет таблицу с указанным названием. - * - * @param name Название таблицы. - * @throws IllegalArgumentException Если название таблицы null или имеет недопустимое значение. - * @throws IllegalStateException Если таблицы с указанным названием не существует. - */ - void removeTable(String name); - - /** - * Устанавливает таблицу с указанным названием в качестве текущей. - * - * @param name Название таблицы. - * @return Имя текущей таблицы. Если таблица не выбрана, возвращает null. - * @throws IllegalArgumentException Если название таблицы null или имеет недопустимое значение. - * @throws IllegalStateException Если предыдущая таблица имеет несохранённые изменения. - */ - String useTable(String name); - - /** - * Список таблиц с их размером. - * - * @return Список пар ключ-значение: имя таблицы, количество ключей. - */ - Map showTables(); - - /** - * Выход. - * - * @throws IllegalStateException Если текущая таблица имеет несохранённые изменения. - */ - void exit(); -} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/junit/Utils.java b/src/ru/fizteh/fivt/students/andrewzhernov/junit/Utils.java deleted file mode 100644 index b909daeed..000000000 --- a/src/ru/fizteh/fivt/students/andrewzhernov/junit/Utils.java +++ /dev/null @@ -1,26 +0,0 @@ -package ru.fizteh.fivt.students.andrewzhernov.junit; - -import java.io.*; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.DirectoryStream; - -public class Utils { - public static void removeDir(Path directory) throws IllegalStateException { - try { - if (Files.isDirectory(directory)) { - try (DirectoryStream stream = Files.newDirectoryStream(directory)) { - for (Path entry : stream) { - removeDir(entry); - } - } - } - if (!directory.toFile().delete()) { - throw new IllegalStateException("Can't remove " + directory.toString()); - } - } catch (IOException e) { - System.err.println(e.getMessage()); - System.exit(1); - } - } -} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/multifilemap/DataBase.java b/src/ru/fizteh/fivt/students/andrewzhernov/multifilemap/DataBase.java deleted file mode 100644 index 271a0ac00..000000000 --- a/src/ru/fizteh/fivt/students/andrewzhernov/multifilemap/DataBase.java +++ /dev/null @@ -1,199 +0,0 @@ -package ru.fizteh.fivt.students.andrewzhernov.multifilemap; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.io.RandomAccessFile; -import java.util.Map; -import java.util.HashMap; - -public class DataBase { - private static final int COUNT = 16; - - private Map recordsCount; - private Map table; - private String name; - private Path dir; - - public DataBase(String directory) throws Exception { - if (directory == null) { - throw new Exception("Usage: java -Dfizteh.db.dir= ..."); - } - recordsCount = new HashMap(); - table = new HashMap(); - dir = Paths.get(directory); - if (!Files.exists(dir)) { - Files.createDirectory(dir); - } else { - for (String tablename : dir.toFile().list()) { - reloadTable(tablename); - recordsCount.put(tablename, table.size()); - } - table.clear(); - } - name = null; - } - - private static String readString(RandomAccessFile file) throws Exception { - int wordSize = file.readInt(); - byte[] word = new byte[wordSize]; - file.read(word, 0, wordSize); - return new String(word); - } - - private static void writeString(RandomAccessFile file, String word) throws Exception { - file.writeInt(word.getBytes("UTF-8").length); - file.write(word.getBytes("UTF-8")); - } - - public void reloadTable(String tablename) throws Exception { - table.clear(); - name = tablename; - Path tablePath = dir.resolve(tablename); - for (int i = 0; i < COUNT; ++i) { - Path tableDir = tablePath.resolve(Integer.toString(i) + ".dir"); - if (Files.isDirectory(tableDir)) { - for (int j = 0; j < COUNT; ++j) { - Path tableFile = tableDir.resolve(Integer.toString(j) + ".dat"); - if (Files.exists(tableFile)) { - RandomAccessFile file = new RandomAccessFile(tableFile.toString(), "r"); - while (file.getFilePointer() < file.length()) { - String key = readString(file); - String value = readString(file); - table.put(key, value); - } - file.close(); - } - } - } - } - } - - public void saveTable() throws Exception { - if (name == null) { - return; - } - Path tablePath = dir.resolve(name); - for (int i = 0; i < COUNT; ++i) { - Path tableDir = tablePath.resolve(Integer.toString(i) + ".dir"); - if (!Files.isDirectory(tableDir)) { - Files.createDirectory(tableDir); - } - for (int j = 0; j < COUNT; ++j) { - Path tableFile = tableDir.resolve(Integer.toString(j) + ".dat"); - if (!Files.exists(tableFile)) { - Files.createFile(tableFile); - } - RandomAccessFile file = new RandomAccessFile(tableFile.toString(), "rw"); - boolean written = false; - for (String key : table.keySet()) { - int first = key.hashCode() % COUNT; - int second = key.hashCode() / COUNT % COUNT; - if (i == first && j == second) { - writeString(file, key); - writeString(file, table.get(key)); - written = true; - } - } - file.close(); - if (!written) { - Files.deleteIfExists(tableFile); - } - } - if (tableDir.toFile().list().length == 0) { - Files.deleteIfExists(tableDir); - } - } - } - - public void create(String tablename) throws Exception { - Path tablePath = dir.resolve(tablename); - if (Files.isDirectory(tablePath)) { - System.out.println("tablename exists"); - } else { - Files.createDirectory(tablePath); - recordsCount.put(tablename, 0); - System.out.println("created"); - } - } - - public void drop(String tablename) throws Exception { - Path tablePath = dir.resolve(tablename); - if (Files.isDirectory(tablePath)) { - Utils.remove(tablePath); - recordsCount.remove(tablename); - if (name != null && name.equals(tablename)) { - name = null; - } - System.out.println("dropped"); - } else { - System.out.println("tablename not exists"); - } - } - - public void use(String tablename) throws Exception { - Path tablePath = dir.resolve(tablename); - if (Files.isDirectory(tablePath)) { - if (name != null) { - saveTable(); - } - reloadTable(tablename); - System.out.println("using tablename"); - } else { - System.out.println("tablename not exists"); - } - } - - public void showTables() throws Exception { - for (String tablename : recordsCount.keySet()) { - System.out.format("%s %d\n", tablename, recordsCount.get(tablename)); - } - } - - public void put(String key, String value) throws Exception { - if (name == null) { - throw new Exception("The table is not selected"); - } - if (table.containsKey(key)) { - System.out.println("overwrite"); - System.out.println(table.get(key)); - table.put(key, value); - } else { - table.put(key, value); - recordsCount.put(name, recordsCount.get(name) + 1); - System.out.println("new"); - } - } - - public void get(String key) throws Exception { - if (name == null) { - throw new Exception("The table is not selected"); - } - if (table.containsKey(key)) { - System.out.println("found"); - System.out.println(table.get(key)); - } else { - System.out.println("not found"); - } - } - - public void remove(String key) throws Exception { - if (name == null) { - throw new Exception("The table is not selected"); - } - if (table.containsKey(key)) { - table.remove(key); - recordsCount.put(name, recordsCount.get(name) - 1); - System.out.println("removed"); - } else { - System.out.println("not found"); - } - } - - public void list() throws Exception { - if (name == null) { - throw new Exception("The table is not selected"); - } - System.out.println(String.join(", ", table.keySet())); - } -} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/multifilemap/MultiFileMap.java b/src/ru/fizteh/fivt/students/andrewzhernov/multifilemap/MultiFileMap.java deleted file mode 100644 index bff38e279..000000000 --- a/src/ru/fizteh/fivt/students/andrewzhernov/multifilemap/MultiFileMap.java +++ /dev/null @@ -1,109 +0,0 @@ -package ru.fizteh.fivt.students.andrewzhernov.multifilemap; - -import java.util.Scanner; - -public class MultiFileMap { - public static void main(String[] args) { - try { - DataBase dataBase = new DataBase(System.getProperty("fizteh.db.dir")); - if (args.length == 0) { - interactiveMode(dataBase); - } else { - batchMode(args, dataBase); - } - } catch (Exception e) { - System.err.println(e.getMessage()); - System.exit(1); - } - } - - public static void interactiveMode(DataBase dataBase) throws Exception { - Scanner input = new Scanner(System.in); - System.out.print("$ "); - while (input.hasNextLine()) { - try { - executeCommand(parseCommand(input.nextLine()), dataBase); - } catch (Exception e) { - System.err.println(e.getMessage()); - } - System.out.print("$ "); - } - dataBase.saveTable(); - input.close(); - } - - public static void batchMode(String[] args, DataBase dataBase) throws Exception { - String[] input = parseInput(args); - for (String cmd : input) { - executeCommand(parseCommand(cmd), dataBase); - } - dataBase.saveTable(); - } - - private static String[] parseInput(String[] args) throws Exception { - StringBuilder input = new StringBuilder(); - for (String cmd : args) { - input.append(cmd).append(';'); - } - return input.toString().split("\\s*;\\s*"); - } - - private static String[] parseCommand(String cmd) throws Exception { - return cmd.trim().split("\\s+"); - } - - public static void executeCommand(String[] cmd, DataBase dataBase) throws Exception { - if (cmd.length > 0 && cmd[0].length() > 0) { - if (cmd[0].equals("create")) { - if (cmd.length != 2) { - throw new Exception("Usage: create "); - } - dataBase.create(cmd[1]); - } else if (cmd[0].equals("drop")) { - if (cmd.length != 2) { - throw new Exception("Usage: drop "); - } - dataBase.drop(cmd[1]); - } else if (cmd[0].equals("use")) { - if (cmd.length != 2) { - throw new Exception("Usage: use "); - } - dataBase.use(cmd[1]); - } else if (cmd[0].equals("show")) { - if (cmd.length != 2) { - throw new Exception("Usage: show tables"); - } else if (cmd[1].equals("tables")) { - dataBase.showTables(); - } - } else if (cmd[0].equals("put")) { - if (cmd.length != 3) { - throw new Exception("Usage: put "); - } - dataBase.put(cmd[1], cmd[2]); - } else if (cmd[0].equals("get")) { - if (cmd.length != 2) { - throw new Exception("Usage: get "); - } - dataBase.get(cmd[1]); - } else if (cmd[0].equals("remove")) { - if (cmd.length != 2) { - throw new Exception("Usage: remove "); - } - dataBase.remove(cmd[1]); - } else if (cmd[0].equals("list")) { - if (cmd.length != 1) { - throw new Exception("Usage: list"); - } - dataBase.list(); - } else if (cmd[0].equals("exit")) { - if (cmd.length != 1) { - throw new Exception("Usage: exit"); - } - dataBase.saveTable(); - System.exit(0); - } else { - throw new Exception(cmd[0] + ": no such command"); - } - } - } -} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/multifilemap/Utils.java b/src/ru/fizteh/fivt/students/andrewzhernov/multifilemap/Utils.java deleted file mode 100644 index 0c6d3ec9d..000000000 --- a/src/ru/fizteh/fivt/students/andrewzhernov/multifilemap/Utils.java +++ /dev/null @@ -1,20 +0,0 @@ -package ru.fizteh.fivt.students.andrewzhernov.multifilemap; - -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; - -public class Utils { - static void remove(Path directory) throws Exception { - if (Files.isDirectory(directory)) { - try (DirectoryStream stream = Files.newDirectoryStream(directory)) { - for (Path entry : stream) { - remove(entry); - } - } - } - if (!directory.toFile().delete()) { - throw new Exception("Cannot delete " + directory.toString()); - } - } -} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/shell/Cat.java b/src/ru/fizteh/fivt/students/andrewzhernov/shell/Cat.java deleted file mode 100644 index 1aaac475d..000000000 --- a/src/ru/fizteh/fivt/students/andrewzhernov/shell/Cat.java +++ /dev/null @@ -1,39 +0,0 @@ -package ru.fizteh.fivt.students.andrewzhernov.shell; - -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; -import java.io.OutputStream; - -public class Cat { - public static void execute(String[] args) throws Exception { - if (args.length != 2) { - throw new Exception("Usage: cat "); - } else { - File file = ChangeDir.openFile(args[1]); - if (file.isFile()) { - InputStream input = null; - try { - input = new FileInputStream(file); - printFile(input, System.out); - } catch (Exception e) { - throw new Exception("cat: can't read file"); - } finally { - input.close(); - } - } else if (file.isDirectory()) { - throw new Exception("cat: " + args[1] + " is a directory"); - } else { - throw new Exception("cat: " + args[1] + ": no such file or directory"); - } - } - } - - public static void printFile(InputStream input, OutputStream output) throws Exception { - int index; - byte[] buffer = new byte[4096]; - while ((index = input.read(buffer)) != -1) { - output.write(buffer, 0, index); - } - } -} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/shell/ChangeDir.java b/src/ru/fizteh/fivt/students/andrewzhernov/shell/ChangeDir.java deleted file mode 100644 index d98dcd88d..000000000 --- a/src/ru/fizteh/fivt/students/andrewzhernov/shell/ChangeDir.java +++ /dev/null @@ -1,28 +0,0 @@ -package ru.fizteh.fivt.students.andrewzhernov.shell; - -import java.io.File; - -public class ChangeDir { - public static File openFile(String file) { - if (file.charAt(0) == File.separatorChar) { - return new File(file); - } else { - return new File(System.getProperty("user.dir") + File.separator + file); - } - } - - public static void execute(String[] args) throws Exception { - if (args.length != 2) { - throw new Exception("Usage: cd "); - } else { - File file = openFile(args[1]); - if (file.isDirectory()) { - System.setProperty("user.dir", file.getCanonicalPath()); - } else if (file.exists()) { - throw new Exception("cd: " + args[1] + ": isn't a directory"); - } else { - throw new Exception("cd: " + args[1] + ": no such file or directory"); - } - } - } -} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/shell/Copy.java b/src/ru/fizteh/fivt/students/andrewzhernov/shell/Copy.java deleted file mode 100644 index 4e9ddf2c4..000000000 --- a/src/ru/fizteh/fivt/students/andrewzhernov/shell/Copy.java +++ /dev/null @@ -1,73 +0,0 @@ -package ru.fizteh.fivt.students.andrewzhernov.shell; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.OutputStream; - -public class Copy { - public static void execute(String[] args) throws Exception { - File source = null; - File destination = null; - if (args.length == 3) { - source = ChangeDir.openFile(args[1]); - destination = ChangeDir.openFile(args[2]); - if (source.isDirectory()) { - throw new Exception("cp: " + args[1] + " is a directory"); - } - } else if (args.length == 4 && args[1].equals("-r")) { - source = ChangeDir.openFile(args[2]); - destination = ChangeDir.openFile(args[3]); - } else { - throw new Exception("Usage: cp [-r] "); - } - if (source.equals(destination)) { - throw new Exception("cp: '" + source.getPath() + "' and '" + destination.getPath() + "' are the same"); - } - if (!source.exists()) { - throw new Exception("cp: " + source.getPath() + ": no such file or directory"); - } - if (destination.exists() && destination.isDirectory()) { - destination = new File(destination, source.getName()); - } - if (!destination.exists()) { - if (source.isDirectory() && !destination.mkdir() || source.isFile() && !destination.createNewFile()) { - throw new Exception("cp: " + destination.getPath() + ": no such file or directory"); - } - } - copy(source, destination); - } - - private static void copy(File source, File destination) throws Exception { - if (source.isFile()) { - copyFile(source, destination); - } else { - copyDir(source, destination); - } - } - - private static void copyFile(File source, File destination) throws Exception { - InputStream input = null; - OutputStream output = null; - - try { - input = new FileInputStream(source); - output = new FileOutputStream(destination); - Cat.printFile(input, output); - } catch (Exception e) { - throw new Exception("cp: can't read file"); - } finally { - input.close(); - output.close(); - } - } - - private static void copyDir(File source, File destination) throws Exception { - destination.mkdir(); - String[] list = source.list(); - for (String fileName : list) { - copy(new File(source, fileName), new File(destination, fileName)); - } - } -} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/shell/List.java b/src/ru/fizteh/fivt/students/andrewzhernov/shell/List.java deleted file mode 100644 index 6574ffe42..000000000 --- a/src/ru/fizteh/fivt/students/andrewzhernov/shell/List.java +++ /dev/null @@ -1,19 +0,0 @@ -package ru.fizteh.fivt.students.andrewzhernov.shell; - -import java.io.File; - -public class List { - public static void execute(String[] args) throws Exception { - if (args.length != 1) { - throw new Exception("Usage: ls"); - } else { - File currentPath = new File(System.getProperty("user.dir")); - String[] list = currentPath.list(); - for (String fileName : list) { - if (fileName.charAt(0) != '.') { - System.out.println(fileName); - } - } - } - } -} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/shell/MakeDir.java b/src/ru/fizteh/fivt/students/andrewzhernov/shell/MakeDir.java deleted file mode 100644 index 2839a9fc5..000000000 --- a/src/ru/fizteh/fivt/students/andrewzhernov/shell/MakeDir.java +++ /dev/null @@ -1,16 +0,0 @@ -package ru.fizteh.fivt.students.andrewzhernov.shell; - -import java.io.File; - -public class MakeDir { - public static void execute(String[] args) throws Exception { - if (args.length != 2) { - throw new Exception("Usage: mkdir "); - } else { - File dir = ChangeDir.openFile(args[1]); - if (!dir.mkdir()) { - throw new Exception("mkdir: " + args[1] + ": can't create directory"); - } - } - } -} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/shell/Move.java b/src/ru/fizteh/fivt/students/andrewzhernov/shell/Move.java deleted file mode 100644 index 93e641a3b..000000000 --- a/src/ru/fizteh/fivt/students/andrewzhernov/shell/Move.java +++ /dev/null @@ -1,28 +0,0 @@ -package ru.fizteh.fivt.students.andrewzhernov.shell; - -public class Move { - public static void execute(String[] args) throws Exception { - if (args.length != 3) { - throw new Exception("Usage: mv "); - } else { - try { - String[] copyArgs = new String[4]; - copyArgs[0] = args[0]; - copyArgs[1] = "-r"; - copyArgs[2] = args[1]; - copyArgs[3] = args[2]; - Copy.execute(copyArgs); - - String[] removeArgs = new String[3]; - removeArgs[0] = args[0]; - removeArgs[1] = "-r"; - removeArgs[2] = args[1]; - Remove.execute(removeArgs); - } catch (Exception e) { - String newException = e.getMessage(); - newException = newException.replaceFirst("(cp|rm):", "mv:"); - throw new Exception(newException); - } - } - } -} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/shell/Pwd.java b/src/ru/fizteh/fivt/students/andrewzhernov/shell/Pwd.java deleted file mode 100644 index 704767b0a..000000000 --- a/src/ru/fizteh/fivt/students/andrewzhernov/shell/Pwd.java +++ /dev/null @@ -1,11 +0,0 @@ -package ru.fizteh.fivt.students.andrewzhernov.shell; - -public class Pwd { - public static void execute(String[] args) throws Exception { - if (args.length != 1) { - throw new Exception("Usage: pwd"); - } else { - System.out.println(System.getProperty("user.dir")); - } - } -} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/shell/Remove.java b/src/ru/fizteh/fivt/students/andrewzhernov/shell/Remove.java deleted file mode 100644 index 431c1da03..000000000 --- a/src/ru/fizteh/fivt/students/andrewzhernov/shell/Remove.java +++ /dev/null @@ -1,41 +0,0 @@ -package ru.fizteh.fivt.students.andrewzhernov.shell; - -import java.io.File; - -public class Remove { - public static void execute(String[] args) throws Exception { - File file = null; - if (args.length == 2) { - file = ChangeDir.openFile(args[1]); - if (file.isDirectory()) { - throw new Exception("rm: " + args[1] + " is a directory"); - } - } else if (args.length == 3 && args[1].equals("-r")) { - file = ChangeDir.openFile(args[2]); - } else { - throw new Exception("Usage: rm [-r] "); - } - - if (!file.exists()) { - throw new Exception("rm: " + file.getPath() + ": no such file or directory"); - } - - removeFile(file); - } - - public static void removeFile(File file) throws Exception { - boolean isRemoved = false; - if (file.isFile()) { - isRemoved = file.delete(); - } else { - String[] list = file.list(); - for (String son : list) { - removeFile(new File(file, son)); - } - isRemoved = file.delete(); - } - if (!isRemoved) { - throw new Exception("rm: " + file.getCanonicalPath() + ": can't remove"); - } - } -} diff --git a/src/ru/fizteh/fivt/students/andrewzhernov/shell/Shell.java b/src/ru/fizteh/fivt/students/andrewzhernov/shell/Shell.java deleted file mode 100644 index 0ea475c55..000000000 --- a/src/ru/fizteh/fivt/students/andrewzhernov/shell/Shell.java +++ /dev/null @@ -1,87 +0,0 @@ -package ru.fizteh.fivt.students.andrewzhernov.shell; - -import java.util.Scanner; - -public class Shell { - public static void main(String[] args) { - if (args.length == 0) { - interactiveMode(); - } else { - packageMode(args); - } - } - - public static void interactiveMode() { - Scanner input = null; - try { - input = new Scanner(System.in); - System.out.print("$ "); - while (input.hasNextLine()) { - try { - executeCommand(parseCommand(input.nextLine())); - } catch (Exception e) { - System.err.println(e.getMessage()); - } - System.out.print("$ "); - } - } catch (Exception e) { - System.err.println(e.getMessage()); - } finally { - System.out.println(); - input.close(); - } - } - - public static void packageMode(String[] args) { - String[] input = parseInput(args); - int isError = 0; - for (String cmd : input) { - try { - executeCommand(parseCommand(cmd)); - } catch (Exception e) { - System.err.println(e.getMessage()); - isError = 1; - } - } - System.exit(isError); - } - - private static String[] parseInput(String[] args) { - StringBuilder input = new StringBuilder(); - for (String cmd : args) { - input.append(cmd).append(' '); - } - return input.toString().split("\\s*;\\s*"); - } - - private static String[] parseCommand(String cmd) { - return cmd.split("\\s+"); - } - - private static void executeCommand(String[] cmd) throws Exception { - if (cmd.length > 0 && cmd[0].length() > 0) { - if (cmd[0].equals("cd")) { - ChangeDir.execute(cmd); - } else if (cmd[0].equals("mkdir")) { - MakeDir.execute(cmd); - } else if (cmd[0].equals("pwd")) { - Pwd.execute(cmd); - } else if (cmd[0].equals("rm")) { - Remove.execute(cmd); - } else if (cmd[0].equals("cp")) { - Copy.execute(cmd); - } else if (cmd[0].equals("mv")) { - Move.execute(cmd); - } else if (cmd[0].equals("ls")) { - List.execute(cmd); - } else if (cmd[0].equals("cat")) { - Cat.execute(cmd); - } else if (cmd[0].equals("exit")) { - System.exit(0); - } else { - throw new Exception("Shell: " + cmd[0] + ": no such command"); - } - } - } - -} diff --git a/tasks/01-Shell.md b/tasks/01-Shell.md deleted file mode 100644 index a4c6dbda2..000000000 --- a/tasks/01-Shell.md +++ /dev/null @@ -1,113 +0,0 @@ -## Shell (Оболочка) -Консольное приложение, частично эмулирующее оболочку -[shell](http://en.wikipedia.org/wiki/Unix_shell). - -``` -java Shell [COMMAND1 [; COMMAND2 ...]] -``` - -Если Shell запускается без параметров, то запускается интерактивный режим, -в котором пользователь может ввести команду (или команды) прямо в консоли. - -Список команд: -* ```cd ``` — change directory, смена -текущей директории. Поддерживаются ```.```, ```..```, относительные и абсолютные -пути -* ```mkdir ``` — создание директории в текущей директории -* ```pwd``` — print working directory, печатает абсолютный путь к текущей -директории -* ```rm [-r] ``` — удаляет указанную в параметрах файл или папку. Если указать параметр ```-r``` то удаляется рекурсивно. -* ```cp [-r] ``` — копирует указанную в параметрах -папку/файл в указанное место. Параметр ```-r``` позволяет копировать рекурсивно. -* ```mv ``` — переносит указанный файл/папку в -новое место (файл на прежнем месте удаляется). В частности переименовывает -файл/папку, если ```source``` и ```destination``` находятся в одной папке -* ```ls``` — печатает содержимое текущей директории -* ```exit``` — выход из приложения -* ```cat ``` — выводит содержимое файла на экран. - -За один раз можно написать несколько команд. Разделителем команд является -```;``` (точка с запятой). - -### Интерактивный режим -В интерактивном режиме должно отображаться "приглашение" — ```$ ``` (знак -доллара и пробел), после которого производится ввод команд. Также в интерактивном -режиме допускается вывод текущей директории перед "приглашением". - -### Пакетный режим -Если запустить консольное приложение с параметрами, то параметры должны -интерпретироваться как команды (все команды должны склеиваться через пробел). -Приложение должно выполнить последовательно все команды и завершиться. - -В случае ошибки команды, приложение должно выводить ошибку в stderr. - -В случае ошибки в любой из команд, приложение должно написать сообщение об -ошибке и завершиться с ненулевым кодом. - -### Вывод команд -Вывод команд имеет строгий формат. Никакого дополнительного вывода в stdout быть -не должно (в финальном коде вся debug-информация должна отсутствовать). - -```(bash) -$ cd /noexistingdir -cd: '/noexistingdir': No such file or directory -$ cd /home/user -$ pwd -/home/user -$ ls -Folder -file.txt -$ mkdir MyFolder -$ ls -Folder -MyFolder -file.txt -$ cp file.txt MyFolder -$ cd MyFolder -$ ls -file.txt -$ mv file.txt file2.txt -$ ls -file2.txt -$ rm file.txt -rm: cannot remove 'file.txt': No such file or directory -$ rm file2.txt -$ ls -$ cd .. -$ ls -Folder -MyFolder -file.txt -$ mv Folder MyFolder -$ ls -MyFolder -file.txt -$ cp MyFolder NewFolder -cp: MyFolder is a directory (not copied). -$ ls -Folder -MyFolder -file.txt -$ cp -r MyFolder NewFolder -$ ls -Folder -MyFolder -NewFolder -file.txt -$ rm NewFolder -rm: NewFolder: is a directory -$ ls -Folder -MyFolder -NewFolder -file.txt -$ rm -r NewFolder -$ ls -Folder -MyFolder -file.txt -$ cat file.text -Hello World! -$ cat wrongfile.txt -cat: wrongfile.txt: No such file or directory -$ exit