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