diff --git a/homework-g599-lantsetov/pom.xml b/homework-g599-lantsetov/pom.xml
new file mode 100644
index 000000000..904c44839
--- /dev/null
+++ b/homework-g599-lantsetov/pom.xml
@@ -0,0 +1,64 @@
+
+
+
+ mipt-java-2016
+ ru.mipt.java2016
+ 1.0.0
+
+ 4.0.0
+
+ homework-g599-lantsetov
+
+ 1.4.2.RELEASE
+
+
+
+
+ ru.mipt.java2016
+ homework-base
+ 1.0.0
+
+
+
+ net.sourceforge.jeval
+ jeval
+ 0.9.4
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ ${spring.boot.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+ ${spring.boot.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+ ${spring.boot.version}
+
+
+ com.zaxxer
+ HikariCP
+ 2.5.1
+
+
+ com.h2database
+ h2
+ 1.4.193
+
+
+
+ ru.mipt.java2016
+ homework-tests
+ 1.0.0
+ test
+
+
+
+
\ No newline at end of file
diff --git a/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task1/MyCalculator.java b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task1/MyCalculator.java
new file mode 100644
index 000000000..0f4192248
--- /dev/null
+++ b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task1/MyCalculator.java
@@ -0,0 +1,193 @@
+package ru.mipt.java2016.homework.g599.lantsetov.task1;
+
+import ru.mipt.java2016.homework.base.task1.Calculator;
+import ru.mipt.java2016.homework.base.task1.ParsingException;
+
+
+import java.util.Stack;
+import java.util.Arrays;
+import java.util.Scanner;
+import java.util.HashSet;
+import java.security.InvalidParameterException;
+
+
+class MyCalculator implements Calculator {
+ static final Calculator INSTANCE = new MyCalculator();
+ private static final HashSet OPERATORS = new HashSet<>(Arrays.asList("+", "-", "*", "/"));
+
+ @Override
+ public double calculate(String expression) throws ParsingException, ArithmeticException {
+ if (expression == null) {
+ throw new ParsingException("Null string");
+ }
+ String rpn = buildRPN(expression);
+ return eval(rpn);
+ }
+
+ private static double count(String operator, double op1, double op2) throws InvalidParameterException {
+ switch (operator) {
+ case "+": return op1 + op2;
+ case "-": return op1 - op2;
+ case "*": return op1 * op2;
+ case "/": return op1 / op2;
+ default: return 0;
+ }
+ }
+
+ private static int getPriority(String operator) throws InvalidParameterException {
+ switch (operator) {
+ case "+": return 1;
+ case "-": return 1;
+ case "*": return 2;
+ case "/": return 2;
+ case "(": return 0;
+ case ")": return 0;
+ case "#": return 3;
+ default: return -1;
+ }
+ }
+
+ private double eval(String reverseNotation) throws ParsingException {
+ Stack operands = new Stack<>();
+ Double a;
+ Double b;
+ Double result = 0.0;
+ String token;
+ try (Scanner sc = new Scanner(reverseNotation)) {
+ while (sc.hasNext()) {
+ token = sc.next();
+ if (OPERATORS.contains(token)) {
+ if (operands.size() < 2) {
+ throw new ParsingException("Incorrect expression");
+ }
+ b = operands.pop();
+ a = operands.pop();
+ try {
+ result = count(token, a, b);
+ } finally {
+ operands.push(result);
+ }
+ } else if (token.equals("#")) {
+ if (operands.size() < 1) {
+ throw new ParsingException("Invalid expression: expected number");
+ } else {
+ double operand = operands.pop();
+ operands.push(-1 * operand);
+ }
+ } else {
+ try {
+ a = Double.parseDouble(token);
+ operands.push(a);
+ } catch (NumberFormatException npe) {
+ throw new ParsingException(String.format("Invalid operand: %s", token));
+ }
+ }
+ }
+ if (operands.size() != 1) {
+ throw new ParsingException("Incorrect expression");
+ } else {
+ return operands.pop();
+ }
+ }
+ }
+
+ private String buildRPN(String expression) throws ParsingException {
+ StringBuilder answer = new StringBuilder();
+ Stack operators = new Stack<>();
+ Character c;
+ boolean unary = true;
+ boolean prevIsDot = false;
+ String prevUnary = "";
+ int searchCondition = 0;
+ for (int i = 0; i < expression.length(); ++i) {
+ c = expression.charAt(i);
+ if (prevIsDot && !(Character.isDigit(c))) {
+ throw new ParsingException("Invalid expression: unexpected token after dot");
+ }
+ if (Character.isDigit(c)) {
+ unary = false;
+ answer.append(c);
+ searchCondition = 1;
+ prevIsDot = false;
+ } else if (Character.isWhitespace(c)) {
+ answer.append(' ');
+ } else if (c.equals('.')) {
+ if (searchCondition == 0) {
+ throw new ParsingException("Invalid expression: unexpected symbol '.'");
+ } else {
+ answer.append('.');
+ searchCondition = 1;
+ }
+ prevIsDot = true;
+ } else if (c.equals('(')) {
+ if (searchCondition == 1) {
+ throw new ParsingException("Invalid expression: unexpected symbol '('");
+ } else {
+ unary = true;
+ answer.append(' ');
+ operators.push("(");
+ searchCondition = 0;
+ }
+ prevIsDot = false;
+ } else if (c.equals(')')) {
+ if (searchCondition == 0) {
+ throw new ParsingException("Invalid expression: unexpected ) after operator");
+ } else {
+ boolean hasCorrespondingOpeningBracket = false;
+ while (!(operators.empty())) {
+ String curOperator = operators.pop();
+ if (curOperator.equals("(")) {
+ hasCorrespondingOpeningBracket = true;
+ break;
+ } else {
+ answer.append(' ').append(curOperator).append(' ');
+ }
+ }
+ if (!hasCorrespondingOpeningBracket) {
+ throw new ParsingException("Invalid expression: invalid brackets");
+ }
+
+ }
+ prevIsDot = false;
+ } else if (OPERATORS.contains(c.toString())) {
+ if (unary) {
+ switch (c.toString()) {
+ case "-":
+ operators.push("#");
+ case "+":
+ if (prevUnary.equals("+")) {
+ throw new ParsingException("Invalid expression: unexpected+");
+ }
+ unary = true;
+ break;
+ default:
+ throw new ParsingException(
+ String.format("Invalid expression: invalid unary operator: %c", c));
+ }
+ prevUnary = c.toString();
+ } else {
+ unary = true;
+ answer.append(' ');
+ int curOperatorPriority = getPriority(c.toString());
+ while (!(operators.empty()) && curOperatorPriority <= getPriority(operators.peek())) {
+ answer.append(' ').append(operators.pop()).append(' ');
+ }
+ operators.push(c.toString());
+ }
+
+ searchCondition = 0;
+ } else {
+ throw new ParsingException(String.format("Invalid expression: unexpected %s", c.toString()));
+ }
+ }
+ while (!operators.empty()) {
+ String curOperator = operators.pop();
+ if (OPERATORS.contains(curOperator) || curOperator == "#") {
+ answer.append(String.format(" %s ", curOperator));
+ } else {
+ throw new ParsingException(String.format("Invalid expression: unexpected %s", curOperator));
+ }
+ }
+ return answer.toString();
+ }
+}
\ No newline at end of file
diff --git a/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task2/DoubleSerializer.java b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task2/DoubleSerializer.java
new file mode 100644
index 000000000..7b5e44bd4
--- /dev/null
+++ b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task2/DoubleSerializer.java
@@ -0,0 +1,17 @@
+package ru.mipt.java2016.homework.g599.lantsetov.task2;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+
+public class DoubleSerializer implements MySerializer {
+ @Override
+ public Double read(RandomAccessFile file) throws IOException {
+ return file.readDouble();
+ }
+
+ @Override
+ public void write(RandomAccessFile file, Double arg) throws IOException {
+ file.writeDouble(arg);
+ }
+}
\ No newline at end of file
diff --git a/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task2/IntegerSerializer.java b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task2/IntegerSerializer.java
new file mode 100644
index 000000000..05c27836e
--- /dev/null
+++ b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task2/IntegerSerializer.java
@@ -0,0 +1,17 @@
+package ru.mipt.java2016.homework.g599.lantsetov.task2;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+
+public class IntegerSerializer implements MySerializer {
+ @Override
+ public Integer read(RandomAccessFile file) throws IOException {
+ return file.readInt();
+ }
+
+ @Override
+ public void write(RandomAccessFile file, Integer arg) throws IOException {
+ file.writeInt(arg);
+ }
+}
\ No newline at end of file
diff --git a/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task2/MyKeyValueStorage.java b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task2/MyKeyValueStorage.java
new file mode 100644
index 000000000..4c079e0b5
--- /dev/null
+++ b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task2/MyKeyValueStorage.java
@@ -0,0 +1,118 @@
+package ru.mipt.java2016.homework.g599.lantsetov.task2;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.File;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.io.RandomAccessFile;
+
+import ru.mipt.java2016.homework.base.task2.KeyValueStorage;
+
+public class MyKeyValueStorage implements KeyValueStorage {
+ private RandomAccessFile file;
+ private File flag;
+ private MySerializer keySerializer;
+ private MySerializer valueSerializer;
+ private Map dataBase;
+
+ public MyKeyValueStorage(String path, MySerializer keySerializerArg,
+ MySerializer valueSerializerArg) throws IOException {
+ flag = Paths.get(path, "flag").toFile();
+ if (!flag.createNewFile()) {
+ throw new RuntimeException("File has already been opened");
+ }
+
+ keySerializer = keySerializerArg;
+ valueSerializer = valueSerializerArg;
+ dataBase = new HashMap<>();
+ File pathToFile = Paths.get(path, "storage.db").toFile();
+
+ try {
+ pathToFile.createNewFile();
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot create the file");
+ }
+ try {
+ file = new RandomAccessFile(pathToFile, "rw");
+ dataBase = readMapFromFile();
+ } catch (FileNotFoundException e) {
+ throw new IOException("File not found");
+ }
+ }
+
+ @Override
+ public V read(K key) {
+ checkState();
+ return dataBase.get(key);
+ }
+
+ @Override
+ public boolean exists(K key) {
+ checkState();
+ return dataBase.containsKey(key);
+ }
+
+ @Override
+ public void write(K key, V value) {
+ checkState();
+ dataBase.put(key, value);
+ }
+
+ @Override
+ public void delete(K key) {
+ checkState();
+ dataBase.remove(key);
+ }
+
+ @Override
+ public Iterator readKeys() {
+ checkState();
+ return dataBase.keySet().iterator();
+ }
+
+ @Override
+ public int size() {
+ checkState();
+ return dataBase.size();
+ }
+
+ @Override
+ public void close() throws IOException {
+ checkState();
+ saveChanges();
+ dataBase = null;
+ file.close();
+ flag.delete();
+ }
+
+ private void checkState() {
+ if (dataBase == null) {
+ throw new RuntimeException("Already closed");
+ }
+ }
+
+ private Map readMapFromFile() throws IOException {
+ Map bufMap = new HashMap<>();
+ K key;
+ V value;
+ file.seek(0);
+ while (file.getFilePointer() < file.length()) {
+ key = keySerializer.read(file);
+ value = valueSerializer.read(file);
+ bufMap.put(key, value);
+ }
+ return bufMap;
+ }
+
+ private void saveChanges() throws IOException {
+ file.setLength(0);
+ file.seek(0);
+ for (Map.Entry entry : dataBase.entrySet()) {
+ keySerializer.write(file, entry.getKey());
+ valueSerializer.write(file, entry.getValue());
+ }
+ }
+}
\ No newline at end of file
diff --git a/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task2/MySerializer.java b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task2/MySerializer.java
new file mode 100644
index 000000000..e8e02dcbe
--- /dev/null
+++ b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task2/MySerializer.java
@@ -0,0 +1,14 @@
+package ru.mipt.java2016.homework.g599.lantsetov.task2;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * Created by Михаил on 30.10.2016.
+ */
+public interface MySerializer {
+
+ T read(RandomAccessFile file) throws IOException;
+
+ void write(RandomAccessFile file, T arg) throws IOException;
+}
\ No newline at end of file
diff --git a/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task2/StringSerializer.java b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task2/StringSerializer.java
new file mode 100644
index 000000000..f61fe1780
--- /dev/null
+++ b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task2/StringSerializer.java
@@ -0,0 +1,22 @@
+package ru.mipt.java2016.homework.g599.lantsetov.task2;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+
+public class StringSerializer implements MySerializer {
+ @Override
+ public String read(RandomAccessFile file) throws IOException {
+ int length = file.readInt();
+ byte[] bytes = new byte[length];
+ file.readFully(bytes);
+ return new String(bytes);
+ }
+
+ @Override
+ public void write(RandomAccessFile file, String arg) throws IOException {
+ byte[] bytes = arg.getBytes();
+ file.writeInt(bytes.length);
+ file.write(bytes);
+ }
+}
\ No newline at end of file
diff --git a/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task3/MyKeyValueStorage.java b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task3/MyKeyValueStorage.java
new file mode 100644
index 000000000..9f52179e5
--- /dev/null
+++ b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task3/MyKeyValueStorage.java
@@ -0,0 +1,242 @@
+package ru.mipt.java2016.homework.g599.lantsetov.task3;
+
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import ru.mipt.java2016.homework.base.task2.KeyValueStorage;
+import ru.mipt.java2016.homework.g599.lantsetov.task2.MySerializer;
+
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+
+public class MyKeyValueStorage implements KeyValueStorage {
+ private final String pathToStorage;
+ private final MySerializer keySerializer;
+ private final MySerializer valueSerializer;
+ private final ReentrantReadWriteLock lock;
+ private final File flag;
+ private final RandomAccessFile keyOffsetTable;
+ private RandomAccessFile valueTable;
+ private Map dataBase;
+ private int deletedCount;
+ private Map written;
+
+
+ public MyKeyValueStorage(String path, MySerializer keySerializerArg,
+ MySerializer valueSerializerArg) throws IOException {
+ flag = Paths.get(path, "flag").toFile();
+ if (!flag.createNewFile()) {
+ throw new RuntimeException("File has already been opened");
+ }
+
+ lock = new ReentrantReadWriteLock();
+ pathToStorage = path;
+ keySerializer = keySerializerArg;
+ valueSerializer = valueSerializerArg;
+ dataBase = new HashMap<>();
+ written = new HashMap<>();
+ deletedCount = 0;
+ File pathToFile = Paths.get(path, "storage.db").toFile();
+
+ try {
+ valueTable = new RandomAccessFile(pathToFile, "rw");
+ } catch (FileNotFoundException e) {
+ throw new IOException("File not found");
+ }
+
+ pathToFile = Paths.get(path, "index.db").toFile();
+ try {
+ keyOffsetTable = new RandomAccessFile(pathToFile, "rw");
+ dataBase = readMapFromFile();
+ } catch (FileNotFoundException e) {
+ throw new IOException("File not found");
+ }
+ }
+
+ @Override
+ public V read(K key) {
+ lock.writeLock().lock();
+ try {
+ checkState();
+ if (!dataBase.containsKey(key)) {
+ return null;
+ }
+ long offset = dataBase.get(key);
+ if (offset < 0) {
+ return written.get(key);
+ }
+ try {
+ valueTable.seek(offset);
+ return valueSerializer.read(valueTable);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ @Override
+ public boolean exists(K key) {
+ lock.readLock().lock();
+ try {
+ checkState();
+ return dataBase.containsKey(key);
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ @Override
+ public void write(K key, V value) {
+ lock.writeLock().lock();
+ try {
+ checkState();
+ dataBase.put(key, (long) -1);
+ written.put(key, value);
+ if (written.size() >= 100) {
+ try {
+ merge();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ @Override
+ public void delete(K key) {
+ lock.writeLock().lock();
+ try {
+ checkState();
+ if (exists(key)) {
+ deletedCount++;
+ dataBase.remove(key);
+ }
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ @Override
+ public Iterator readKeys() {
+ lock.readLock().lock();
+ try {
+ checkState();
+ return dataBase.keySet().iterator();
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ @Override
+ public int size() {
+ lock.readLock().lock();
+ try {
+ checkState();
+ return dataBase.size();
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ checkState();
+ lock.writeLock().lock();
+ try {
+ if (written.size() != 0) {
+ merge();
+ }
+ if (deletedCount != 0) {
+ rewriteFile();
+ }
+ dataBase = null;
+ written = null;
+ deletedCount = 0;
+ valueTable.close();
+ keyOffsetTable.close();
+ flag.delete();
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ private void checkState() {
+ if (dataBase == null) {
+ throw new RuntimeException("Already closed");
+ }
+ }
+
+ private Map readMapFromFile() throws IOException {
+ Map bufMap = new HashMap<>();
+ K key;
+ long offset;
+ keyOffsetTable.seek(0);
+ while (keyOffsetTable.getFilePointer() < keyOffsetTable.length()) {
+ key = keySerializer.read(keyOffsetTable);
+ offset = keyOffsetTable.readLong();
+ bufMap.put(key, offset);
+ }
+ return bufMap;
+ }
+
+ private void merge() throws IOException {
+ if (deletedCount >= 100) {
+ try {
+ rewriteFile();
+ deletedCount = 0;
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ long offset = valueTable.length();
+ valueTable.seek(offset);
+ keyOffsetTable.seek(keyOffsetTable.length());
+ for (Map.Entry entry : written.entrySet()) {
+ keySerializer.write(keyOffsetTable, entry.getKey());
+ keyOffsetTable.writeLong(offset);
+ dataBase.remove(entry.getKey());
+ dataBase.put(entry.getKey(), offset);
+ valueSerializer.write(valueTable, entry.getValue());
+ offset = valueTable.length();
+ }
+ written.clear();
+ }
+
+ private void rewriteFile() throws IOException {
+ keyOffsetTable.setLength(0);
+ keyOffsetTable.seek(0);
+
+ File pathToFile = Paths.get(pathToStorage, "storageCopy.db").toFile();
+ try (RandomAccessFile bufFile = new RandomAccessFile(pathToFile, "rw")) {
+ bufFile.seek(0);
+ long offset = 0;
+ V bufValue;
+
+ for (Map.Entry entry : dataBase.entrySet()) {
+ if (entry.getValue() >= 0) {
+ keySerializer.write(keyOffsetTable, entry.getKey());
+ keyOffsetTable.writeLong(offset);
+ valueTable.seek(entry.getValue());
+ bufValue = valueSerializer.read(valueTable);
+ valueSerializer.write(bufFile, bufValue);
+ offset += bufFile.length();
+ }
+ }
+
+ valueTable.close();
+ bufFile.close();
+ Files.move(Paths.get(pathToStorage + File.separator + "storageCopy.db"),
+ Paths.get(pathToStorage + File.separator + "storage.db"),
+ REPLACE_EXISTING);
+ valueTable = bufFile;
+ }
+ }
+}
\ No newline at end of file
diff --git a/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task4/BillingDao.java b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task4/BillingDao.java
new file mode 100644
index 000000000..b2d24379b
--- /dev/null
+++ b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task4/BillingDao.java
@@ -0,0 +1,67 @@
+package ru.mipt.java2016.homework.g599.lantsetov.task4;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Repository;
+
+import javax.annotation.PostConstruct;
+import javax.sql.DataSource;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+@Repository
+public class BillingDao {
+ private static final Logger LOG = LoggerFactory.getLogger(BillingDao.class);
+
+ @Autowired
+ private DataSource dataSource;
+
+ private JdbcTemplate jdbcTemplate;
+
+ @PostConstruct
+ public void postConstruct() {
+ jdbcTemplate = new JdbcTemplate(dataSource, false);
+ initSchema();
+ }
+
+ public void initSchema() {
+ LOG.trace("Initializing schema");
+ jdbcTemplate.execute("CREATE SCHEMA IF NOT EXISTS billing");
+ jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS billing.users " +
+ "(username VARCHAR PRIMARY KEY, password VARCHAR, enabled BOOLEAN)");
+ }
+
+
+ public BillingUser loadUser(String username) throws EmptyResultDataAccessException {
+ LOG.trace("Querying for user " + username);
+ return jdbcTemplate.queryForObject(
+ "SELECT username, password, enabled FROM billing.users WHERE username = ?",
+ new Object[]{username},
+ new RowMapper() {
+ @Override
+ public BillingUser mapRow(ResultSet rs, int rowNum) throws SQLException {
+ return new BillingUser(
+ rs.getString("username"),
+ rs.getString("password"),
+ rs.getBoolean("enabled")
+ );
+ }
+ }
+ );
+ }
+
+ public boolean createUser(String username, String password, boolean enabled) {
+ LOG.trace("Creating user " + username);
+ try {
+ loadUser(username);
+ return false;
+ } catch (Exception e) {
+ jdbcTemplate.execute("INSERT INTO billing.users VALUES ('" + username + "', '" + password + "', TRUE)");
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task4/BillingDatabaseConfiguration.java b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task4/BillingDatabaseConfiguration.java
new file mode 100644
index 000000000..8b6c31a01
--- /dev/null
+++ b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task4/BillingDatabaseConfiguration.java
@@ -0,0 +1,26 @@
+package ru.mipt.java2016.homework.g599.lantsetov.task4;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.sql.DataSource;
+
+@Configuration
+public class BillingDatabaseConfiguration {
+ @Bean
+ public DataSource billingDataSource(
+ @Value("${ru.mipt.java2016.homework.g599.lantsetov.task4.jdbcUrl}") String jdbcUrl,
+ @Value("${ru.mipt.java2016.homework.g599.lantsetov.task4.username:}") String username,
+ @Value("${ru.mipt.java2016.homework.g599.lantsetov.task4.password:}") String password
+ ) {
+ HikariConfig config = new HikariConfig();
+ config.setDriverClassName(org.h2.Driver.class.getName());
+ config.setJdbcUrl(jdbcUrl);
+ config.setUsername(username);
+ config.setPassword(password);
+ return new HikariDataSource(config);
+ }
+}
\ No newline at end of file
diff --git a/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task4/BillingUser.java b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task4/BillingUser.java
new file mode 100644
index 000000000..2b509585f
--- /dev/null
+++ b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task4/BillingUser.java
@@ -0,0 +1,69 @@
+package ru.mipt.java2016.homework.g599.lantsetov.task4;
+
+public class BillingUser {
+ private final String username;
+ private final String password;
+ private final boolean enabled;
+
+ public BillingUser(String username, String password, boolean enabled) {
+ if (username == null) {
+ throw new IllegalArgumentException("Null username is not allowed");
+ }
+ if (password == null) {
+ throw new IllegalArgumentException("Null password is not allowed");
+ }
+ this.username = username;
+ this.password = password;
+ this.enabled = enabled;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ @Override
+ public String toString() {
+ return "BillingUser{" +
+ "username='" + username + '\'' +
+ ", password='" + password + '\'' +
+ ", enabled=" + enabled +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ BillingUser that = (BillingUser) o;
+
+ if (enabled != that.enabled) {
+ return false;
+ }
+ if (!username.equals(that.username)) {
+ return false;
+ }
+ return password.equals(that.password);
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = username.hashCode();
+ result = 31 * result + password.hashCode();
+ result = 31 * result + (enabled ? 1 : 0);
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task4/CalculatorController.java b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task4/CalculatorController.java
new file mode 100644
index 000000000..08088c19b
--- /dev/null
+++ b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task4/CalculatorController.java
@@ -0,0 +1,53 @@
+package ru.mipt.java2016.homework.g599.lantsetov.task4;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import ru.mipt.java2016.homework.base.task1.Calculator;
+import ru.mipt.java2016.homework.base.task1.ParsingException;
+
+@RestController
+public class CalculatorController {
+ private static final Logger LOG = LoggerFactory.getLogger(CalculatorController.class);
+ @Autowired
+ private Calculator calculator;
+
+ @RequestMapping(path = "/ping", method = RequestMethod.GET, produces = "text/plain")
+ public String echo() {
+ return "pong\n";
+ }
+
+ @RequestMapping(path = "/", method = RequestMethod.GET, produces = "text/html")
+ public String main(@RequestParam(required = false) String name) {
+ if (name == null) {
+ name = "world";
+ }
+ return "" +
+ "RestCalc" +
+ "Hello, " + name + "!
" +
+ "";
+ }
+
+ @RequestMapping(path = "/eval", method = RequestMethod.POST, consumes = "text/plain", produces = "text/plain")
+ public String eval(@RequestBody String expression) throws ParsingException {
+ LOG.debug("Evaluation request: [" + expression + "]");
+ double result = calculator.calculate(expression);
+ LOG.trace("Result: " + result);
+ return Double.toString(result) + "\n";
+ }
+
+
+ @Autowired
+ private BillingDao billingDao;
+
+ @RequestMapping(path = "/signup/{username}", method = RequestMethod.PUT,
+ consumes = "text/plain", produces = "text/plain")
+ public String signup(@PathVariable String username, @RequestBody String password) {
+ if (billingDao.createUser(username, password, true)) {
+ return "Successful signup";
+ } else {
+ return "Failed signup";
+ }
+ }
+}
\ No newline at end of file
diff --git a/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task4/CalculatorImplementation.java b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task4/CalculatorImplementation.java
new file mode 100644
index 000000000..666be4b6a
--- /dev/null
+++ b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task4/CalculatorImplementation.java
@@ -0,0 +1,191 @@
+package ru.mipt.java2016.homework.g599.lantsetov.task4;
+
+import ru.mipt.java2016.homework.base.task1.Calculator;
+import ru.mipt.java2016.homework.base.task1.ParsingException;
+
+import java.security.InvalidParameterException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Scanner;
+import java.util.Stack;
+
+class CalculatorImplementation implements Calculator {
+ static final Calculator INSTANCE = new CalculatorImplementation();
+ private static final HashSet OPERATORS = new HashSet<>(Arrays.asList("+", "-", "*", "/"));
+
+ @Override
+ public double calculate(String expression) throws ParsingException, ArithmeticException {
+ if (expression == null) {
+ throw new ParsingException("Null string");
+ }
+ String rpn = buildRPN(expression);
+ return eval(rpn);
+ }
+
+ private static double count(String operator, double op1, double op2) throws InvalidParameterException {
+ switch (operator) {
+ case "+": return op1 + op2;
+ case "-": return op1 - op2;
+ case "*": return op1 * op2;
+ case "/": return op1 / op2;
+ default: return 0;
+ }
+ }
+
+ private static int getPriority(String operator) throws InvalidParameterException {
+ switch (operator) {
+ case "+": return 1;
+ case "-": return 1;
+ case "*": return 2;
+ case "/": return 2;
+ case "(": return 0;
+ case ")": return 0;
+ case "#": return 3;
+ default: return -1;
+ }
+ }
+
+ private double eval(String reverseNotation) throws ParsingException {
+ Stack operands = new Stack<>();
+ Double a;
+ Double b;
+ Double result = 0.0;
+ String token;
+ try (Scanner sc = new Scanner(reverseNotation)) {
+ while (sc.hasNext()) {
+ token = sc.next();
+ if (OPERATORS.contains(token)) {
+ if (operands.size() < 2) {
+ throw new ParsingException("Incorrect expression");
+ }
+ b = operands.pop();
+ a = operands.pop();
+ try {
+ result = count(token, a, b);
+ } finally {
+ operands.push(result);
+ }
+ } else if (token.equals("#")) {
+ if (operands.size() < 1) {
+ throw new ParsingException("Invalid expression: expected number");
+ } else {
+ double operand = operands.pop();
+ operands.push(-1 * operand);
+ }
+ } else {
+ try {
+ a = Double.parseDouble(token);
+ operands.push(a);
+ } catch (NumberFormatException npe) {
+ throw new ParsingException(String.format("Invalid operand: %s", token));
+ }
+ }
+ }
+ if (operands.size() != 1) {
+ throw new ParsingException("Incorrect expression");
+ } else {
+ return operands.pop();
+ }
+ }
+ }
+
+ private String buildRPN(String expression) throws ParsingException {
+ StringBuilder answer = new StringBuilder();
+ Stack operators = new Stack<>();
+ Character c;
+ boolean unary = true;
+ boolean prevIsDot = false;
+ String prevUnary = "";
+ int searchCondition = 0;
+ for (int i = 0; i < expression.length(); ++i) {
+ c = expression.charAt(i);
+ if (prevIsDot && !(Character.isDigit(c))) {
+ throw new ParsingException("Invalid expression: unexpected token after dot");
+ }
+ if (Character.isDigit(c)) {
+ unary = false;
+ answer.append(c);
+ searchCondition = 1;
+ prevIsDot = false;
+ } else if (Character.isWhitespace(c)) {
+ answer.append(' ');
+ } else if (c.equals('.')) {
+ if (searchCondition == 0) {
+ throw new ParsingException("Invalid expression: unexpected symbol '.'");
+ } else {
+ answer.append('.');
+ searchCondition = 1;
+ }
+ prevIsDot = true;
+ } else if (c.equals('(')) {
+ if (searchCondition == 1) {
+ throw new ParsingException("Invalid expression: unexpected symbol '('");
+ } else {
+ unary = true;
+ answer.append(' ');
+ operators.push("(");
+ searchCondition = 0;
+ }
+ prevIsDot = false;
+ } else if (c.equals(')')) {
+ if (searchCondition == 0) {
+ throw new ParsingException("Invalid expression: unexpected ) after operator");
+ } else {
+ boolean hasCorrespondingOpeningBracket = false;
+ while (!(operators.empty())) {
+ String curOperator = operators.pop();
+ if (curOperator.equals("(")) {
+ hasCorrespondingOpeningBracket = true;
+ break;
+ } else {
+ answer.append(' ').append(curOperator).append(' ');
+ }
+ }
+ if (!hasCorrespondingOpeningBracket) {
+ throw new ParsingException("Invalid expression: invalid brackets");
+ }
+
+ }
+ prevIsDot = false;
+ } else if (OPERATORS.contains(c.toString())) {
+ if (unary) {
+ switch (c.toString()) {
+ case "-":
+ operators.push("#");
+ case "+":
+ if (prevUnary.equals("+")) {
+ throw new ParsingException("Invalid expression: unexpected+");
+ }
+ unary = true;
+ break;
+ default:
+ throw new ParsingException(
+ String.format("Invalid expression: invalid unary operator: %c", c));
+ }
+ prevUnary = c.toString();
+ } else {
+ unary = true;
+ answer.append(' ');
+ int curOperatorPriority = getPriority(c.toString());
+ while (!(operators.empty()) && curOperatorPriority <= getPriority(operators.peek())) {
+ answer.append(' ').append(operators.pop()).append(' ');
+ }
+ operators.push(c.toString());
+ }
+
+ searchCondition = 0;
+ } else {
+ throw new ParsingException(String.format("Invalid expression: unexpected %s", c.toString()));
+ }
+ }
+ while (!operators.empty()) {
+ String curOperator = operators.pop();
+ if (OPERATORS.contains(curOperator) || curOperator == "#") {
+ answer.append(String.format(" %s ", curOperator));
+ } else {
+ throw new ParsingException(String.format("Invalid expression: unexpected %s", curOperator));
+ }
+ }
+ return answer.toString();
+ }
+}
\ No newline at end of file
diff --git a/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task4/RestCalcApplication.java b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task4/RestCalcApplication.java
new file mode 100644
index 000000000..1bf297cad
--- /dev/null
+++ b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task4/RestCalcApplication.java
@@ -0,0 +1,41 @@
+package ru.mipt.java2016.homework.g599.lantsetov.task4;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.Banner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import ru.mipt.java2016.homework.base.task1.Calculator;
+
+/**
+ * curl http://localhost:9001/eval \
+ * -X POST \
+ * -H "Content-Type: text/plain" \
+ * -H "Authorization: Basic $(echo -n "username:password" | base64)" \
+ * --data-raw "44*3+2"
+ */
+@EnableAutoConfiguration
+@Configuration
+@ComponentScan(basePackageClasses = RestCalcApplication.class)
+public class RestCalcApplication {
+
+ @Bean
+ public Calculator calculator() {
+ return CalculatorImplementation.INSTANCE;
+ }
+
+ @Bean
+ public EmbeddedServletContainerCustomizer customizer(
+ @Value("${ru.mipt.java2016.homework.g599.lantsetov.task4.httpPort:9001}") int port) {
+ return container -> container.setPort(port);
+ }
+
+ public static void main(String[] args) {
+ SpringApplication application = new SpringApplication(RestCalcApplication.class);
+ application.setBannerMode(Banner.Mode.OFF);
+ application.run(args);
+ }
+}
\ No newline at end of file
diff --git a/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task4/SecurityServiceConfiguration.java b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task4/SecurityServiceConfiguration.java
new file mode 100644
index 000000000..15a03eadf
--- /dev/null
+++ b/homework-g599-lantsetov/src/main/java/ru/mipt/java2016/homework/g599/lantsetov/task4/SecurityServiceConfiguration.java
@@ -0,0 +1,55 @@
+package ru.mipt.java2016.homework.g599.lantsetov.task4;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+
+import java.util.Collections;
+
+@Configuration
+@EnableWebSecurity
+public class SecurityServiceConfiguration extends WebSecurityConfigurerAdapter {
+ private static final Logger LOG = LoggerFactory.getLogger(SecurityServiceConfiguration.class);
+
+ @Autowired
+ private BillingDao billingDao;
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ LOG.info("Configuring security");
+ http
+ .httpBasic().realmName("Calculator").and()
+ .formLogin().disable()
+ .logout().disable()
+ .csrf().disable()
+ .authorizeRequests()
+ .antMatchers("/eval/**").authenticated()
+ .anyRequest().permitAll();
+ }
+
+ @Autowired
+ public void registerGlobalAuthentication(AuthenticationManagerBuilder auth) throws Exception {
+ LOG.info("Registering global user details service");
+ auth.userDetailsService(username -> {
+ try {
+ BillingUser user = billingDao.loadUser(username);
+ return new User(
+ user.getUsername(),
+ user.getPassword(),
+ Collections.singletonList(() -> "AUTH")
+ );
+ } catch (EmptyResultDataAccessException e) {
+ LOG.error("No such user: " + username);
+ throw new UsernameNotFoundException(username);
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/homework-g599-lantsetov/src/test/java/ru/mipt/java2016/homework/g599/lantsetov/task1/MyCalculatorTest.java b/homework-g599-lantsetov/src/test/java/ru/mipt/java2016/homework/g599/lantsetov/task1/MyCalculatorTest.java
new file mode 100644
index 000000000..e249d8fdb
--- /dev/null
+++ b/homework-g599-lantsetov/src/test/java/ru/mipt/java2016/homework/g599/lantsetov/task1/MyCalculatorTest.java
@@ -0,0 +1,14 @@
+package ru.mipt.java2016.homework.g599.lantsetov.task1;
+
+import ru.mipt.java2016.homework.base.task1.Calculator;
+import ru.mipt.java2016.homework.g599.lantsetov.task1.MyCalculator;
+import ru.mipt.java2016.homework.tests.task1.AbstractCalculatorTest;
+
+
+public class MyCalculatorTest extends AbstractCalculatorTest {
+
+ @Override
+ protected Calculator calc() {
+ return new MyCalculator();
+ }
+}
\ No newline at end of file
diff --git a/homework-g599-lantsetov/src/test/java/ru/mipt/java2016/homework/g599/lantsetov/task2/MyKeyValueStorageTest.java b/homework-g599-lantsetov/src/test/java/ru/mipt/java2016/homework/g599/lantsetov/task2/MyKeyValueStorageTest.java
new file mode 100644
index 000000000..c079d753d
--- /dev/null
+++ b/homework-g599-lantsetov/src/test/java/ru/mipt/java2016/homework/g599/lantsetov/task2/MyKeyValueStorageTest.java
@@ -0,0 +1,46 @@
+package ru.mipt.java2016.homework.g599.lantsetov.task2;
+
+import ru.mipt.java2016.homework.tests.task2.AbstractSingleFileStorageTest;
+import ru.mipt.java2016.homework.tests.task2.Student;
+import ru.mipt.java2016.homework.tests.task2.StudentKey;
+import ru.mipt.java2016.homework.base.task2.KeyValueStorage;
+
+import java.io.IOException;
+
+/**
+ * Created by Михаил on 31.10.2016.
+ */
+public class MyKeyValueStorageTest extends AbstractSingleFileStorageTest {
+ @Override
+ protected KeyValueStorage buildStringsStorage(String path) {
+ StringSerializer keySerializer = new StringSerializer();
+ StringSerializer valueSerializer = new StringSerializer();
+ try {
+ return new MyKeyValueStorage<>(path, keySerializer, valueSerializer);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot connect to DataBase");
+ }
+ }
+
+ @Override
+ protected KeyValueStorage buildNumbersStorage(String path) {
+ IntegerSerializer keySerializer = new IntegerSerializer();
+ DoubleSerializer valueSerializer = new DoubleSerializer();
+ try {
+ return new MyKeyValueStorage<>(path, keySerializer, valueSerializer);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot connect to DataBase");
+ }
+ }
+
+ @Override
+ protected KeyValueStorage buildPojoStorage(String path) {
+ StudentKeySerializer keySerializer = new StudentKeySerializer();
+ StudentSerializer valueSerializer = new StudentSerializer();
+ try {
+ return new MyKeyValueStorage<>(path, keySerializer, valueSerializer);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot connect to DataBase");
+ }
+ }
+}
\ No newline at end of file
diff --git a/homework-g599-lantsetov/src/test/java/ru/mipt/java2016/homework/g599/lantsetov/task2/StudentKeySerializer.java b/homework-g599-lantsetov/src/test/java/ru/mipt/java2016/homework/g599/lantsetov/task2/StudentKeySerializer.java
new file mode 100644
index 000000000..e1cd7f76e
--- /dev/null
+++ b/homework-g599-lantsetov/src/test/java/ru/mipt/java2016/homework/g599/lantsetov/task2/StudentKeySerializer.java
@@ -0,0 +1,25 @@
+package ru.mipt.java2016.homework.g599.lantsetov.task2;
+
+import ru.mipt.java2016.homework.tests.task2.StudentKey;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * Created by Михаил on 31.10.2016.
+ */
+public class StudentKeySerializer implements MySerializer {
+ @Override
+ public StudentKey read(RandomAccessFile file) throws IOException {
+ int groupId = file.readInt();
+ StringSerializer helper = new StringSerializer();
+ String name = helper.read(file);
+ return new StudentKey(groupId, name);
+ }
+
+ @Override
+ public void write(RandomAccessFile file, StudentKey arg) throws IOException {
+ file.writeInt(arg.getGroupId());
+ StringSerializer helper = new StringSerializer();
+ helper.write(file, arg.getName());
+ }
+}
\ No newline at end of file
diff --git a/homework-g599-lantsetov/src/test/java/ru/mipt/java2016/homework/g599/lantsetov/task2/StudentSerializer.java b/homework-g599-lantsetov/src/test/java/ru/mipt/java2016/homework/g599/lantsetov/task2/StudentSerializer.java
new file mode 100644
index 000000000..74c3fa998
--- /dev/null
+++ b/homework-g599-lantsetov/src/test/java/ru/mipt/java2016/homework/g599/lantsetov/task2/StudentSerializer.java
@@ -0,0 +1,36 @@
+package ru.mipt.java2016.homework.g599.lantsetov.task2;
+
+import ru.mipt.java2016.homework.tests.task2.Student;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.Date;
+
+
+/**
+ * Created by Михаил on 31.10.2016.
+ */
+public class StudentSerializer implements MySerializer {
+ private final StringSerializer helper = new StringSerializer();
+
+ @Override
+ public Student read(RandomAccessFile file) throws IOException {
+ int groupId = file.readInt();
+ String name = helper.read(file);
+ String hometown = helper.read(file);
+ Date birthDate = new Date(file.readLong());
+ boolean hasDormiory = file.readBoolean();
+ double averageScore = file.readDouble();
+ return new Student(groupId, name, hometown, birthDate, hasDormiory, averageScore);
+ }
+
+ @Override
+ public void write(RandomAccessFile file, Student arg) throws IOException {
+ file.writeInt(arg.getGroupId());
+ helper.write(file, arg.getName());
+ helper.write(file, arg.getHometown());
+ file.writeLong(arg.getBirthDate().getTime());
+ file.writeBoolean(arg.isHasDormitory());
+ file.writeDouble(arg.getAverageScore());
+ }
+}
\ No newline at end of file
diff --git a/homework-g599-lantsetov/src/test/java/ru/mipt/java2016/homework/g599/lantsetov/task3/KeyValueStorageTest.java b/homework-g599-lantsetov/src/test/java/ru/mipt/java2016/homework/g599/lantsetov/task3/KeyValueStorageTest.java
new file mode 100644
index 000000000..072fd7191
--- /dev/null
+++ b/homework-g599-lantsetov/src/test/java/ru/mipt/java2016/homework/g599/lantsetov/task3/KeyValueStorageTest.java
@@ -0,0 +1,48 @@
+package ru.mipt.java2016.homework.g599.lantsetov.task3;
+
+import ru.mipt.java2016.homework.g599.lantsetov.task2.*;
+import ru.mipt.java2016.homework.tests.task3.KeyValueStoragePerformanceTest;
+import ru.mipt.java2016.homework.base.task2.KeyValueStorage;
+import ru.mipt.java2016.homework.base.task2.MalformedDataException;
+import ru.mipt.java2016.homework.tests.task2.Student;
+import ru.mipt.java2016.homework.tests.task2.StudentKey;
+
+import java.io.IOException;
+
+/**
+ * Created by mikhail on 22.11.16.
+ */
+public class KeyValueStorageTest extends KeyValueStoragePerformanceTest {
+ @Override
+ public KeyValueStorage buildStringsStorage(String path) throws MalformedDataException {
+ StringSerializer keySerializer = new StringSerializer();
+ StringSerializer valueSerializer = new StringSerializer();
+ try {
+ return new MyKeyValueStorage<>(path, keySerializer, valueSerializer);
+ } catch (IOException e) {
+ throw new MalformedDataException("Something went wrong");
+ }
+ }
+
+ @Override
+ public KeyValueStorage buildNumbersStorage(String path) throws MalformedDataException {
+ IntegerSerializer keySerializer = new IntegerSerializer();
+ DoubleSerializer valueSerializer = new DoubleSerializer();
+ try {
+ return new MyKeyValueStorage<>(path, keySerializer, valueSerializer);
+ } catch (IOException e) {
+ throw new MalformedDataException("Something went wrong");
+ }
+ }
+
+ @Override
+ public KeyValueStorage buildPojoStorage(String path) throws MalformedDataException {
+ StudentKeySerializer keySerializer = new StudentKeySerializer();
+ StudentSerializer valueSerializer = new StudentSerializer();
+ try {
+ return new MyKeyValueStorage<>(path, keySerializer, valueSerializer);
+ } catch (IOException e) {
+ throw new MalformedDataException("Something went wrong");
+ }
+ }
+}
\ No newline at end of file