diff --git a/homework-g597-bogdanov/pom.xml b/homework-g597-bogdanov/pom.xml
index b77da7194..c2e8465f6 100644
--- a/homework-g597-bogdanov/pom.xml
+++ b/homework-g597-bogdanov/pom.xml
@@ -10,7 +10,12 @@
4.0.0
homework-g597-bogdanov
+ pom
+ 1.0.0
+
+ 1.4.2.RELEASE
+
@@ -19,6 +24,43 @@
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.3.13
+
+
+
+ com.h2database
+ h2
+ 1.4.190
+
+
ru.mipt.java2016
homework-tests
diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task3/MyOptimisedKeyValueStorage.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task3/MyOptimisedKeyValueStorage.java
index 5592b68f8..a9f1902df 100644
--- a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task3/MyOptimisedKeyValueStorage.java
+++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task3/MyOptimisedKeyValueStorage.java
@@ -221,8 +221,8 @@ private void dumpMemtable() {
}
private void clearDeletedElements() {
- dumpMemtable();
readWriteLock.writeLock().lock();
+ dumpMemtable();
try {
HashMap updatedOffsets = new HashMap<>();
File tmpFileForValues = new File(path + File.separator + this.fileName + "_values");
diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CURL commands.txt b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CURL commands.txt
new file mode 100644
index 000000000..6bdc8b066
--- /dev/null
+++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CURL commands.txt
@@ -0,0 +1,23 @@
+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"
+
+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"
+
+curl http://localhost:9001/variable/mlgrank -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "username:password" | base64)" --data-raw "1"
+
+curl http://localhost:9001/variable/mlgrank -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "username:password" | base64)" --data-raw "99999"
+
+curl http://localhost:9001/variable/mlgrank -X GET -H "Authorization: Basic $(echo -n "username:password" | base64)"
+
+curl http://localhost:9001/variable/mlgrank -X GET -H "Authorization: Basic $(echo -n "username:password" | base64)"
+
+curl http://localhost:9001/variable/ml -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "username:password" | base64)" --data-raw "max(1.010, 1.009)"
+
+curl http://localhost:9001/function/myfunc?args=x&args=y -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "username:password" | base64)" --data-raw "pow(2, x)"
+
+curl http://localhost:9001/function/myfunc1?args=x,y -X PUT -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "username:password" | base64)" --data-raw "log2(x) + y"
+
+curl http://localhost:9001/eval -X POST -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "username:password" | base64)" --data-raw "15 + myfunc1(64, 4)"
+
+curl http://localhost:9001/eval -X POST -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "username:password" | base64)" --data-raw "15 + myfunc1(64, 4)"
+
+curl http://localhost:9001/function/myfunc1 -X GET -H "Content-Type: text/plain" -H "Authorization: Basic $(echo -n "username:password" | base64)"
\ No newline at end of file
diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CalculatorWithTokens/Token.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CalculatorWithTokens/Token.java
new file mode 100644
index 000000000..1de6f1201
--- /dev/null
+++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CalculatorWithTokens/Token.java
@@ -0,0 +1,48 @@
+package ru.mipt.java2016.homework.g597.bogdanov.task4.CalculatorWithTokens;
+
+/**
+ * Created by Semyo_000 on 20.12.2016.
+ */
+public class Token {
+ public enum TokenType {
+ PLUS, MINUS, MULTIPLY, DIVIDE,
+ NUMBER, NAME,
+ LEFT_BRACE, RIGHT_BRACE, COMMA,
+ UNKNOWN
+ }
+
+ private final TokenType type;
+ private Double number = null;
+ private String name = null;
+
+ public Token(TokenType type) {
+ this.type = type;
+ }
+
+ public Token(String name) {
+ this.name = name;
+ this.type = TokenType.NAME;
+ }
+
+ public Token(Double number) {
+ this.number = number;
+ this.type = TokenType.NUMBER;
+ }
+
+ public TokenType getType() {
+ return type;
+ }
+
+ public Double getNumber() {
+ return number;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Type: %s, Number: %s, Name: %s \n", type, number, name);
+ }
+}
\ No newline at end of file
diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CalculatorWithTokens/TokenCalculator.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CalculatorWithTokens/TokenCalculator.java
new file mode 100644
index 000000000..8027752b0
--- /dev/null
+++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/CalculatorWithTokens/TokenCalculator.java
@@ -0,0 +1,186 @@
+package ru.mipt.java2016.homework.g597.bogdanov.task4.CalculatorWithTokens;
+
+import ru.mipt.java2016.homework.base.task1.Calculator;
+import ru.mipt.java2016.homework.base.task1.ParsingException;
+
+import java.util.ArrayList;
+
+/**
+ * Created by Semyo_000 on 20.12.2016.
+ */
+public class TokenCalculator implements Calculator {
+ private final ArrayList tokens = new ArrayList<>();
+ private int tokensIndex = 0;
+
+
+ private void parse(String expression) throws ParsingException {
+ if (expression == null) {
+ throw new ParsingException("Expression is null.");
+ }
+
+ for (int expressionIndex = 0; expressionIndex < expression.length(); ++expressionIndex) {
+ Token.TokenType currentTokenType;
+ Double currentNumber = null;
+
+ if (Character.isWhitespace(expression.charAt(expressionIndex)) ||
+ Character.isSpaceChar(expression.charAt(expressionIndex))) {
+ continue;
+ }
+
+ switch (expression.charAt(expressionIndex)) {
+ case '+':
+ currentTokenType = Token.TokenType.PLUS;
+ break;
+
+ case '-':
+ currentTokenType = Token.TokenType.MINUS;
+ break;
+
+ case '*':
+ currentTokenType = Token.TokenType.MULTIPLY;
+ break;
+
+ case '/':
+ currentTokenType = Token.TokenType.DIVIDE;
+ break;
+
+ case '(':
+ currentTokenType = Token.TokenType.LEFT_BRACE;
+ break;
+
+ case ')':
+ currentTokenType = Token.TokenType.RIGHT_BRACE;
+ break;
+
+ default:
+ if (!Character.isDigit(expression.charAt(expressionIndex))) {
+ throw new ParsingException(String.format("Unexpected symbol at %d", expressionIndex));
+ }
+
+ boolean readDot = false;
+ int numberStartIndex = expressionIndex;
+ for (; expressionIndex < expression.length(); ++expressionIndex) {
+ Character currentCharacter = expression.charAt(expressionIndex);
+ if (currentCharacter == '.' && !readDot) {
+ readDot = true;
+ } else if (!Character.isDigit(currentCharacter)) {
+ break;
+ }
+ }
+
+ currentNumber = Double.parseDouble(expression.substring(numberStartIndex, expressionIndex));
+ --expressionIndex;
+ currentTokenType = Token.TokenType.NUMBER;
+ break;
+ }
+
+ if (currentTokenType != Token.TokenType.NUMBER) {
+ tokens.add(new Token(currentTokenType));
+ } else {
+ tokens.add(new Token(currentNumber));
+ }
+ }
+ }
+
+ private void regressTokensIndex() {
+ --tokensIndex;
+ }
+
+ private Token progressTokens() {
+ if (tokensIndex >= tokens.size()) {
+ return null;
+ }
+
+ return tokens.get(tokensIndex++);
+ }
+
+ private Double expression() throws ParsingException {
+ Double result = multiple();
+
+ for (Token token = progressTokens(); token != null; token = progressTokens()) {
+ if (token.getType() == Token.TokenType.PLUS) {
+ result += multiple();
+ } else if (token.getType() == Token.TokenType.MINUS) {
+ result -= multiple();
+ } else {
+ regressTokensIndex();
+ break;
+ }
+ }
+
+ return result;
+ }
+
+ private Double multiple() throws ParsingException {
+ Double result = bracedExpression();
+
+ for (Token token = progressTokens(); token != null; token = progressTokens()) {
+ if (token.getType() == Token.TokenType.MULTIPLY) {
+ result *= bracedExpression();
+ } else if (token.getType() == Token.TokenType.DIVIDE) {
+ result /= bracedExpression();
+ } else {
+ regressTokensIndex();
+ break;
+ }
+ }
+
+ return result;
+ }
+
+ private Double bracedExpression() throws ParsingException {
+ Double result;
+
+ Token token = progressTokens();
+ if (token == null) {
+ throw new ParsingException("Invalid amount of numbers.");
+ }
+
+ if (token.getType() == Token.TokenType.LEFT_BRACE) {
+ result = expression();
+ token = progressTokens();
+
+ if (token == null || token.getType() != Token.TokenType.RIGHT_BRACE) {
+ throw new ParsingException("Wrong number of left/right braces");
+ }
+ } else {
+ regressTokensIndex();
+ result = numberExpression();
+ }
+
+ return result;
+ }
+
+ private Double numberExpression() throws ParsingException {
+ Double result;
+
+ Token token = progressTokens();
+ if (token == null) {
+ throw new ParsingException("Invalid amount of numbers");
+ }
+
+ if (token.getType() == Token.TokenType.MINUS) {
+ result = -expression();
+ } else if (token.getType() == Token.TokenType.NUMBER) {
+ result = token.getNumber();
+ } else {
+ throw new ParsingException("Invalid order of operations");
+ }
+
+ return result;
+ }
+
+ public double calculate(String expression) throws ParsingException {
+ tokens.clear();
+ tokensIndex = 0;
+
+ parse(expression);
+ Double result = expression();
+
+ if (tokensIndex != tokens.size()) {
+ throw new ParsingException("Invalid number of tokens (too many).");
+ }
+
+ return result;
+ }
+}
diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/IFunctionalCalculator.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/IFunctionalCalculator.java
new file mode 100644
index 000000000..dd75fc5ea
--- /dev/null
+++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/IFunctionalCalculator.java
@@ -0,0 +1,30 @@
+package ru.mipt.java2016.homework.g597.bogdanov.task4.REST;
+
+import ru.mipt.java2016.homework.base.task1.ParsingException;
+import ru.mipt.java2016.homework.g597.bogdanov.task4.REST.functions.CalculatorFunctionObject;
+
+import java.util.List;
+
+/**
+ * Created by Semyo_000 on 20.12.2016.
+ */
+public interface IFunctionalCalculator {
+
+ Double getVariable(String variableAlias);
+
+ boolean putVariable(String variableAlias, Double value);
+
+ boolean deleteVariable(String variableAlias);
+
+ List getVariableList();
+
+ CalculatorFunctionObject getFunction(String functionAlias);
+
+ boolean putFunction(String functionAlias, String expression, List arguments);
+
+ boolean deleteFunction(String functionAlias);
+
+ List getFunctionList();
+
+ Double calculate(String expression) throws ParsingException;
+}
diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/RESTCalculator.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/RESTCalculator.java
new file mode 100644
index 000000000..5915e9e18
--- /dev/null
+++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/RESTCalculator.java
@@ -0,0 +1,120 @@
+package ru.mipt.java2016.homework.g597.bogdanov.task4.REST;
+
+import ru.mipt.java2016.homework.base.task1.ParsingException;
+import ru.mipt.java2016.homework.g597.bogdanov.task4.REST.functions.CalculateableFunction;
+import ru.mipt.java2016.homework.g597.bogdanov.task4.REST.functions.CalculatorFunctionObject;
+import ru.mipt.java2016.homework.g597.bogdanov.task4.REST.functions.IEvaluateableFunction;
+import ru.mipt.java2016.homework.g597.bogdanov.task4.REST.functions.PredefinedFunction;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+/**
+ * Created by Semyo_000 on 20.12.2016.
+ */
+public class RESTCalculator implements IFunctionalCalculator {
+ private Map functions = new ConcurrentHashMap<>();
+ private Map variables = new ConcurrentHashMap<>();
+
+ private void initializePredefinedFunctions() {
+ functions.put("sin", new PredefinedFunction(PredefinedFunction.PredefinedFunctionType.SIN));
+ functions.put("cos", new PredefinedFunction(PredefinedFunction.PredefinedFunctionType.COS));
+ functions.put("tg", new PredefinedFunction(PredefinedFunction.PredefinedFunctionType.TG));
+ functions.put("sqrt", new PredefinedFunction(PredefinedFunction.PredefinedFunctionType.SQRT));
+ functions.put("pow", new PredefinedFunction(PredefinedFunction.PredefinedFunctionType.POW));
+ functions.put("abs", new PredefinedFunction(PredefinedFunction.PredefinedFunctionType.ABS));
+ functions.put("sign", new PredefinedFunction(PredefinedFunction.PredefinedFunctionType.SIGN));
+ functions.put("log", new PredefinedFunction(PredefinedFunction.PredefinedFunctionType.LOG));
+ functions.put("log2", new PredefinedFunction(PredefinedFunction.PredefinedFunctionType.LOG2));
+ functions.put("rnd", new PredefinedFunction(PredefinedFunction.PredefinedFunctionType.RND));
+ functions.put("max", new PredefinedFunction(PredefinedFunction.PredefinedFunctionType.MAX));
+ functions.put("min", new PredefinedFunction(PredefinedFunction.PredefinedFunctionType.MIN));
+ }
+
+ public RESTCalculator() {
+ initializePredefinedFunctions();
+ }
+
+ @Override
+ public Double getVariable(String variableAlias) {
+ return variables.get(variableAlias);
+ }
+
+ @Override
+ public boolean putVariable(String variableAlias, Double value) {
+ if (functions.containsKey(variableAlias)) {
+ return false;
+ } else {
+ variables.put(variableAlias, value);
+ return true;
+ }
+ }
+
+ @Override
+ public boolean deleteVariable(String variableAlias) {
+ if (variables.containsKey(variableAlias)) {
+ variables.remove(variableAlias);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public List getVariableList() {
+ return variables.keySet().stream().collect(Collectors.toList());
+ }
+
+ public CalculatorFunctionObject getFunction(String functionAlias) {
+ if (!functions.containsKey(functionAlias) || functions.get(functionAlias).isPredefined()) {
+ return null;
+ }
+
+ CalculateableFunction function = (CalculateableFunction) functions.get(functionAlias);
+ return new CalculatorFunctionObject(function.getFunctionExpression(), function.getParameterList());
+ }
+
+ @Override
+ public boolean putFunction(String functionAlias, String expression, List arguments) {
+ if (variables.containsKey(functionAlias) ||
+ (functions.containsKey(functionAlias) && functions.get(functionAlias).isPredefined())) {
+ return false;
+ }
+
+ try {
+ functions.put(functionAlias, new CalculateableFunction(expression, arguments, functions, variables));
+ } catch (ParsingException e) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean deleteFunction(String functionAlias) {
+ if (functions.containsKey(functionAlias)) {
+ IEvaluateableFunction function = functions.get(functionAlias);
+ if (function == null || function.isPredefined()) {
+ return false;
+ } else {
+ functions.remove(functionAlias);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public List getFunctionList() {
+ return functions.keySet().stream().collect(Collectors.toList());
+ }
+
+ @Override
+ public Double calculate(String expression) throws ParsingException {
+ return new CalculateableFunction(expression, new ArrayList<>(), functions, variables).evaluate();
+ }
+}
\ No newline at end of file
diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/CalculateableFunction.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/CalculateableFunction.java
new file mode 100644
index 000000000..cbe06d0fd
--- /dev/null
+++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/CalculateableFunction.java
@@ -0,0 +1,323 @@
+package ru.mipt.java2016.homework.g597.bogdanov.task4.REST.functions;
+
+import ru.mipt.java2016.homework.base.task1.ParsingException;
+import ru.mipt.java2016.homework.g597.bogdanov.task4.CalculatorWithTokens.Token;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * Created by Semyo_000 on 20.12.2016.
+ */
+public class CalculateableFunction implements IEvaluateableFunction {
+ private Map functions;
+ private Map variables;
+
+ private final ArrayList tokens = new ArrayList<>();
+ private int tokensIndex = 0;
+ private String functionExpression;
+
+ private Map parameterNumberToParameterString = new HashMap<>();
+ private Map parameters = new HashMap<>();
+
+ public CalculateableFunction(String functionExpression, List functionParameters,
+ Map functions,
+ Map variables) throws ParsingException {
+ if (functionParameters != null) {
+ for (int i = 0; i < functionParameters.size(); ++i) {
+ parameterNumberToParameterString.put(i, functionParameters.get(i));
+ }
+ }
+
+ this.functionExpression = functionExpression;
+ this.functions = functions;
+ this.variables = variables;
+
+ parse(functionExpression);
+ }
+
+ private void parse(String expression) throws ParsingException {
+ if (expression == null) {
+ throw new ParsingException("Expression is null.");
+ }
+
+ for (int expressionIndex = 0; expressionIndex < expression.length(); ++expressionIndex) {
+ Token currentToken;
+
+ if (Character.isWhitespace(expression.charAt(expressionIndex)) ||
+ Character.isSpaceChar(expression.charAt(expressionIndex))) {
+ continue;
+ }
+
+ switch (expression.charAt(expressionIndex)) {
+ case '+':
+ currentToken = new Token(Token.TokenType.PLUS);
+ break;
+
+ case '-':
+ currentToken = new Token(Token.TokenType.MINUS);
+ break;
+
+ case '*':
+ currentToken = new Token(Token.TokenType.MULTIPLY);
+ break;
+
+ case '/':
+ currentToken = new Token(Token.TokenType.DIVIDE);
+ break;
+
+ case '(':
+ currentToken = new Token(Token.TokenType.LEFT_BRACE);
+ break;
+
+ case ')':
+ currentToken = new Token(Token.TokenType.RIGHT_BRACE);
+ break;
+
+ case ',':
+ currentToken = new Token(Token.TokenType.COMMA);
+ break;
+
+ default:
+ if (Character.isDigit(expression.charAt(expressionIndex))) {
+ boolean readDot = false;
+ int numberStartIndex = expressionIndex;
+ for (; expressionIndex < expression.length(); ++expressionIndex) {
+ Character currentCharacter = expression.charAt(expressionIndex);
+ if (currentCharacter == '.' && !readDot) {
+ readDot = true;
+ } else if (!Character.isDigit(currentCharacter)) {
+ break;
+ }
+ }
+
+ Double currentNumber =
+ Double.parseDouble(expression.substring(numberStartIndex, expressionIndex));
+ --expressionIndex;
+ currentToken = new Token(currentNumber);
+
+ } else if (Character.isAlphabetic(expression.charAt(expressionIndex))) {
+ int nameStartIndex = expressionIndex;
+ for (; expressionIndex < expression.length(); ++expressionIndex) {
+ Character currentCharacter = expression.charAt(expressionIndex);
+ if (!Character.isAlphabetic(currentCharacter) &&
+ !Character.isDigit(currentCharacter) &&
+ currentCharacter != '_') {
+ break;
+ }
+ }
+
+ currentToken = new Token(functionExpression.substring(nameStartIndex, expressionIndex));
+ --expressionIndex;
+ } else {
+ throw new ParsingException(String.format("Unexpected symbol at %d", expressionIndex));
+ }
+ break;
+ }
+
+ tokens.add(currentToken);
+ }
+ }
+
+ private void regressTokensIndex() {
+ --tokensIndex;
+ }
+
+ private Token progressTokens() {
+ if (tokensIndex >= tokens.size()) {
+ return null;
+ }
+
+ return tokens.get(tokensIndex++);
+ }
+
+ private Double expression() throws ParsingException {
+ Double result = multiple();
+
+ for (Token token = progressTokens(); token != null; token = progressTokens()) {
+ if (token.getType() == Token.TokenType.PLUS) {
+ result += multiple();
+ } else if (token.getType() == Token.TokenType.MINUS) {
+ result -= multiple();
+ } else {
+ regressTokensIndex();
+ break;
+ }
+ }
+
+ return result;
+ }
+
+ private Double multiple() throws ParsingException {
+ Double result = bracedExpression();
+
+ for (Token token = progressTokens(); token != null; token = progressTokens()) {
+ if (token.getType() == Token.TokenType.MULTIPLY) {
+ result *= bracedExpression();
+ } else if (token.getType() == Token.TokenType.DIVIDE) {
+ result /= bracedExpression();
+ } else {
+ regressTokensIndex();
+ break;
+ }
+ }
+
+ return result;
+ }
+
+ private Double bracedExpression() throws ParsingException {
+ Double result;
+
+ Token token = progressTokens();
+ if (token == null) {
+ throw new ParsingException("Invalid amount of numbers.");
+ }
+
+ if (token.getType() == Token.TokenType.LEFT_BRACE) {
+ result = expression();
+ token = progressTokens();
+
+ if (token == null || token.getType() != Token.TokenType.RIGHT_BRACE) {
+ throw new ParsingException("Wrong number of left/right braces");
+ }
+ } else {
+ regressTokensIndex();
+ result = combinedExpression();
+ }
+
+ return result;
+ }
+
+ private Double combinedExpression() throws ParsingException {
+ Double result;
+
+ Token token = progressTokens();
+ if (token == null) {
+ throw new ParsingException("Invalid amount of numbers");
+ }
+
+ if (token.getType() == Token.TokenType.MINUS) {
+ result = -bracedExpression();
+ } else if (token.getType() == Token.TokenType.NUMBER) {
+ result = token.getNumber();
+ } else if (token.getType() == Token.TokenType.NAME) {
+ regressTokensIndex();
+ result = namedExpression();
+ } else {
+ throw new ParsingException("Invalid order of operations");
+ }
+
+ return result;
+ }
+
+ private Double namedExpression() throws ParsingException {
+ Double result;
+
+ Token token = progressTokens();
+ if (token == null) {
+ throw new ParsingException("Invalid amount of numbers");
+ }
+ String tokenName = token.getName();
+
+ Token nextToken = progressTokens();
+ if (nextToken != null && nextToken.getType() == Token.TokenType.LEFT_BRACE) {
+ if (!functions.containsKey(tokenName)) {
+ throw new ParsingException("Unexpected symbol. Function call impossible.");
+ }
+
+ result = functionCall(tokenName);
+ } else {
+ if (nextToken != null) {
+ regressTokensIndex();
+ }
+
+ if (parameters.containsKey(tokenName)) {
+ result = parameters.get(tokenName);
+ } else {
+ if (!variables.containsKey(tokenName)) {
+ throw new ParsingException("Unexpected symbol. No such variable.");
+ }
+ result = variables.get(tokenName);
+ }
+ }
+
+ return result;
+ }
+
+ private Double functionCall(String functionName) throws ParsingException {
+ Double result;
+
+ IEvaluateableFunction function = functions.get(functionName);
+ List functionArguments = new ArrayList<>();
+
+ Token token;
+ do {
+ token = progressTokens();
+
+ if (token == null) {
+ throw new ParsingException("Unexpected end of function expression.");
+ } else {
+ if (token.getType() == Token.TokenType.COMMA) {
+ throw new ParsingException("Unexpected comma - no parameter.");
+ } else if (token.getType() == Token.TokenType.RIGHT_BRACE) {
+ break;
+ } else {
+ regressTokensIndex();
+ }
+ }
+
+ functionArguments.add(expression());
+
+ token = progressTokens();
+ if (token == null ||
+ (token.getType() != Token.TokenType.COMMA && token.getType() != Token.TokenType.RIGHT_BRACE)) {
+ throw new ParsingException("Unexpected end of function expression.");
+ }
+ } while (token.getType() != Token.TokenType.RIGHT_BRACE);
+
+ function.setArguments(functionArguments);
+ result = function.evaluate();
+ return result;
+ }
+
+ @Override
+ public Double evaluate() throws ParsingException {
+ tokensIndex = 0;
+ Double result = expression();
+
+ if (tokensIndex != tokens.size()) {
+ throw new ParsingException("Invalid number of tokens (too many).");
+ }
+
+ return result;
+ }
+
+ @Override
+ public void setArguments(List arguments) throws ParsingException {
+ if (arguments.size() != parameterNumberToParameterString.size()) {
+ throw new ParsingException("Number of arguments to be set is invalid.");
+ }
+ for (int argumentsIndex = 0; argumentsIndex < arguments.size(); ++argumentsIndex) {
+ parameters.put(parameterNumberToParameterString.get(argumentsIndex), arguments.get(argumentsIndex));
+ }
+ }
+
+ public String getFunctionExpression() {
+ return functionExpression;
+ }
+
+ public List getParameterList() {
+ List result = new ArrayList<>();
+
+ List> entries =
+ parameterNumberToParameterString.entrySet()
+ .stream()
+ .sorted(Comparator.comparingInt(Map.Entry::getKey))
+ .collect(Collectors.toList());
+
+ for (Map.Entry entry : entries) {
+ result.add(entry.getValue());
+ }
+ return result;
+ }
+}
diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/CalculatorFunctionObject.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/CalculatorFunctionObject.java
new file mode 100644
index 000000000..cab458cf9
--- /dev/null
+++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/CalculatorFunctionObject.java
@@ -0,0 +1,57 @@
+package ru.mipt.java2016.homework.g597.bogdanov.task4.REST.functions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by Semyo_000 on 20.12.2016.
+ */
+public class CalculatorFunctionObject {
+ private String expression;
+ private List arguments;
+
+ public CalculatorFunctionObject() {
+ expression = "";
+ arguments = new ArrayList<>();
+ }
+
+ public CalculatorFunctionObject(String expression, List arguments) {
+ this.expression = expression;
+ this.arguments = arguments;
+ }
+
+ public String getExpression() {
+ return expression;
+ }
+
+ public List getArguments() {
+ return arguments;
+ }
+
+
+ public void setExpression(String expression) {
+ this.expression = expression;
+ }
+
+ public void setArguments(List arguments) {
+ this.arguments = arguments;
+ }
+
+ public String toString() {
+ return "Expression: " + expression + '\n' +
+ "Arguments: " + String.join(", ", arguments);
+ }
+
+ public boolean equals(Object obj) {
+ if (!(obj instanceof CalculatorFunctionObject)) {
+ return false;
+ }
+ CalculatorFunctionObject comparedCalculatorFunction = (CalculatorFunctionObject) obj;
+ return expression.equals(comparedCalculatorFunction.expression) &&
+ arguments.equals(comparedCalculatorFunction.arguments);
+ }
+
+ public int hashCode() {
+ return expression.hashCode() * 31 + arguments.hashCode();
+ }
+}
diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/IEvaluateableFunction.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/IEvaluateableFunction.java
new file mode 100644
index 000000000..d172adad2
--- /dev/null
+++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/IEvaluateableFunction.java
@@ -0,0 +1,18 @@
+package ru.mipt.java2016.homework.g597.bogdanov.task4.REST.functions;
+
+import ru.mipt.java2016.homework.base.task1.ParsingException;
+
+import java.util.List;
+
+/**
+ * Created by Semyo_000 on 20.12.2016.
+ */
+public interface IEvaluateableFunction {
+ Double evaluate() throws ParsingException;
+
+ void setArguments(List arguments) throws ParsingException;
+
+ default boolean isPredefined() {
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/PredefinedFunction.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/PredefinedFunction.java
new file mode 100644
index 000000000..2b623b267
--- /dev/null
+++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/REST/functions/PredefinedFunction.java
@@ -0,0 +1,113 @@
+package ru.mipt.java2016.homework.g597.bogdanov.task4.REST.functions;
+
+import ru.mipt.java2016.homework.base.task1.ParsingException;
+
+import java.util.*;
+
+/**
+ * Created by Semyo_000 on 20.12.2016.
+ */
+public class PredefinedFunction implements IEvaluateableFunction {
+ public enum PredefinedFunctionType {
+ SIN, COS, TG, SQRT, POW, ABS, SIGN, LOG, LOG2, RND, MAX, MIN
+ }
+
+ public static final Map PREDEFINED_FUNCTION_TYPE_NUM_ARGUMENTS_MAP;
+
+ static {
+ Map predefinedFunctionTypeNumArguments = new HashMap<>();
+ predefinedFunctionTypeNumArguments.put(PredefinedFunctionType.SIN, 1);
+ predefinedFunctionTypeNumArguments.put(PredefinedFunctionType.COS, 1);
+ predefinedFunctionTypeNumArguments.put(PredefinedFunctionType.TG, 1);
+ predefinedFunctionTypeNumArguments.put(PredefinedFunctionType.SQRT, 1);
+ predefinedFunctionTypeNumArguments.put(PredefinedFunctionType.POW, 2);
+ predefinedFunctionTypeNumArguments.put(PredefinedFunctionType.ABS, 1);
+ predefinedFunctionTypeNumArguments.put(PredefinedFunctionType.SIGN, 1);
+ predefinedFunctionTypeNumArguments.put(PredefinedFunctionType.LOG, 1);
+ predefinedFunctionTypeNumArguments.put(PredefinedFunctionType.LOG2, 1);
+ predefinedFunctionTypeNumArguments.put(PredefinedFunctionType.RND, 0);
+ predefinedFunctionTypeNumArguments.put(PredefinedFunctionType.MAX, 2);
+ predefinedFunctionTypeNumArguments.put(PredefinedFunctionType.MIN, 2);
+ PREDEFINED_FUNCTION_TYPE_NUM_ARGUMENTS_MAP =
+ Collections.unmodifiableMap(predefinedFunctionTypeNumArguments);
+ }
+
+ private static final Random RANDOM_NUMBER_GENERATOR = new Random(1337);
+
+ private final PredefinedFunctionType functionType;
+ private final List arguments = new ArrayList<>();
+
+ public PredefinedFunction(PredefinedFunctionType functionType) {
+ this.functionType = functionType;
+ for (int numArgumentsCounter = 0;
+ numArgumentsCounter < PREDEFINED_FUNCTION_TYPE_NUM_ARGUMENTS_MAP.get(functionType);
+ ++numArgumentsCounter) {
+ arguments.add(0.0);
+ }
+ }
+
+ @Override
+ public Double evaluate() throws ParsingException {
+ Double result = 0.0;
+
+ switch (functionType) {
+ case SIN:
+ result = Math.sin(arguments.get(0));
+ break;
+ case COS:
+ result = Math.cos(arguments.get(0));
+ break;
+ case TG:
+ result = Math.tan(arguments.get(0));
+ break;
+ case SQRT:
+ result = Math.sqrt(arguments.get(0));
+ break;
+ case POW:
+ result = Math.pow(arguments.get(0), arguments.get(1));
+ break;
+ case ABS:
+ result = Math.abs(arguments.get(0));
+ break;
+ case SIGN:
+ result = Math.signum(arguments.get(0));
+ break;
+ case LOG:
+ result = Math.log(arguments.get(0));
+ break;
+ case LOG2:
+ result = Math.log(arguments.get(0)) / Math.log(2);
+ break;
+ case RND:
+ synchronized (RANDOM_NUMBER_GENERATOR) {
+ result = RANDOM_NUMBER_GENERATOR.nextDouble();
+ }
+ break;
+ case MAX:
+ result = Math.max(arguments.get(0), arguments.get(1));
+ break;
+ case MIN:
+ result = Math.min(arguments.get(0), arguments.get(1));
+ break;
+ default:
+ break;
+ }
+
+ return result;
+ }
+
+ @Override
+ public void setArguments(List arguments) throws ParsingException {
+ if (this.arguments.size() != arguments.size()) {
+ throw new ParsingException("Number of arguments to be set is invalid.");
+ }
+ for (int argumentsIndex = 0; argumentsIndex < arguments.size(); ++argumentsIndex) {
+ this.arguments.set(argumentsIndex, arguments.get(argumentsIndex));
+ }
+ }
+
+ @Override
+ public boolean isPredefined() {
+ return true;
+ }
+}
diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorApplication.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorApplication.java
new file mode 100644
index 000000000..01720f0ab
--- /dev/null
+++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorApplication.java
@@ -0,0 +1,39 @@
+package ru.mipt.java2016.homework.g597.bogdanov.task4.server;
+
+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.g597.bogdanov.task4.REST.RESTCalculator;
+import ru.mipt.java2016.homework.g597.bogdanov.task4.REST.IFunctionalCalculator;
+
+/**
+ * Created by Semyo_000 on 20.12.2016.
+ */
+@EnableAutoConfiguration
+@Configuration
+@ComponentScan(basePackageClasses = CalculatorApplication.class)
+public class CalculatorApplication {
+
+ @Bean
+ public IFunctionalCalculator calculator() {
+ return new RESTCalculator();
+ }
+
+ @Bean
+ public EmbeddedServletContainerCustomizer customizer(
+ @Value("${ru.mipt.java2016.homework.g597.bogdanov.task4.server.httpPort:9001}") int port) {
+ return container -> container.setPort(port);
+ }
+
+ public static void main(String[] args) {
+ SpringApplication application = new SpringApplication(CalculatorApplication.class);
+ application.setBannerMode(Banner.Mode.OFF);
+ application.run(args);
+ }
+}
diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorController.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorController.java
new file mode 100644
index 000000000..c225cfce3
--- /dev/null
+++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorController.java
@@ -0,0 +1,134 @@
+package ru.mipt.java2016.homework.g597.bogdanov.task4.server;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.Authentication;
+import org.springframework.web.bind.annotation.*;
+
+import ru.mipt.java2016.homework.base.task1.ParsingException;
+import ru.mipt.java2016.homework.g597.bogdanov.task4.REST.RESTCalculator;
+import ru.mipt.java2016.homework.g597.bogdanov.task4.REST.functions.CalculatorFunctionObject;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Arrays;
+
+/**
+ * Created by Semyo_000 on 20.12.2016.
+ */
+
+@RestController
+public class CalculatorController {
+ private static final Logger LOG = LoggerFactory.getLogger(CalculatorController.class);
+
+ @Autowired
+ private RESTCalculator calculator;
+
+ @Autowired
+ private CalculatorDao calculatorDao;
+
+ @RequestMapping(path = "/variable/{name}", method = RequestMethod.GET, produces = "text/plain")
+ public String getVariable(Authentication authentication, @PathVariable String name) throws ParsingException {
+ String ourName = authentication.getName();
+ Double result = calculatorDao.getVariable(ourName, name);
+ return name + " = " + result + "\n";
+ }
+
+ @RequestMapping(path = "/variable/{name}", method = RequestMethod.DELETE, produces = "text/plain")
+ public String deleteVariable(Authentication authentication, @PathVariable String name) throws ParsingException {
+ String username = authentication.getName();
+ boolean success = calculatorDao.deleteVariable(username, name);
+ if (success) {
+ return name + " deleted\n";
+ } else {
+ return name + " does not exist\n";
+ }
+ }
+
+ @RequestMapping(path = "/variable/{name}", method = RequestMethod.PUT,
+ consumes = "text/plain", produces = "text/plain")
+ public String addVariable(Authentication authentication, @PathVariable String name, @RequestBody String value)
+ throws ParsingException {
+ String username = authentication.getName();
+ calculatorDao.addVariable(username, name, Double.parseDouble(value));
+ return "Variable added\n";
+ }
+
+ @RequestMapping(path = "/variable", method = RequestMethod.GET, produces = "text/plain")
+ public String getVariables(Authentication authentication) throws ParsingException {
+ String username = authentication.getName();
+ Map result = calculatorDao.getVariables(username);
+ return String.join(", ", result.keySet()) + "\n" +
+ "";
+ }
+
+ @RequestMapping(path = "/function/{name}", method = RequestMethod.GET, produces = "text/plain")
+ public String getFunction(Authentication authentication, @PathVariable String name) throws ParsingException {
+ String ourName = authentication.getName();
+ CalculatorFunctionObject result = calculatorDao.getFunction(ourName, name);
+ return name + "(" + String.join(", ", result.getArguments()) + ")" + " = " + result.getExpression() + "\n";
+ }
+
+ @RequestMapping(path = "/function/{name}", method = RequestMethod.DELETE, produces = "text/plain")
+ public String deleteFunction(Authentication authentication, @PathVariable String name) throws ParsingException {
+ String username = authentication.getName();
+ boolean success = calculatorDao.deleteFunction(username, name);
+ if (success) {
+ return name + " deleted\n";
+ } else {
+ return name + " not exists\n";
+ }
+ }
+
+ @RequestMapping(path = "/function/{name}", method = RequestMethod.PUT,
+ consumes = "text/plain", produces = "text/plain")
+ public String addFunction(Authentication authentication, @PathVariable String name,
+ @RequestParam(value = "args") String args,
+ @RequestBody String expression)
+ throws ParsingException {
+ String username = authentication.getName();
+ List arguments = Arrays.asList(args.split(","));
+ calculatorDao.addFunction(username, name, arguments, expression);
+ return "Function added\n";
+ }
+
+ @RequestMapping(path = "/function", method = RequestMethod.GET, produces = "text/plain")
+ public String getFunctions(Authentication authentication) throws ParsingException {
+ String username = authentication.getName();
+ Map result = calculatorDao.getFunctions(username);
+ return String.join(", ", result.keySet()) + "\n";
+ }
+
+ @RequestMapping(path = "/eval", method = RequestMethod.POST, consumes = "text/plain", produces = "text/plain")
+ public String calculate(Authentication authentication, @RequestBody String expression) throws ParsingException {
+ LOG.debug("Calculation request: [" + expression + "]");
+ String username = authentication.getName();
+ Map variables = calculatorDao.getVariables(username);
+ for (Map.Entry entry : variables.entrySet()) {
+ calculator.putVariable(entry.getKey(), entry.getValue());
+ }
+ Map functions = calculatorDao.getFunctions(username);
+ for (Map.Entry entry : functions.entrySet()) {
+ calculator.putFunction(entry.getKey(), entry.getValue().getExpression(), entry.getValue().getArguments());
+ }
+ double result = calculator.calculate(expression);
+ calculator = new RESTCalculator();
+ return result + "\n";
+ }
+
+ @RequestMapping(path = "/register/{username}", method = RequestMethod.PUT,
+ consumes = "text/plain", produces = "text/plain")
+ public String register(@PathVariable String username, @RequestBody String pswd)
+ throws ParsingException {
+ LOG.debug("New user: [" + username + ' ' + pswd + "]");
+ boolean success = calculatorDao.addUserIfNotExists(username, pswd, true);
+ if (success) {
+ LOG.trace("Success");
+ return "You have been successfully registered\n";
+ } else {
+ LOG.trace("Fail");
+ return "This user already exists\n";
+ }
+ }
+}
\ No newline at end of file
diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorDao.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorDao.java
new file mode 100644
index 000000000..01a285433
--- /dev/null
+++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorDao.java
@@ -0,0 +1,212 @@
+package ru.mipt.java2016.homework.g597.bogdanov.task4.server;
+
+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 ru.mipt.java2016.homework.base.task1.ParsingException;
+import ru.mipt.java2016.homework.g597.bogdanov.task4.REST.functions.CalculatorFunctionObject;
+
+import javax.annotation.PostConstruct;
+import javax.sql.DataSource;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.*;
+
+/**
+ * Created by Semyo_000 on 20.12.2016.
+ */
+@Repository
+public class CalculatorDao {
+ private static final Logger LOG = LoggerFactory.getLogger(CalculatorDao.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)");
+ jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS billing.variables " +
+ "(username VARCHAR, name VARCHAR, value DOUBLE)");
+ jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS billing.functions " +
+ "(username VARCHAR, name VARCHAR, arguments VARCHAR, expression VARCHAR)");
+ addUserIfNotExists("username", "password", true);
+ }
+
+ boolean addUserIfNotExists(String username, String password, boolean enabled) {
+ try {
+ loadUser(username);
+ return false;
+ } catch (EmptyResultDataAccessException e) {
+ jdbcTemplate.update("INSERT INTO billing.users VALUES (?, ?, ?)",
+ new Object[]{username, password, enabled});
+ return true;
+ }
+ }
+
+ public Double getVariable(String username, String variable) {
+ return jdbcTemplate.queryForObject(
+ "SELECT username, name, value FROM billing.variables WHERE username = ? AND name = ?",
+ new Object[]{username, variable},
+ new RowMapper() {
+ @Override
+ public Double mapRow(ResultSet rs, int rowNum) throws SQLException {
+ return rs.getDouble("value");
+ }
+ }
+ );
+ }
+
+ Map getVariables(String username) {
+ try {
+ return jdbcTemplate.queryForObject(
+ "SELECT username, name, value FROM billing.variables WHERE username = ?",
+ new Object[]{username},
+ new RowMapper>() {
+ @Override
+ public HashMap mapRow(ResultSet rs, int rowNum) throws SQLException {
+ HashMap result = new HashMap<>();
+ while (true) {
+ result.put(rs.getString("name"), rs.getDouble("value"));
+ if (!rs.next()) {
+ break;
+ }
+ }
+ return result;
+ }
+ }
+ );
+ } catch (EmptyResultDataAccessException e) {
+ HashMap result = new HashMap<>();
+ return result;
+ }
+ }
+
+ boolean deleteVariable(String username, String name) throws ParsingException {
+ try {
+ getVariable(username, name);
+ jdbcTemplate.update("DELETE FROM billing.variables WHERE username = ? AND name = ?",
+ new Object[]{username, name});
+ return true;
+ } catch (EmptyResultDataAccessException e) {
+ return false;
+ }
+ }
+
+ void addVariable(String username, String name, Double value) throws ParsingException {
+ try {
+ getVariable(username, name);
+ jdbcTemplate.update("DELETE FROM billing.variables WHERE username = ? AND name = ?",
+ new Object[]{username, name});
+ jdbcTemplate.update("INSERT INTO billing.variables VALUES (?, ?, ?)",
+ new Object[]{username, name, value});
+ } catch (EmptyResultDataAccessException e) {
+ jdbcTemplate.update("INSERT INTO billing.variables VALUES (?, ?, ?)",
+ new Object[]{username, name, value});
+ }
+ }
+
+ public CalculatorFunctionObject getFunction(String username, String function) {
+ return jdbcTemplate.queryForObject(
+ "SELECT username, name, arguments, expression FROM billing.functions WHERE username = ? AND name = ?",
+ new Object[]{username, function},
+ new RowMapper() {
+ @Override
+ public CalculatorFunctionObject mapRow(ResultSet rs, int rowNum) throws SQLException {
+ List arguments = Arrays.asList(rs.getString("arguments").split(" "));
+ String expression = rs.getString("expression");
+ return new CalculatorFunctionObject(expression, arguments);
+ }
+ }
+ );
+ }
+
+ Map getFunctions(String username) {
+ try {
+ return jdbcTemplate.queryForObject(
+ "SELECT username, name, arguments, expression FROM billing.functions WHERE username = ?",
+ new Object[]{username},
+ new RowMapper>() {
+ @Override
+ public HashMap mapRow(ResultSet rs,
+ int rowNum) throws SQLException {
+ HashMap result = new HashMap<>();
+ while (true) {
+ String name = rs.getString("name");
+ List arguments = Arrays.asList(rs.getString("arguments").split(" "));
+ String expression = rs.getString("expression");
+ result.put(name, new CalculatorFunctionObject(expression, arguments));
+ if (!rs.next()) {
+ break;
+ }
+ }
+ return result;
+ }
+ }
+ );
+ } catch (EmptyResultDataAccessException e) {
+ HashMap result = new HashMap<>();
+ return result;
+ }
+ }
+
+ boolean deleteFunction(String username, String function) throws ParsingException {
+ try {
+ getFunction(username, function);
+ jdbcTemplate.update("DELETE FROM billing.variables WHERE username = ? AND name = ?",
+ new Object[]{username, function});
+ return true;
+ } catch (EmptyResultDataAccessException e) {
+ return false;
+ }
+ }
+
+ void addFunction(String username, String function, List arguments,
+ String expression) throws ParsingException {
+ try {
+ getFunction(username, function);
+ jdbcTemplate.update("DELETE FROM billing.functions WHERE username = ? AND name = ?",
+ new Object[]{username, function});
+ String stringArguments = String.join(" ", arguments);
+ jdbcTemplate.update("INSERT INTO billing.functions VALUES (?, ?, ?, ?)",
+ new Object[]{username, function, stringArguments, expression});
+ } catch (EmptyResultDataAccessException e) {
+ String stringArguments = String.join(" ", arguments);
+ jdbcTemplate.update("INSERT INTO billing.functions VALUES (?, ?, ?, ?)",
+ new Object[]{username, function, stringArguments, expression});
+ }
+ }
+
+ public CalculatorUser 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 CalculatorUser mapRow(ResultSet rs, int rowNum) throws SQLException {
+ return new CalculatorUser(
+ rs.getString("username"),
+ rs.getString("password"),
+ rs.getBoolean("enabled")
+ );
+ }
+ }
+ );
+ }
+}
diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorDatabaseConfiguration.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorDatabaseConfiguration.java
new file mode 100644
index 000000000..c84aecdfc
--- /dev/null
+++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorDatabaseConfiguration.java
@@ -0,0 +1,29 @@
+package ru.mipt.java2016.homework.g597.bogdanov.task4.server;
+
+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;
+
+/**
+ * Created by Semyo_000 on 20.12.2016.
+ */
+@Configuration
+public class CalculatorDatabaseConfiguration {
+ @Bean
+ public DataSource billingDataSource(
+ @Value("${ru.mipt.java2016.homework.g597.bogdanov.task4.server.jdbcUrl}") String jdbcUrl,
+ @Value("${ru.mipt.java2016.homework.g597.bogdanov.task4.server.username:}") String username,
+ @Value("${ru.mipt.java2016.homework.g597.bogdanov.task4.server.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);
+ }
+}
diff --git a/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorUser.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorUser.java
new file mode 100644
index 000000000..c4cfd3f79
--- /dev/null
+++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/CalculatorUser.java
@@ -0,0 +1,68 @@
+package ru.mipt.java2016.homework.g597.bogdanov.task4.server;
+
+/**
+ * Created by Semyo_000 on 20.12.2016.
+ */
+public class CalculatorUser {
+ private final String username;
+ private final String password;
+ private final boolean enabled;
+
+ public CalculatorUser(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 "CalculatorUser{" +
+ "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;
+ }
+
+ CalculatorUser that = (CalculatorUser) o;
+
+ return enabled == that.enabled &&
+ username.equals(that.username) &&
+ 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-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/SecurityServiceConfiguration.java b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/SecurityServiceConfiguration.java
new file mode 100644
index 000000000..c76647de9
--- /dev/null
+++ b/homework-g597-bogdanov/src/main/java/ru/mipt/java2016/homework/g597/bogdanov/task4/server/SecurityServiceConfiguration.java
@@ -0,0 +1,60 @@
+package ru.mipt.java2016.homework.g597.bogdanov.task4.server;
+
+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;
+
+/**
+ * Created by Semyo_000 on 20.12.2016.
+ */
+@Configuration
+@EnableWebSecurity
+public class SecurityServiceConfiguration extends WebSecurityConfigurerAdapter {
+ private static final Logger LOG = LoggerFactory.getLogger(SecurityServiceConfiguration.class);
+
+ @Autowired
+ private CalculatorDao calculatorDao;
+
+ @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("/calculate/**").authenticated()
+ .antMatchers("/variable/**").authenticated()
+ .antMatchers("/function/**").authenticated()
+ .anyRequest().permitAll();
+ }
+
+ @Autowired
+ public void registerGlobalAuthentication(AuthenticationManagerBuilder auth) throws Exception {
+ LOG.info("Registering global user details service");
+ auth.userDetailsService(username -> {
+ try {
+ CalculatorUser user = calculatorDao.loadUser(username);
+ return new User(
+ user.getUsername(),
+ user.getPassword(),
+ Collections.singletonList(() -> "AUTH")
+ );
+ } catch (EmptyResultDataAccessException e) {
+ LOG.warn("No such user: " + username);
+ throw new UsernameNotFoundException(username);
+ }
+ });
+ }
+}
\ No newline at end of file