diff --git a/src/ru/fizteh/fivt/students/LevkovMiron/ProxyTest/TableCloseabilityTest.java b/src/ru/fizteh/fivt/students/LevkovMiron/ProxyTest/TableCloseabilityTest.java new file mode 100644 index 000000000..253a89c95 --- /dev/null +++ b/src/ru/fizteh/fivt/students/LevkovMiron/ProxyTest/TableCloseabilityTest.java @@ -0,0 +1,70 @@ +package ru.fizteh.fivt.students.LevkovMiron.ProxyTest; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import ru.fizteh.fivt.students.LevkovMiron.Proxy.CTable; +import ru.fizteh.fivt.students.LevkovMiron.Proxy.CTableProvider; +import ru.fizteh.fivt.students.LevkovMiron.Proxy.CTableProviderFactory; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collection; + +/** + * Created by Мирон on 01.12.2014 ru.fizteh.fivt.students.LevkovMiron.ProxyTest. + */ +@RunWith(Parameterized.class) +public class TableCloseabilityTest { + + private Method currentMethod; + private CTableProvider provider; + private CTable table; + private CTableProviderFactory factory = new CTableProviderFactory(); + + @Rule + public TemporaryFolder tmpFolder = new TemporaryFolder(); + + @Before + public void before() throws IOException { + factory = new CTableProviderFactory(); + provider = (CTableProvider) factory.create(tmpFolder.newFolder().getAbsolutePath()); + table = (CTable) provider.createTable("t1", Arrays.asList(Integer.class, String.class)); + } + + @Parameterized.Parameters + public static Collection init() { + Method[] methods = CTable.class.getDeclaredMethods(); + Method[][] data = new Method[methods.length][1]; + for (int i = 0; i < methods.length; i++) { + data[i][0] = methods[i]; + } + return Arrays.asList(data); + } + + public TableCloseabilityTest(Method method) { + currentMethod = method; + } + + @Test (expected = IllegalStateException.class) + public void testCurrentMethod() throws Throwable { + table.close(); + if (currentMethod.equals(CTable.class.getMethod("close")) || currentMethod.getModifiers() != 1) { + throw new IllegalStateException(); + } + Object[] args = new Object[currentMethod.getParameterCount()]; + if (currentMethod.getName().equals("getColumnType")) { + args[0] = 0; + } + try { + currentMethod.invoke(table, args); + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/Shell/Commands.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/Shell/Commands.java new file mode 100644 index 000000000..f415dab67 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/Shell/Commands.java @@ -0,0 +1,11 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell; + + +public interface Commands { + + String getCommandName(); + + int getArgumentQuantity(); + + void implement(String[] args, State state) throws SomethingIsWrongException; +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/Shell/Parser.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/Shell/Parser.java new file mode 100644 index 000000000..294da1161 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/Shell/Parser.java @@ -0,0 +1,45 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell; + +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Parser { + + + public static ArrayList parseCommandArgs(String params) { + ArrayList matchList = new ArrayList(); + Pattern regex = Pattern.compile("[^\\s\"']+|\"[^\"]*\"|'[^']*'"); + Matcher regexMatcher = regex.matcher(params); + while (regexMatcher.find()) { + String param = regexMatcher.group().replaceAll("\"?([~\"]*)\"?", "$1"); + matchList.add(param); + } + return matchList; + } + + public static String getName(String command) { + int spiltIndex = command.indexOf(' '); + if (spiltIndex == -1) { + return command; + } else { + return command.substring(0, spiltIndex); + } + } + + public static String[] parseFullCommand(String commands) { + String[] commandArray = commands.split(";"); + for (int i = 0; i < commandArray.length; ++i) { + commandArray[i] = commandArray[i].trim(); + } + return commandArray; + } + public static String getParameters(String command) { + int spiltIndex = command.indexOf(' '); + if (spiltIndex == -1) { + return ""; + } else { + return command.substring(spiltIndex + 1); + } + } +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/Shell/Shell.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/Shell/Shell.java new file mode 100644 index 000000000..0cf9db533 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/Shell/Shell.java @@ -0,0 +1,138 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; +import java.util.Set; + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.multifilemap.MultiFileMapShellState; + + +public class Shell { + private final Map availableCommands; + private static final String GREETING = "$ "; + State state; + + + public Shell(Set commands) { + Map tempCommands = new HashMap(); + for (Commands temp : commands) { + tempCommands.put(temp.getCommandName(), temp); + } + availableCommands = Collections.unmodifiableMap(tempCommands); + } + + public void setShellState(State state) { + this.state = state; + } + + private void runCommand(String[] data, State state) throws SomethingIsWrongException { + if (data[0].length() == 0) { + throw new SomethingIsWrongException("Empty string."); + } + Commands usedOne = availableCommands.get(data[0]); + if (usedOne == null) { + throw new SomethingIsWrongException("Unknown command."); + } else if (data.length - 1 != usedOne.getArgumentQuantity()) { + if (!(usedOne.getCommandName().equals("rm") && data.length - 1 == usedOne.getArgumentQuantity() + 1) + && !(usedOne.getCommandName().equals("cp") && data.length - 1 + == usedOne.getArgumentQuantity() + 1)) { + throw new SomethingIsWrongException("Wrong number of arguments. Correct argument quantity = " + + (data.length - 1) + "\nTo correctly run this command use " + + usedOne.getArgumentQuantity() + " arguments."); + } + } + String[] commandArguments = Arrays.copyOfRange(data, 1, data.length); + usedOne.implement(commandArguments, state); + } + + private String[] splitLine(String str) { + str = str.trim(); + String[] toReturn = str.split("\\s*;\\s*", -2); + return toReturn; + } + private void runLine(String line, State state) throws SomethingIsWrongException { + String[] splitted = splitLine(line); + int count = splitted.length - 1; + for (String temp : splitted) { + --count; + if (count >= 0) { + runCommand(temp.split("\\s+"), state); //This thing is needed to check if after last ";" + } else if (count < 0) { //situated a command or an empty string. So it + if (temp.length() != 0) { //won't throw a "Wrong command" exception when it + runCommand(temp.split("\\s+"), state); //looks like: "dir; cd ..; dir;" neither it will + } //loose a "dir" in: "dir; cd ..; dir; dir". + } //So it does nothing if it is the end, but performs + } //if there is a command after last ";". + } + + public void consoleWay(State state) { + Scanner forInput = new Scanner(System.in); + while (!Thread.currentThread().isInterrupted()) { + System.out.print(GREETING); + try { + runLine(forInput.nextLine(), state); + } catch (SomethingIsWrongException exc) { + if (exc.getMessage().equals("EXIT")) { + forInput.close(); + System.exit(0); + } else { + System.err.println(exc.getMessage()); + } + } + } + forInput.close(); + } + + public void run(String[] args, Shell shell) { + if (args.length != 0) { + String arg = UtilMethods.uniteItems(Arrays.asList(args), " "); + String[] commands = Parser.parseFullCommand(arg); + try { + runCommand(commands, state); + } catch (SomethingIsWrongException exc) { + if (exc.getMessage().equals("EXIT")) { + System.exit(0); + } else { + System.err.println(exc.getMessage()); + System.exit(-1); + } + } + } else { + consoleWay(state); + } + System.exit(0); + } + + /* public static void notmain(String[] args) { + ShellState state = new ShellState(System.getProperty("user.dir")); + Set commands = new HashSet() {{ + /*add(new WhereAreWeCommand()); add(new CopyCommand()); add(new DirectoryInfoCommand()); + add(new ExitCommand()); add(new MakeDirectoryCommand()); + add(new MoveCommand()); add(new RemoveCommand()); }};*/ + /* Shell shell = new Shell(commands); + + if (args.length != 0) { + String arg = UtilMethods.uniteItems(Arrays.asList(args), " "); + try { + shell.runLine(arg, state); + } catch (SomethingIsWrongException exc) { + if (exc.getMessage().equals("EXIT")) { + System.exit(0); + } else { + System.err.println(exc.getMessage()); + System.exit(-1); + } + } + } else { + shell.consoleWay(state); + } + System.exit(0); + }*/ + + + +} + diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/Shell/ShellState.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/Shell/ShellState.java new file mode 100644 index 000000000..893f4b1c2 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/Shell/ShellState.java @@ -0,0 +1,14 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell; + +public class ShellState { + private String currentDirectory; + public ShellState(String currentDirectory) { + this.currentDirectory = currentDirectory; + } + public String getCurDir() { + return currentDirectory; + } + void changeCurDir(String newCurDir) { + currentDirectory = newCurDir; + } +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/Shell/SomeCommand.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/Shell/SomeCommand.java new file mode 100644 index 000000000..8e1592e66 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/Shell/SomeCommand.java @@ -0,0 +1,4 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell; + +public abstract class SomeCommand implements Commands { +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/Shell/SomethingIsWrongException.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/Shell/SomethingIsWrongException.java new file mode 100644 index 000000000..cfe244972 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/Shell/SomethingIsWrongException.java @@ -0,0 +1,20 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell; + + +public final class SomethingIsWrongException extends Exception { + public SomethingIsWrongException() { + super(); + } + + public SomethingIsWrongException(String message) { + super(message); + } + + public SomethingIsWrongException(String message, Throwable cause) { + super(message, cause); + } + + public SomethingIsWrongException(Throwable cause) { + super(cause); + } +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/Shell/UtilMethods.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/Shell/UtilMethods.java new file mode 100644 index 000000000..d40ecdaf8 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/Shell/UtilMethods.java @@ -0,0 +1,100 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell; + +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.Collection; + +public class UtilMethods { + public static final String ENCODING = "UTF-8"; + public static String uniteItems(Collection items, String separator) { + boolean isFirstIteration = true; + StringBuilder joinBuilder = new StringBuilder(); + for (Object item: items) { + if (isFirstIteration) { + isFirstIteration = false; + } else { + joinBuilder.append(separator); + } + joinBuilder.append(item.toString()); + } + return joinBuilder.toString(); + } + + public static void closeCalm(Closeable something) { + try { + if (something != null) { + something.close(); + } + } catch (IOException e) { + System.err.println("Error aquired while trying to close " + e.getMessage()); + } + } + + public static void copyFile(File source, File dirDestination) throws SomethingIsWrongException { + File copy = new File(dirDestination, source.getName()); + FileInputStream ofOriginal = null; + FileOutputStream ofCopy = null; + try { + copy.createNewFile(); + ofOriginal = new FileInputStream(source); + ofCopy = new FileOutputStream(copy); + byte[] buf = new byte[4096]; //size of reading = 4kB + int read = ofOriginal.read(buf); + while (read > 0) { + ofCopy.write(buf, 0, read); + read = ofOriginal.read(buf); + } + } catch (FileNotFoundException e) { + throw new SomethingIsWrongException("This file or directory doesn't exist yet. " + e.getMessage()); + } catch (IOException e) { + throw new SomethingIsWrongException("Error while writing/reading file. " + e.getMessage()); + } finally { + closeCalm(ofOriginal); + closeCalm(ofCopy); + } + } + + public static File getAbsoluteName(String fileName, ShellState state) { + File file = new File(fileName); + + if (!file.isAbsolute()) { + file = new File(state.getCurDir(), fileName); + } + return file; + } + public static byte[] getBytes(String string, String encoding) throws SomethingIsWrongException { + byte[] bytes = null; + try { + bytes = string.getBytes(encoding); + } catch (UnsupportedEncodingException e) { + throw new SomethingIsWrongException("Unable to convert string to bytes of this type: " + e.getMessage()); + } + return bytes; + } + + public static byte[] bytesToArray(ByteArrayOutputStream bytes) { + byte[] result = new byte[bytes.size()]; + result = bytes.toByteArray(); + return result; + } + + public static boolean doesExist(String path) { + File file = new File(path); + return file.exists(); + } + + public static int countBytes(String string, String encoding) { + try { + byte[] bytes = string.getBytes(encoding); + return bytes.length; + } catch (Exception e) { + return 0; + } + } +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/CommitCommand.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/CommitCommand.java new file mode 100644 index 000000000..7f02aa5af --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/CommitCommand.java @@ -0,0 +1,23 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap; + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell.*; + +public class CommitCommand implements Commands { + + public String getCommandName() { + return "commit"; + } + + public int getArgumentQuantity() { + return 0; + } + + public void implement(String[] args, FileMapShellState state) + throws SomethingIsWrongException { + if (state.table == null) { + throw new SomethingIsWrongException("Table not found."); + } + state.table.commit(); + } + +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/ExitCommand.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/ExitCommand.java new file mode 100644 index 000000000..078bdbaf9 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/ExitCommand.java @@ -0,0 +1,24 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap; + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell.*; + +public class ExitCommand implements Commands { + + public String getCommandName() { + return "exit"; + } + + public int getArgumentQuantity() { + return 0; + } + public void implement(String[] args, FileMapShellState state) throws SomethingIsWrongException { + MyTable temp = (MyTable) state.table; + if (state.table != null && !temp.getAutoCommit()) { + temp.rollback(); + } else if (temp.getAutoCommit() && state.table != null) { + temp.commit(); + } + throw new SomethingIsWrongException("EXIT"); + } + +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/FileMapMain.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/FileMapMain.java new file mode 100644 index 000000000..fe237164f --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/FileMapMain.java @@ -0,0 +1,22 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap; + + +import java.util.HashSet; +import java.util.Set; + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell.*; + +public class FileMapMain { + public static void main(String[] args) { + FileMapShellState state = new FileMapShellState(); + Set com = new HashSet() { { add(new ExitCommand()); + add(new RollbackCommand()); add(new CommitCommand()); + add(new PutCommand()); add(new GetCommand()); add(new RemoveKeyCommand()); + add(new ListCommand()); }}; + Shell shell = new Shell(com); + String dbDirectory = System.getProperty("fizteh.db.dir"); + state.table = new SingleFileTable(dbDirectory, "master"); + shell.setShellState(state); + shell.consoleWay(state); + } +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/FileMapReadingUtils.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/FileMapReadingUtils.java new file mode 100644 index 000000000..2b67222c6 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/FileMapReadingUtils.java @@ -0,0 +1,112 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap; + +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.EOFException; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; + + +public class FileMapReadingUtils implements Closeable{ + + protected static RandomAccessFile tempFile; + private static int valueShift = -1; + + public FileMapReadingUtils(String pathToFile) throws IOException { + try { + tempFile = new RandomAccessFile(pathToFile, "r"); + } catch (FileNotFoundException e) { + tempFile = null; + valueShift = 0; + return; + } + if (tempFile.length() == 0) { + throw new IOException("empty file"); + } + skipKey(); + valueShift = readOffset(); + tempFile.seek(0); + } + + public String readKey() throws IOException { + if (tempFile.getFilePointer() >= valueShift) { + return null; + } + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + byte b = tempFile.readByte(); + while (b != 0) { + bytes.write(b); + b = tempFile.readByte(); + } + return new String(bytes.toByteArray(), GlobalUtils.ENCODING); + } + + public String readValue() throws IOException { + int offset = readOffset(); + int nextOffset = readNextOffset(); + long currentOffset; + currentOffset = tempFile.getFilePointer(); + tempFile.seek(offset); + int valueLength = nextOffset - offset; + byte[] bytes = new byte[valueLength]; + tempFile.read(bytes, 0, valueLength); + tempFile.seek(currentOffset); + return new String(bytes, GlobalUtils.ENCODING); + + } + + public static void scanFromDisk(String file, TableBuilder builder) throws IOException { + if (!GlobalUtils.doesExist(file)) { + throw new IOException("didn't exist"); + } + FileMapReadingUtils read = new FileMapReadingUtils(file); + while (!read.endOfFile()) { + String key = read.readKey(); + String value = read.readValue(); + builder.put(key, value); + } + read.close(); + } + + public boolean endOfFile() throws IOException { + if (tempFile == null) { + return true; + } + boolean result = true; + try { + result = (tempFile.getFilePointer() == valueShift); + } catch (EOFException e) { + System.err.println("Reached end of file"); + } + return result; + } + + + private int readNextOffset() throws IOException { + int nextOffset = 0; + long currentOffset = tempFile.getFilePointer(); + if (readKey() == null) { + nextOffset = (int) tempFile.length(); + } else { + nextOffset = readOffset(); + } + tempFile.seek(currentOffset); + return nextOffset; + } + + private void skipKey() throws IOException { + byte b; + do { + b = tempFile.readByte(); + } while(b != 0); + } + + private int readOffset() throws IOException { + return tempFile.readInt(); + } + + public void close() { + GlobalUtils.closeCalm(tempFile); + } +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/FileMapShellState.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/FileMapShellState.java new file mode 100644 index 000000000..9b8d1d354 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/FileMapShellState.java @@ -0,0 +1,6 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap; + + +public class FileMapShellState { + public Table table = null; + } diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/FileMapShellStateInterface.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/FileMapShellStateInterface.java new file mode 100644 index 000000000..db1836208 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/FileMapShellStateInterface.java @@ -0,0 +1,27 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap; + + +public interface FileMapShellStateInterface { + + Value put(Key key, Value value); + + Value get(Key key); + + int commit(); + + int rollback(); + + int size(); + + Value remove(Key key); + + Table getTable(); + + String keyToString(Key key); + + String valueToString(Value value); + + Key parseKey(String key); + + Value parseValue(String value); +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/FileMapWritingUtils.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/FileMapWritingUtils.java new file mode 100644 index 000000000..5d46c5022 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/FileMapWritingUtils.java @@ -0,0 +1,65 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap; + +import java.io.Closeable; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.io.UnsupportedEncodingException; +import java.util.Set; + + + +public class FileMapWritingUtils implements Closeable { + + protected static RandomAccessFile dataFile; + public FileMapWritingUtils(String filePath) throws IOException { + try { + dataFile = new RandomAccessFile(filePath, "rw"); + } catch (FileNotFoundException e) { + throw new IOException(String.format("error while creating file: '%s'", filePath)); + } + try { + dataFile.setLength(0); + } catch (IOException e) { + throw new IOException("Error aqcuired while resizing a file " + e.getMessage()); + } + } + + public void writeKey(String key) throws IOException { + byte[] bytes = GlobalUtils.getBytes(key, GlobalUtils.ENCODING); + dataFile.write(bytes); + dataFile.writeByte(0); + } + + public static void writeOnDisk(Set keys, String file, TableBuilder builder) throws IOException { + FileMapWritingUtils write = new FileMapWritingUtils(file); + int shift = GlobalUtils.getKeysLength(keys, GlobalUtils.ENCODING); + for (String key : keys) { + write.writeKey(key); + write.writeOffset(shift); + shift += GlobalUtils.countBytes(builder.get(key), GlobalUtils.ENCODING); + } + for (String key : keys) { + write.writeValue(builder.get(key)); + } + write.close(); + } + + public void writeOffset(int offset) throws IOException { + dataFile.writeInt(offset); + } + + public void writeValue(String value) throws IOException { + try { + byte[] bytes = value.getBytes(GlobalUtils.ENCODING); + dataFile.write(bytes); + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException("Unrecognized encoding"); + } + } + + public void close() { + GlobalUtils.closeCalm(dataFile); + } + +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/GetCommand.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/GetCommand.java new file mode 100644 index 000000000..4cd5570f3 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/GetCommand.java @@ -0,0 +1,30 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap; + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell.*; + +public class GetCommand implements Commands { + + public String getCommandName() { + return "get"; + } + + public int getArgumentQuantity() { + return 1; + } + + public void implement(String[] args, FileMapShellState state) + throws SomethingIsWrongException { + if (state.table == null) { + throw new SomethingIsWrongException("Table not found."); + } + String value = state.table.get(args[0]); + if (value == null) { + System.out.println("Not found\n"); + } else { + System.out.println("Found:\n" + value); + } + + } + + +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/GlobalUtils.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/GlobalUtils.java new file mode 100644 index 000000000..e534b10b9 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/GlobalUtils.java @@ -0,0 +1,149 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell.ShellState; + +public class GlobalUtils { + public static final Charset ENCODING = StandardCharsets.UTF_8; + public static final int DIR_QUANTITY = 16; + public static final int FILES_PER_DIR = 16; + private static final Pattern DIR_PATTERN = Pattern.compile("([^\\.]+).dir"); + private static final Pattern FILE_PATTERN = Pattern.compile("([^\\.]+).dat"); + + public static String uniteItems(Collection items, String separator) { + boolean isFirstIteration = true; + StringBuilder joinBuilder = new StringBuilder(); + for (Object item: items) { + if (isFirstIteration) { + isFirstIteration = false; + } else { + joinBuilder.append(separator); + } + joinBuilder.append(item.toString()); + } + return joinBuilder.toString(); + } + + public static void closeCalm(Closeable something) { + try { + if (something != null) { + something.close(); + } + } catch (IOException e) { + System.err.println("Error aqcuired while trying to close " + e.getMessage()); + } + } + + public static File getAbsoluteName(String fileName, ShellState state) { + File file = new File(fileName); + + if (!file.isAbsolute()) { + file = new File(state.getCurDir(), fileName); + } + return file; + } + public static byte[] getBytes(String string, Charset encoding) { + byte[] bytes = null; + bytes = string.getBytes(encoding); + return bytes; + } + + public static byte[] bytesToArray(ByteArrayOutputStream bytes) { + byte[] result = new byte[bytes.size()]; + result = bytes.toByteArray(); + return result; + } + + public static boolean doesExist(String path) { + File file = new File(path); + return file.exists(); + } + + public static int countBytes(String string, Charset encoding) { + byte[] bytes = string.getBytes(encoding); + return bytes.length; + } + + public static int getKeysLength(Set keys, Charset charset) { + int keysLength = 0; + for (final String key : keys) { + int keyLength = countBytes(key, charset); + keysLength += keyLength + 5; + } + return keysLength; + } + + public static boolean compare(Object key1, Object key2) { + if (key1 == null && key2 == null) { + return true; + } else if (key1 == null || key2 == null) { + return false; + } else { + return key1.equals(key2); + } + } + + public static void deleteFile(File fileToDelete) { + if (!fileToDelete.exists()) { + return; + } + if (fileToDelete.isDirectory()) { + for (final File file : fileToDelete.listFiles()) { + deleteFile(file); + } + } + fileToDelete.delete(); + } + + public static int parseDirNumber(File dir) { + String name = dir.getName(); + Matcher matcher = DIR_PATTERN.matcher(name); + if (matcher.matches()) { + return Integer.parseInt(matcher.group(1)); + } + throw new IllegalArgumentException("incorrect dir name"); + } + + public static int parseFileNumber(File file) { + String name = file.getName(); + Matcher matcher = FILE_PATTERN.matcher(name); + if (matcher.matches()) { + return Integer.parseInt(matcher.group(1)); + } + throw new IllegalArgumentException("incorrect file name"); + } + + public static void checkKeyPlacement(String key, int currentDir, int currentFile) { + if (currentDir != getDirNumber(key) || currentFile != getFileNumber(key)) { + throw new IllegalArgumentException("invalid key placement"); + } + } + + public static String parseTableName(String params) { + int index = params.indexOf(' '); + if (index == -1) { + return params; + } + return params.substring(0, index); + } + + public static int getDirNumber(String key) { + byte[] bytes = GlobalUtils.getBytes(key, GlobalUtils.ENCODING); + return Math.abs(bytes[0] % DIR_QUANTITY); + } + + public static int getFileNumber(String key) { + byte[] bytes = GlobalUtils.getBytes(key, GlobalUtils.ENCODING); + return Math.abs(bytes[0] / DIR_QUANTITY % FILES_PER_DIR); + } + +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/ListCommand.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/ListCommand.java new file mode 100644 index 000000000..1d4abbded --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/ListCommand.java @@ -0,0 +1,38 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap; + +import java.util.List; + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell.*; + +public class ListCommand implements Commands { + + public String getCommandName() { + return "list"; + } + + public int getArgumentQuantity() { + return 0; + } + + public void implement(String[] args, FileMapShellState state) + throws SomethingIsWrongException { + if (state.table == null) { + throw new SomethingIsWrongException("Table not found."); + } + MyTable temp = (MyTable) state.table; + List keySet = temp.list(); + if (keySet.size() == 0) { + System.out.println("\n"); + return; + } + StringBuilder sb = new StringBuilder(""); + for (String key : keySet) { + sb.append(key); + sb.append(", "); + } + sb.deleteCharAt(sb.length() - 1); + sb.deleteCharAt(sb.length() - 1); + System.out.println(sb.toString()); + } + +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/MyTable.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/MyTable.java new file mode 100644 index 000000000..63e7d0174 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/MyTable.java @@ -0,0 +1,18 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap; + +import java.util.List; + + +public interface MyTable extends Table { + String getName(); + void setAutoCommit(boolean status); + boolean getAutoCommit(); + int getChangesCounter(); + List list(); + String get(String a); + String put(String key, String value); + String remove(String a); + int size(); + int commit(); + int rollback(); +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/PutCommand.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/PutCommand.java new file mode 100644 index 000000000..bd00504ae --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/PutCommand.java @@ -0,0 +1,28 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap; + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell.*; + +public class PutCommand implements Commands { + + public String getCommandName() { + return "put"; + } + + public int getArgumentQuantity() { + return 2; + } + + public void implement(String[] args, FileMapShellState state) + throws SomethingIsWrongException { + if (state.table == null) { + throw new SomethingIsWrongException("No table chosen"); + } + String temp = state.table.put(args[0], args[1]); + if (temp != null) { + System.out.println("overwrite\n" + temp); + } else { + System.out.println("new"); + } + } + +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/ReadingUtils.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/ReadingUtils.java new file mode 100644 index 000000000..6acadf993 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/ReadingUtils.java @@ -0,0 +1,141 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap; + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell.*; + +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.io.UnsupportedEncodingException; + + +public class ReadingUtils { + + protected RandomAccessFile tempFile; + private static int valueShift = -1; + + public ReadingUtils(String pathToFile) throws SomethingIsWrongException { + try { + tempFile = new RandomAccessFile(pathToFile, "r"); + } catch (FileNotFoundException e) { + tempFile = null; + valueShift = 0; + return; + } + skipKey(); + valueShift = readOffset(); + try { + tempFile.seek(0); + } catch (IOException e) { + throw new SomethingIsWrongException("Error aqcuired while seeking through file: " + e.getMessage()); + } + } + + public String readKey() throws SomethingIsWrongException { + byte[] array; + try { + if (tempFile.getFilePointer() >= valueShift) { + return null; + } + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + byte b = tempFile.readByte(); + while (b != 0) { + bytes.write(b); + b = tempFile.readByte(); + } + array = UtilMethods.bytesToArray(bytes); + } catch (IOException e) { + throw new SomethingIsWrongException("Error acquired: " + e.getMessage()); + } + String returnKey; + try { + returnKey = new String(array, UtilMethods.ENCODING); + } catch (UnsupportedEncodingException e) { + throw new SomethingIsWrongException("Error acquired: " + e.getMessage()); + } + return returnKey; + } + + public String readValue() throws SomethingIsWrongException { + int offset = readOffset(); + int nextOffset = readNextOffset(); + long currentOffset; + try { + currentOffset = tempFile.getFilePointer(); + tempFile.seek(offset); + int valueLength = nextOffset - offset; + byte[] bytes = new byte[valueLength]; + tempFile.read(bytes, 0, valueLength); + tempFile.seek(currentOffset); + return new String(bytes, UtilMethods.ENCODING); + } catch (IOException e) { + throw new SomethingIsWrongException("Error acquired: " + e.getMessage()); + } + + } + + public boolean endOfFile() throws SomethingIsWrongException { + if (tempFile == null) { + return true; + } + + boolean result = true; + try { + result = (tempFile.getFilePointer() == valueShift); + } catch (EOFException ee) { + return result; + } catch (IOException e) { + if (e.getMessage() != null) { + throw new SomethingIsWrongException("Error aqcuired while reading a file " + e.getMessage()); + } + } + return result; + } + + + private int readNextOffset() throws SomethingIsWrongException { + int nextOffset = 0; + try { + int currentOffset = (int) tempFile.getFilePointer(); + if (readKey() == null) { + nextOffset = (int) tempFile.length(); + } else { + nextOffset = readOffset(); + } + tempFile.seek(currentOffset); + } catch (IOException e) { + if (e.getMessage() != null) { + if (e.getMessage() != null) { + throw new SomethingIsWrongException("Error aqcuired while reading a file " + e.getMessage()); + } + } + } + return nextOffset; + } + + private void skipKey() throws SomethingIsWrongException { + byte b = 0; + do { + try { + b = tempFile.readByte(); + } catch (IOException e) { + if (e.getMessage() != null) { + throw new SomethingIsWrongException("Error aqcuired while reading a file " + e.getMessage()); + } + } + } while(b != 0); + } + + private int readOffset() throws SomethingIsWrongException { + try { + return tempFile.readInt(); + } catch (IOException e) { + if (e.getMessage() != null) { + throw new SomethingIsWrongException("Error aqcuired while reading a file " + e.getMessage()); + } else { + throw new SomethingIsWrongException("Empty file"); + } + } + } +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/RemoveKeyCommand.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/RemoveKeyCommand.java new file mode 100644 index 000000000..5ff887040 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/RemoveKeyCommand.java @@ -0,0 +1,26 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap; + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell.Commands; +import ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell.SomethingIsWrongException; + +public class RemoveKeyCommand implements Commands { + + public String getCommandName() { + return "remove"; + } + + public int getArgumentQuantity() { + return 1; + } + + public void implement(String[] args, FileMapShellState state) + throws SomethingIsWrongException { + String a = state.table.remove(args[0]); + if (a.isEmpty()) { + System.out.println("not found"); + } else { + System.out.println("found\n" + a); + } + } + +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/RollbackCommand.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/RollbackCommand.java new file mode 100644 index 000000000..1d638f696 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/RollbackCommand.java @@ -0,0 +1,23 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap; + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell.*; + +public class RollbackCommand implements Commands { + + public String getCommandName() { + return "rollback"; + } + + public int getArgumentQuantity() { + return 0; + } + + public void implement(String[] args, FileMapShellState state) + throws SomethingIsWrongException { + if (state.table == null) { + throw new SomethingIsWrongException("No table chosen"); + } + state.table.rollback(); + } + +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/SimpleTableBuilder.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/SimpleTableBuilder.java new file mode 100644 index 000000000..5ee2dc35a --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/SimpleTableBuilder.java @@ -0,0 +1,33 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap; + +import java.io.File; +import java.util.Set; + +public class SimpleTableBuilder implements TableBuilder { + TableUsingStrings table; + + public String get(String key) { + return table.rawGet(key); + } + + public void put(String key, String value) { + table.rawPut(key, value); + } + + public Set getKeys() { + return table.unchangedOldData.keySet(); + } + + public File getTableDirectory() { + return new File(table.getParentDirectory(), table.getName()); + } + + public void setCurrentFile(File currentFile) { + } + + public SimpleTableBuilder(TableUsingStrings table) { + this.table = table; + } + + +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/SingleFileTable.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/SingleFileTable.java new file mode 100644 index 000000000..961393f60 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/SingleFileTable.java @@ -0,0 +1,39 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap; + + +import java.io.File; +import java.io.IOException; + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell.SomethingIsWrongException; + + +public class SingleFileTable extends SomeTable { + + public static final String DATABASENAME = "db.dat"; + + public SingleFileTable(String dir, String name) { + super(dir, name); + } + + protected void load() throws SomethingIsWrongException { + scanFromDisk(getPathToDatabase()); + } + + protected void save() throws SomethingIsWrongException { + writeOnDisk(unchangedOldData.keySet(), getPathToDatabase()); + } + + private String getPathToDatabase() { + File curDir = new File(new File(".").getAbsolutePath()); + File databaseFile; + try { + databaseFile = new File(curDir.getCanonicalPath(), DATABASENAME); + return databaseFile.getAbsolutePath(); + } catch (IOException e) { + e.getMessage(); + } + return ""; + } + + +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/SizeCommand.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/SizeCommand.java new file mode 100644 index 000000000..58cebe458 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/SizeCommand.java @@ -0,0 +1,26 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap; + + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell.SomeCommand; +import ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell.SomethingIsWrongException; + +public class SizeCommand> extends SomeCommand { + + public String getCommandName() { + return "size"; + } + + public int getArgumentQuantity() { + return 0; + } + + public void implement(String[] args, State state) + throws SomethingIsWrongException { + if (state.getTable() == null) { + throw new SomethingIsWrongException("no table"); + } + System.out.println(state.size()); + } + +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/SomeStorage.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/SomeStorage.java new file mode 100644 index 000000000..49c3cfe84 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/SomeStorage.java @@ -0,0 +1,205 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap; + +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + + +public abstract class SomeStorage { + protected HashMap unchangedOldData; + protected HashSet deletedKeys; + private String name; + protected String parentDirectory; + protected boolean doAutoCommit = false; + private final Lock transactionLock = new ReentrantLock(true); + + protected final ThreadLocal transaction = new ThreadLocal() { + public Transactions initialValue() { + return new Transactions(); + } + }; + + class Transactions { + HashMap currentData; + int size; + int unsavedChangesCounter; + + Transactions() { + this.currentData = new HashMap(); + this.size = 0; + this.unsavedChangesCounter = 0; + } + + public void newModification(Key key, Value value) { + currentData.put(key, value); + } + + public int saveModifications() { + int changesCounter = 0; + for (Key key : currentData.keySet()) { + Value newOne = currentData.get(key); + if (!GlobalUtils.compare(newOne, unchangedOldData.get(key))) { + if (newOne != null) { + unchangedOldData.put(key, newOne); + } else { + unchangedOldData.remove(key); + } + changesCounter++; + } + } + return changesCounter; + } + + public int calculateChangesQuantity() { + int changesCounter = 0; + for (Key key : currentData.keySet()) { + Value newOne = currentData.get(key); + if (!GlobalUtils.compare(newOne, unchangedOldData.get(key))) { + changesCounter++; + } + } + return changesCounter; + } + + public int calculateSize() { + int size = unchangedOldData.size(); + for (Key key : currentData.keySet()) { + Value newOne = currentData.get(key); + Value oldOne = unchangedOldData.get(key); + if (newOne == null && oldOne != null) { + size--; + } else if (newOne != null && oldOne == null) { + size++; + } + } + return size; + } + + public Value getVal(Key key) { + if (currentData.containsKey(key)) { + return currentData.get(key); + } + return unchangedOldData.get(key); + } + + public int getSize() { + return unchangedOldData.size() + calculateSize(); + } + + public void incrementUnsavedChangesCounter() { + unsavedChangesCounter++; + } + + public int getUnsavedChangesCounter() { + return unsavedChangesCounter; + } + + public void clear() { + currentData.clear(); + unsavedChangesCounter = 0; + size = 0; + } + + } + + public String getParentDirectory() { + return parentDirectory; + } + + public void setAutoCommit(boolean status) { + doAutoCommit = status; + } + + public boolean getAutoCommit() { + return doAutoCommit; + } + + public String getName() { + return name; + } + public int sizeOfStorage() { + return transaction.get().calculateSize(); + } + + public int getChangesCounter() { + return transaction.get().calculateChangesQuantity(); + } + + protected abstract void load() throws IOException; + protected abstract void save() throws IOException; + + public SomeStorage(String dir, String name) { + this.parentDirectory = dir; + this.name = name; + unchangedOldData = new HashMap(); + try { + load(); + } catch (IOException e) { + if (!e.getMessage().equals("didn't exist") && !e.getMessage().equals("empty file")) { + throw new IllegalArgumentException("invalid file format " + e.getMessage()); + } + } + + } + + public Value getFromStorage(Key key) { + if (key == null) { + throw new IllegalArgumentException("key cannot be null"); + } + return transaction.get().getVal(key); + } + + public Value putIntoStorage(Key key, Value value) { + Value oldVal = transaction.get().getVal(key); + transaction.get().newModification(key, value); + return oldVal; + } + + public Value removeFromStorage(Key key) { + if (key == null) { + throw new IllegalArgumentException("key cannot be null"); + } + if (getFromStorage(key) == null) { + return null; + } + Value oldVal = transaction.get().getVal(key); + transaction.get().newModification(key, null); + transaction.get().incrementUnsavedChangesCounter(); + return oldVal; + } + + public int rollbackStorage() { + int deletedOrAdded = transaction.get().calculateChangesQuantity(); + transaction.get().clear(); + return deletedOrAdded; + } + + public int commitStorage() { + try { + transactionLock.lock(); + int commitCount = transaction.get().saveModifications(); + transaction.get().clear(); + try { + save(); + } catch (IOException e) { + System.err.println("commit error: " + e.getMessage()); + return 0; + } + return commitCount; + } finally { + transactionLock.unlock(); + } + } + + void rawPut(Key key, Value value) { + unchangedOldData.put(key, value); + } + + Value rawGet(Key key) { + return unchangedOldData.get(key); + } + + +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/SomeTable.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/SomeTable.java new file mode 100644 index 000000000..c9ff67102 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/SomeTable.java @@ -0,0 +1,202 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap; + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell.*; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + + +public abstract class SomeTable implements MyTable { + protected static HashMap unchangedOldData; + protected static HashMap currentData; + protected static HashSet deletedKeys; + private String name; + private String parentDirectory; + private static int size; + public static int unsavedChangesCounter; + protected boolean autoCommit = false; //to block JUnit functional for a filemap + protected abstract void load() throws SomethingIsWrongException; + protected abstract void save() throws SomethingIsWrongException; + + + public void setAutoCommit(boolean status) { + autoCommit = status; + } + + public boolean getAutoCommit() { + return autoCommit; + } + + public List list() { + String a = ""; + List toReturnSafe = new LinkedList(currentData.keySet()); + return toReturnSafe; + } + + public int getChangesCounter() { + return unsavedChangesCounter; + } + + public String getParentDirectory() { + return parentDirectory; + } + + public String getName() { + return name; + } + public int size() { + return size; + } + + public int getChangesCount() { + return unsavedChangesCounter; + } + + public SomeTable(String dir, String name) { + this.parentDirectory = dir; + this.name = name; + unchangedOldData = new HashMap(); + currentData = new HashMap(); + deletedKeys = new HashSet(); + unsavedChangesCounter = 0; + try { + load(); + } catch (SomethingIsWrongException e) { + if (!e.getMessage().equals("Unable to scan from disc.") && !e.getMessage().equals("Empty file")) { + System.err.println("Error aqcuired while opening a table. Message: " + e.getMessage()); + } + } + + } + + public String get(String key) { + if (key == null) { + throw new IllegalArgumentException("null table cannot exist"); + } + if (currentData.containsKey(key)) { + return currentData.get(key); + } else if (deletedKeys.contains(key)) { + return null; + } + return unchangedOldData.get(key); + } + + public String put(String key, String newValue) { + if (key == null || newValue == null) { + throw new IllegalArgumentException("cannot put null values"); + } + String value = oldValue(key); + currentData.put(key, newValue); + if (value == null) { + ++size; + } + ++unsavedChangesCounter; + return value; + } + + public String remove(String key) { + String value = oldValue(key); + if (currentData.containsKey(key)) { + currentData.remove(key); + if (unchangedOldData.containsKey(key)) { + deletedKeys.add(key); + } + } else { + deletedKeys.add(key); + } + if (value != null) { + --size; + } + ++unsavedChangesCounter; + return value; + } + + private static String oldValue(String key) { + String oldValue = currentData.get(key); + if (oldValue == null && !deletedKeys.contains(key)) { + oldValue = unchangedOldData.get(key); + } + return oldValue; + } + + private int keySize(Set keys) { + int keysSize = 0; + for (String key : keys) { + keysSize += UtilMethods.countBytes(key, UtilMethods.ENCODING) + 5; + } + return keysSize; + } + + public void writeOnDisk(Set keys, String file) throws SomethingIsWrongException { + WritingUtils write = new WritingUtils(file); + int temp = keySize(keys); + for (String key : keys) { + write.writeKey(key); + write.writeOffset(temp); + temp += UtilMethods.countBytes(unchangedOldData.get(key), UtilMethods.ENCODING); + } + for (String key : keys) { + String tempCheck = unchangedOldData.get(key); + if (tempCheck != null) { + write.writeValue(tempCheck); + } + } + UtilMethods.closeCalm(write.dataFile); + } + + public void scanFromDisk(String file) throws SomethingIsWrongException { + if (!UtilMethods.doesExist(file)) { + throw new SomethingIsWrongException("Unable to scan from disc."); + } + ReadingUtils read = new ReadingUtils(file); + while (!read.endOfFile()) { + String key = read.readKey(); + String value = read.readValue(); + unchangedOldData.put(key, value); + } + UtilMethods.closeCalm(read.tempFile); + } + + public int rollback() { + int deletedOrAdded = Math.abs(unchangedOldData.size() - size); + deletedKeys.clear(); + currentData.clear(); + size = unchangedOldData.size(); + unsavedChangesCounter = 0; + return deletedOrAdded; + } + + public void clear() { + unchangedOldData.clear(); + currentData.clear(); + deletedKeys.clear(); + unsavedChangesCounter = 0; + size = 0; + } + + public int commit() { + int commitCount = Math.abs(unchangedOldData.size() - size); + for (final String toDelete : deletedKeys) { + unchangedOldData.remove(toDelete); + } + for (String toAdd : currentData.keySet()) { + unchangedOldData.put(toAdd, currentData.get(toAdd)); + } + size = unchangedOldData.size(); + try { + save(); + } catch (SomethingIsWrongException e) { + System.out.println("Error aqcuired while commiting changes. Message: " + e.getMessage()); + } + unsavedChangesCounter = 0; + deletedKeys.clear(); + currentData.clear(); + return commitCount; + } + + + +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/Table.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/Table.java new file mode 100644 index 000000000..906f8fc86 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/Table.java @@ -0,0 +1,11 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap; + +public interface Table extends ru.fizteh.fivt.storage.strings.Table { + String getName(); + String get(String a); + String put(String a, String b); + String remove(String a); + int size(); + int commit(); + int rollback(); +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/TableBuilder.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/TableBuilder.java new file mode 100644 index 000000000..d2aab8f10 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/TableBuilder.java @@ -0,0 +1,16 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap; + +import java.io.File; +import java.util.Set; + +public interface TableBuilder { + String get(String key); + + void put(String key, String value); + + Set getKeys(); + + File getTableDirectory(); + + void setCurrentFile(File currentFile); +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/TableUsingStrings.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/TableUsingStrings.java new file mode 100644 index 000000000..6cda82f4d --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/TableUsingStrings.java @@ -0,0 +1,32 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap; + +public abstract class TableUsingStrings extends SomeStorage implements MyTable { + protected TableUsingStrings(String dir, String name) { + super(dir, name); + } + + public String remove(String key) { + return removeFromStorage(key); + } + + public int size() { + return sizeOfStorage(); + } + + public String get(String key) { + return getFromStorage(key); + } + + public String put(String key, String value) { + return putIntoStorage(key, value); + } + + public int rollback() { + return rollbackStorage(); + } + + public int commit() { + return commitStorage(); + } + +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/WritingUtils.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/WritingUtils.java new file mode 100644 index 000000000..64fbb5e57 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/filemap/WritingUtils.java @@ -0,0 +1,58 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap; + + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell.*; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.io.UnsupportedEncodingException; + + + +public class WritingUtils { + + protected RandomAccessFile dataFile; + public WritingUtils(String filePath) throws SomethingIsWrongException { + try { + dataFile = new RandomAccessFile(filePath, "rw"); + } catch (FileNotFoundException e) { + throw new SomethingIsWrongException(String.format("error while creating file: '%s'", filePath)); + } + try { + dataFile.setLength(0); + } catch (IOException e) { + throw new SomethingIsWrongException("Error aqcuired while resizing a file " + e.getMessage()); + } + } + + public void writeKey(String key) throws SomethingIsWrongException { + byte[] bytes = UtilMethods.getBytes(key, UtilMethods.ENCODING); + try { + dataFile.write(bytes); + dataFile.writeByte(0); + } catch (IOException e) { + throw new SomethingIsWrongException("Error acquired while writing to file: " + e.getMessage()); + } + } + + public void writeOffset(int offset) throws SomethingIsWrongException { + try { + dataFile.writeInt(offset); + } catch (IOException e) { + throw new SomethingIsWrongException("Error acquired while writing to file: " + e.getMessage()); + } + } + + public void writeValue(String value) throws SomethingIsWrongException { + try { + byte[] bytes = value.getBytes(UtilMethods.ENCODING); + dataFile.write(bytes); + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException("Unrecognized encoding"); + } catch (IOException e) { + throw new SomethingIsWrongException("Error acquired while writing to file: " + e.getMessage()); + } + } + +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/CreateCommand.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/CreateCommand.java new file mode 100644 index 000000000..844679367 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/CreateCommand.java @@ -0,0 +1,24 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.multifilemap; + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell.*; + + +public class CreateCommand implements Commands { + public String getCommandName() { + return "create"; + } + + public int getArgumentQuantity() { + return 1; + } + + public void implement(String[] args, MultiFileMapShellState state) throws SomethingIsWrongException { + try { + state.tableProvider.createTable(args[0]); + System.out.println("created"); + } catch (Exception e) { + throw new SomethingIsWrongException(e.getMessage()); + } + } + +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/Database.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/Database.java new file mode 100644 index 000000000..17e80a54e --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/Database.java @@ -0,0 +1,79 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.multifilemap; + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell.*; + +import java.io.File; +import java.util.HashMap; +import java.util.Map.Entry; + +public class Database implements TableProvider { + HashMap content = new HashMap(); + private String databaseDirectoryPath; + + public Database(String databaseDirectoryPath) { + this.databaseDirectoryPath = databaseDirectoryPath; + File databaseDirectory = new File(databaseDirectoryPath); + //if (databaseDirectory.getUsableSpace() != 0) { + for (File tableFile : databaseDirectory.listFiles()) { + if (tableFile != null || tableFile.isFile()) { + continue; + } + MultifileTable table = new MultifileTable(databaseDirectoryPath, tableFile.getName()); + content.put(table.getName(), table); + } + //} + } + + public MultifileTable getTable(String name) { + if (name == null) { + throw new IllegalArgumentException("Table's name cannot be null"); + } + MultifileTable table = content.get(name); + + if (table == null) { + return null; + } + if (table.getChangesCounter() > 0) { + System.out.println("There are " + table.getChangesCounter() + " uncommited changes."); + } + + return table; + } + + public MultifileTable createTable(String name) { + if (name == null) { + throw new IllegalArgumentException("Table's name cannot be null"); + } + if (content.containsKey(name)) { + return null; + } + MultifileTable table = new MultifileTable(databaseDirectoryPath, name); + content.put(name, table); + return table; + } + + public void removeTable(String name) { + if (name == null) { + throw new IllegalArgumentException("Table's name cannot be null"); + } + + if (!content.containsKey(name)) { + throw new IllegalStateException("Table doesn't exist"); + } + MultifileTable table = content.get(name); + table.clear(); + table.commit(); + content.remove(name); + + File tableFile = new File(databaseDirectoryPath, name); + tableFile.delete(); + } + + public HashMap showTables() { + HashMap result = new HashMap(); + for (Entry contents : content.entrySet()) { + result.put(contents.getKey(), contents.getValue().size()); + } + return result; + } +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/DatabaseFactory.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/DatabaseFactory.java new file mode 100644 index 000000000..d9cf3bbdc --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/DatabaseFactory.java @@ -0,0 +1,16 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.multifilemap; + + +import java.io.File; +import ru.fizteh.fivt.storage.strings.TableProvider; +import ru.fizteh.fivt.storage.strings.TableProviderFactory; + +public class DatabaseFactory implements TableProviderFactory { + public TableProvider create(String directory) { + File databaseDirectory = new File(directory); + if (!databaseDirectory.exists()) { + databaseDirectory.mkdir(); + } + return new Database(databaseDirectory.getAbsolutePath()); + } +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/DatabaseTest.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/DatabaseTest.java new file mode 100644 index 000000000..469ac502c --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/DatabaseTest.java @@ -0,0 +1,89 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.multifilemap; + + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell.SomethingIsWrongException; +import ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap.MyTable; + +public class DatabaseTest { + + DatabaseFactory factory; + ru.fizteh.fivt.storage.strings.TableProvider provider; + + @Before + public void beforeTest() throws SomethingIsWrongException { + factory = new DatabaseFactory(); + provider = factory.create("C:\\temp\\database_test"); + provider.createTable("table1"); + provider.createTable("table2"); + } + + @Test + public void testCreateTable() throws Exception { + // non-existing tables + Assert.assertNotNull(provider.createTable("newtable1")); + Assert.assertNotNull(provider.createTable("newtable2")); + // existing tables + Assert.assertNull(provider.createTable("table1")); + Assert.assertNull(provider.createTable("table2")); + + // clean-up + provider.removeTable("newtable1"); + provider.removeTable("newtable2"); + } + + @Test + public void testGetTable() throws Exception { + // non-existing tables + Assert.assertNull(provider.getTable("nonexistingtable")); + Assert.assertNull(provider.getTable("thereisnosuchtable")); + // existing tables + Assert.assertNotNull(provider.getTable("table1")); + Assert.assertNotNull(provider.getTable("table2")); + + MyTable table1 = (MyTable) provider.getTable("table1"); + Assert.assertEquals(table1, provider.getTable("table1")); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetTableExceptions() throws SomethingIsWrongException { + provider.getTable(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateTableExceptions() throws SomethingIsWrongException { + provider.createTable(null); + } + + @Test + public void testRemoveTable() throws Exception { + //prepare + provider.createTable("newtable1"); + provider.createTable("newtable2"); + + // existing tables + provider.removeTable("newtable1"); + provider.removeTable("newtable2"); + } + + @Test(expected = IllegalArgumentException.class) + public void testRemoveTableIllegalArgumentException() throws SomethingIsWrongException { + provider.removeTable(null); + } + + @Test(expected = IllegalStateException.class) + public void testRemoveTableIllegalStateException() throws SomethingIsWrongException { + provider.removeTable("nonexistingtable"); + provider.removeTable("nosuchtable"); + } + + @After + public void testAfter() throws SomethingIsWrongException { + provider.removeTable("table1"); + provider.removeTable("table2"); + } +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/DropCommand.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/DropCommand.java new file mode 100644 index 000000000..838809b03 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/DropCommand.java @@ -0,0 +1,24 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.multifilemap; + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell.*; + + +public class DropCommand implements Commands { + public String getCommandName() { + return "drop"; + } + + public int getArgumentQuantity() { + return 1; + } + + public void implement(String[] args, MultiFileMapShellState state) throws SomethingIsWrongException { + try { + state.tableProvider.removeTable(args[0]); + System.out.println("removed"); + } catch (Exception e) { + throw new SomethingIsWrongException(e.getMessage()); + } + } + +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/MultiFileMapReadingUtils.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/MultiFileMapReadingUtils.java new file mode 100644 index 000000000..6189f5a5a --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/MultiFileMapReadingUtils.java @@ -0,0 +1,34 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.multifilemap; + +import java.io.File; +import java.io.IOException; + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap.FileMapReadingUtils; +import ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap.TableBuilder; + + +public class MultiFileMapReadingUtils { + public static void load(TableBuilder build) throws IOException { + File tableDir = build.getTableDirectory(); + if (tableDir.listFiles() == null) { + return; + } + + for (File dir : tableDir.listFiles()) { + if (dir.isFile()) { + continue; + } + + if (dir.listFiles().length == 0) { + throw new IllegalArgumentException("empty bucket"); + } + + for (File file : dir.listFiles()) { + build.setCurrentFile(file); + FileMapReadingUtils.scanFromDisk(file.getAbsolutePath(), build); + } + } + + } + +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/MultiFileMapShellState.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/MultiFileMapShellState.java new file mode 100644 index 000000000..bb91e0282 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/MultiFileMapShellState.java @@ -0,0 +1,7 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.multifilemap; +import ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap.*; + + +public class MultiFileMapShellState extends FileMapShellState { + public TableProvider tableProvider; +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/MultiFileMapTableBuilder.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/MultiFileMapTableBuilder.java new file mode 100644 index 000000000..92855f74d --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/MultiFileMapTableBuilder.java @@ -0,0 +1,27 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.multifilemap; + +import java.io.File; + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap.GlobalUtils; +import ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap.SimpleTableBuilder; +import ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap.TableUsingStrings; + +public class MultiFileMapTableBuilder extends SimpleTableBuilder { + private int currentDir; + private int currentFile; + + + public MultiFileMapTableBuilder(TableUsingStrings table) { + super(table); + } + + public void setCurrentFile(File file) { + currentDir = GlobalUtils.parseDirNumber(file.getParentFile()); + currentFile = GlobalUtils.parseFileNumber(file); + } + + public void put(String key, String value) { + GlobalUtils.checkKeyPlacement(key, currentDir, currentFile); + super.put(key, value); + } +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/MultiFileMapWritingUtils.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/MultiFileMapWritingUtils.java new file mode 100644 index 000000000..893d78491 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/MultiFileMapWritingUtils.java @@ -0,0 +1,57 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.multifilemap; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap.FileMapWritingUtils; +import ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap.GlobalUtils; +import ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap.TableBuilder; + +public class MultiFileMapWritingUtils { + + public static void save(TableBuilder build) throws IOException { + File tableDir = build.getTableDirectory(); + if (tableDir.listFiles() == null) { + return; + } + ArrayList> toSave = new ArrayList>(); + boolean dirIsEmpty; + + for (int dirNumber = 0; dirNumber < GlobalUtils.DIR_QUANTITY; ++dirNumber) { + toSave.clear(); + for (int fileNumber = 0; fileNumber < GlobalUtils.FILES_PER_DIR; ++fileNumber) { + toSave.add(new HashSet()); + } + dirIsEmpty = true; + + for (String key : build.getKeys()) { + if (GlobalUtils.getDirNumber(key) == dirNumber) { + int fileNumber = GlobalUtils.getFileNumber(key); + toSave.get(fileNumber).add(key); + dirIsEmpty = false; + } + } + String dirName = dirNumber + ".dir"; + File dir = new File(tableDir, dirName); + if (dirIsEmpty) { + GlobalUtils.deleteFile(dir); + } + for (int fileNumber = 0; fileNumber < GlobalUtils.FILES_PER_DIR; ++fileNumber) { + String fileName = fileNumber + ".dat"; + File file = new File(dir, fileName); + if (toSave.get(fileNumber).isEmpty()) { + GlobalUtils.deleteFile(file); + continue; + } + if (!dir.exists()) { + dir.mkdir(); + } + FileMapWritingUtils.writeOnDisk(toSave.get(fileNumber), + file.getAbsolutePath(), build); + } + } + } +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/MultifileMapMain.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/MultifileMapMain.java new file mode 100644 index 000000000..17b72ed7b --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/MultifileMapMain.java @@ -0,0 +1,40 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.multifilemap; + +import java.util.ArrayList; +import java.util.HashSet; + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell.*; +import ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap.*; +import ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell.Shell; + +public class MultifileMapMain { + public static void main(String[] args) { + HashSet> com = new HashSet>() + { { add(new ExitCommand()); add(new RollbackCommand()); add(new CommitCommand()); + add(new PutCommand()); add(new GetCommand()); add(new RemoveKeyCommand()); + add(new ListCommand()); }}; + HashSet> com1 = new HashSet>() + { { add(new DropCommand()); add(new UseCommand()); add(new CreateCommand()); + add(new ShowTablesCommand()); }}; + ArrayList res = new ArrayList(); + res.addAll(com); + res.addAll(com1); + HashSet actualResult = new HashSet(res); + Shell shell = new Shell(actualResult); + try { + String dbDirectory = System.getProperty("fizteh.db.dir"); + if (dbDirectory == null) { + System.err.println("error: nope. Gimme something."); + System.exit(-2); + } + MultiFileMapShellState state = new MultiFileMapShellState(); + DatabaseFactory factory = new DatabaseFactory(); + state.tableProvider = (TableProvider) factory.create(dbDirectory); + shell.setShellState(state); + } catch (IllegalArgumentException e) { + System.err.println("error: " + e.getMessage()); + System.exit(-1); + } + shell.run(args, shell); + } +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/MultifileMapShellStateInterface.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/MultifileMapShellStateInterface.java new file mode 100644 index 000000000..b12290b99 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/MultifileMapShellStateInterface.java @@ -0,0 +1,16 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.multifilemap; + +import java.io.IOException; + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap.FileMapShellStateInterface; + +interface MultifileMapShellStateInterface + extends FileMapShellStateInterface { + Table useTable(String name); + + Table createTable(String arguments); + + void dropTable(String name) throws IOException; + + String getCurrentTableName(); +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/MultifileTable.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/MultifileTable.java new file mode 100644 index 000000000..0c00442ab --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/MultifileTable.java @@ -0,0 +1,99 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.multifilemap; + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap.*; +import ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell.*; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +public class MultifileTable extends SomeTable { + + private static final int DIR_QUANTITY = 16; + private static final int FILES_PER_DIR = 16; + + public MultifileTable(String directory, String tableName) { + super(directory, tableName); + } + + protected void save() { + File tableDirectory = getTableDirectory(); + ArrayList> keysToSave = new ArrayList>(); + boolean dirIsEmpty; + for (int dirNumber = 0; dirNumber < DIR_QUANTITY; ++dirNumber) { + keysToSave.clear(); + for (int index = 0; index < FILES_PER_DIR; ++index) { + keysToSave.add(new HashSet()); + } + dirIsEmpty = true; + try { + for (final String key : unchangedOldData.keySet()) { + if (getDirNumber(key) == dirNumber) { + int fileNumber = getFileNumber(key); + keysToSave.get(fileNumber).add(key); + dirIsEmpty = false; + } + } + } catch (SomethingIsWrongException e) { + e.printStackTrace(); + } + String dirName = String.format("%d.dir", dirNumber); + File directory = new File(tableDirectory, dirName); + if (dirIsEmpty) { + directory.delete(); + } + for (int fileNumber = 0; fileNumber < FILES_PER_DIR; ++fileNumber) { + String fileName = String.format("%d.dat", fileNumber); + File file = new File(directory, fileName); + if (keysToSave.get(fileNumber).isEmpty()) { + file.delete(); + continue; + } + if (!directory.exists()) { + directory.mkdir(); + } + try { + writeOnDisk(keysToSave.get(fileNumber), file.getAbsolutePath()); + } catch (SomethingIsWrongException e) { + System.out.println("Error acquired while writing into a table. Message: " + e.getMessage()); + } + } + } + } + + + protected void load() { + File tableDirectory = getTableDirectory(); + for (final File backet: tableDirectory.listFiles()) { + for (final File file: backet.listFiles()) { + try { + scanFromDisk(file.getAbsolutePath()); + } catch (SomethingIsWrongException e) { + System.out.println("Error acquired while reading out of table. Message: " + + e.getMessage()); + } + } + } + } + + private File getTableDirectory() { + File curDir = new File(new File(".").getAbsolutePath()); + String temp = curDir.getAbsolutePath(); + File tableDirectory = new File(temp, getName()); + if (!tableDirectory.exists()) { + tableDirectory.mkdir(); + } + return tableDirectory; + } + + private int getDirNumber(String key) throws SomethingIsWrongException { + byte[] bytes = UtilMethods.getBytes(key, UtilMethods.ENCODING); + return bytes[0] % DIR_QUANTITY; + } + + private int getFileNumber(String key) throws SomethingIsWrongException { + byte[] bytes = UtilMethods.getBytes(key, UtilMethods.ENCODING); + return bytes[0] / DIR_QUANTITY % FILES_PER_DIR; + } +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/MultifileTableTest.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/MultifileTableTest.java new file mode 100644 index 000000000..c4ce2af54 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/MultifileTableTest.java @@ -0,0 +1,172 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.multifilemap; + +import java.util.Random; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import ru.fizteh.fivt.storage.strings.Table; +import ru.fizteh.fivt.storage.strings.TableProvider; +import ru.fizteh.fivt.storage.strings.TableProviderFactory; + +public class MultifileTableTest { + + private static final int KEYS_COUNT = 20; + private static final String TABLE_NAME = "testtable"; + Table currentTable; + + TableProviderFactory factory = new DatabaseFactory(); + TableProvider provider = factory.create("C:\\temp\\database_test"); + + + @Before + public void gettingReady() throws Exception { + currentTable = provider.createTable(TABLE_NAME); + for (int index = 0; index < KEYS_COUNT; ++index) { + String key = String.format("key%d", index); + String value = String.format("value%d", index); + currentTable.put(key, value); + } + } + + @Test + public void testForNewData() { + // new data + for (int index = 21; index < KEYS_COUNT + 21; ++index) { + String newKey = String.format("new_key%d", index); + String newValue = String.format("new_value%d", index); + Assert.assertNull(currentTable.put(newKey, newValue)); + } + } + + @Test + public void testForExistingData() { + // existing data + for (int index = 0; index < KEYS_COUNT; ++index) { + String expectedValue = String.format("value%d", index); + String key = String.format("key%d", index); + Assert.assertEquals(expectedValue, currentTable.get(key)); + } + } + + @Test + public void testForUnexistingData() { + // non-existing data + Random random = new Random(); + for (int index = 0; index < KEYS_COUNT; ++index) { + String key = String.format("k%d", random.nextInt(100)); + Assert.assertNull(currentTable.get(key)); + } + } + + @Test + public void testForReplaceData() { + // replacing + for (int index = 0; index < KEYS_COUNT; ++index) { + String key = String.format("key%d", index); + String oldValue = String.format("value%d", index); + String newValue = String.format("new_value%d", index); + Assert.assertEquals(oldValue, currentTable.put(key, newValue)); + } + } + + @Test + public void testCommit() { + int committed = currentTable.commit(); + Assert.assertEquals(KEYS_COUNT, committed); + + for (int index = 0; index < 2 * KEYS_COUNT; ++index) { + String key = String.format("key%d", index); + String value = String.format("value%d", index); + currentTable.put(key, value); + } + + Assert.assertEquals(KEYS_COUNT, currentTable.commit()); + + for (int index = 0; index < 2 * KEYS_COUNT; ++index) { + String key = String.format("key%d", index); + Assert.assertNotNull(currentTable.get(key)); + } + } + + @Test + public void testRollback() { + Assert.assertEquals(KEYS_COUNT, currentTable.rollback()); + + for (int index = 0; index < 2 * KEYS_COUNT; ++index) { + String key = String.format("key%d", index); + String value = String.format("value%d", index); + currentTable.put(key, value); + } + + Assert.assertEquals(2 * KEYS_COUNT, currentTable.rollback()); + + for (int index = 0; index < 2 * KEYS_COUNT; ++index) { + String key = String.format("key%d", index); + Assert.assertNull(currentTable.get(key)); + } + } + + @Test + public void testSize() { + Assert.assertEquals(KEYS_COUNT, currentTable.size()); + } + + @Test + public void testGetName() { + Assert.assertEquals(TABLE_NAME, currentTable.getName()); + } + + @Test(expected = IllegalArgumentException.class) + public void testTableExceptions() { + // get + currentTable.get(null); + + // storagePut + currentTable.put(null, "value"); + currentTable.put("key", null); + + // storageRemove + currentTable.remove(null); + } + + @Test + public void testRollbackAndCommit() { + for (int index = 0; index < KEYS_COUNT; ++index) { + String key = String.format("key%d", index); + String value = String.format("value%d", index); + currentTable.put(key, value); + } + currentTable.commit(); + for (int index = 0; index < KEYS_COUNT; ++index) { + String key = String.format("key%d", index); + currentTable.remove(key); + } + for (int index = 0; index < KEYS_COUNT; ++index) { + String key = String.format("key%d", index); + String value = String.format("value%d", index); + currentTable.put(key, value); + } + Assert.assertEquals(0, currentTable.rollback()); + + currentTable.remove("non-exists"); + currentTable.remove("non-exists1"); + currentTable.remove("key1"); + currentTable.put("key1", "value1"); + Assert.assertEquals(0, currentTable.rollback()); + + currentTable.put("key1", "value1"); + currentTable.commit(); + currentTable.remove("key1"); + currentTable.put("key1", "value1"); + Assert.assertEquals(0, currentTable.rollback()); + } + + @After + public void cleaningUp() throws Exception { + provider.removeTable(TABLE_NAME); + } + +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/ShowTablesCommand.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/ShowTablesCommand.java new file mode 100644 index 000000000..dccabd5cb --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/ShowTablesCommand.java @@ -0,0 +1,28 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.multifilemap; + + + +import java.util.HashMap; +import java.util.Map.Entry; + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell.*; + +public class ShowTablesCommand implements Commands { + public String getCommandName() { + return "show"; + } + + public int getArgumentQuantity() { + return 1; + } + + public void implement(String[] args, MultiFileMapShellState state) throws SomethingIsWrongException { + if (!args[0].equals("tables")) { + throw new SomethingIsWrongException("no command with this name"); + } + HashMap tables = state.tableProvider.showTables(); + for (Entry iterator : tables.entrySet()) { + System.out.println(iterator.getKey() + ' ' + iterator.getValue()); + } + } +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/TableProvider.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/TableProvider.java new file mode 100644 index 000000000..39a9665d8 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/TableProvider.java @@ -0,0 +1,12 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.multifilemap; + +import java.util.HashMap; + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.filemap.*; + +public interface TableProvider extends ru.fizteh.fivt.storage.strings.TableProvider{ + Table getTable(String a); + Table createTable(String a); + void removeTable(String a); + HashMap showTables(); +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/TableProviderFactory.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/TableProviderFactory.java new file mode 100644 index 000000000..497db6f32 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/TableProviderFactory.java @@ -0,0 +1,5 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.multifilemap; + +public interface TableProviderFactory { + TableProvider create(String dir); +} diff --git a/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/UseCommand.java b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/UseCommand.java new file mode 100644 index 000000000..b59a68979 --- /dev/null +++ b/src/ru/fizteh/fivt/students/NikolaiKrivchanskii/multifilemap/UseCommand.java @@ -0,0 +1,24 @@ +package ru.fizteh.fivt.students.NikolaiKrivchanskii.multifilemap; + + + +import ru.fizteh.fivt.students.NikolaiKrivchanskii.Shell.*; + +public class UseCommand implements Commands { + public String getCommandName() { + return "use"; + } + + public int getArgumentQuantity() { + return 1; + } + + public void implement(String[] args, MultiFileMapShellState state) throws SomethingIsWrongException { + MultifileTable oldOne = (MultifileTable) state.table; + if (oldOne != null && oldOne.getChangesCounter() != 0) { + System.out.println(oldOne.getChangesCounter() + " unsaved changes"); + } + state.table = state.tableProvider.getTable(args[0]); + System.out.println("using table " + state.table.getName()); + } +}