diff --git a/homework-g596-kozlova/pom.xml b/homework-g596-kozlova/pom.xml
index f8aae5c72..c5af0bc0b 100644
--- a/homework-g596-kozlova/pom.xml
+++ b/homework-g596-kozlova/pom.xml
@@ -10,6 +10,12 @@
4.0.0
homework-g596-kozlova
+ pom
+ 1.0.0
+
+
+ 1.4.2.RELEASE
+
@@ -27,6 +33,41 @@
homework-tests
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
+
\ No newline at end of file
diff --git a/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/Application.java b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/Application.java
new file mode 100644
index 000000000..4e2b7aa69
--- /dev/null
+++ b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/Application.java
@@ -0,0 +1,33 @@
+package ru.mipt.java2016.homework.g596.kozlova.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;
+
+@EnableAutoConfiguration
+@Configuration
+@ComponentScan(basePackageClasses = Application.class)
+public class Application {
+
+ @Bean
+ public Calculator calculator() {
+ return Calculator.INSTANCE;
+ }
+
+ @Bean
+ public EmbeddedServletContainerCustomizer customizer(
+ @Value("${ru.mipt.java2016.homework.g596.kozlova.task4.httpPort:9001}") int port) {
+ return container -> container.setPort(port);
+ }
+
+ public static void main(String[] args) {
+ SpringApplication application = new SpringApplication(Application.class);
+ application.setBannerMode(Banner.Mode.OFF);
+ application.run(args);
+ }
+}
\ No newline at end of file
diff --git a/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/BillingDao.java b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/BillingDao.java
new file mode 100644
index 000000000..314edfaa3
--- /dev/null
+++ b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/BillingDao.java
@@ -0,0 +1,159 @@
+package ru.mipt.java2016.homework.g596.kozlova.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.stereotype.Repository;
+import ru.mipt.java2016.homework.base.task1.ParsingException;
+
+import javax.annotation.PostConstruct;
+import javax.sql.DataSource;
+import java.sql.ResultSet;
+import java.util.List;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.HashMap;
+
+@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);
+ LOG.trace("Initializing");
+ 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, expression VARCHAR)");
+ jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS billing.functions " +
+ "(username VARCHAR, name VARCHAR, arguments VARCHAR, expression VARCHAR)");
+ createNewUser("userName", "password", true);
+ }
+
+ public boolean createNewUser(String userName, String password, boolean enabled) {
+ try {
+ loadUser(userName);
+ return false;
+ } catch (EmptyResultDataAccessException e) {
+ jdbcTemplate.update("INSERT INTO billing.users VALUES (?, ?, ?)", userName, password, enabled);
+ return true;
+ }
+ }
+
+ 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},
+ (ResultSet resultSet, int numberOfRow) -> {
+ return new BillingUser(resultSet.getString("userName"), resultSet.getString("password"),
+ resultSet.getBoolean("enabled"));
+ }
+ );
+ }
+
+ public Variable getVariable(String userName, String variable) {
+ return jdbcTemplate.queryForObject(
+ "SELECT userName, name, value, expression FROM billing.variables WHERE userName = ? AND name = ?",
+ new Object[]{userName, variable},
+ (ResultSet resultSet, int numberOfRow) -> {
+ return new Variable(
+ resultSet.getString("userName"),
+ resultSet.getString("name"),
+ resultSet.getDouble("value"),
+ resultSet.getString("expression"));
+ }
+ );
+ }
+
+ public Map getVariables(String userName) {
+ try {
+ return jdbcTemplate.queryForObject(
+ "SELECT userName, name, value, expression FROM billing.variables WHERE userName = ?",
+ new Object[]{userName},
+ (ResultSet resultSet, int numberOfRow) -> {
+ Map map = new HashMap<>();
+ while (!resultSet.next()) {
+ map.put(resultSet.getString("name"), Double.toString(resultSet.getDouble("value")));
+ }
+ return map;
+ }
+ );
+ } catch (EmptyResultDataAccessException e) {
+ return new HashMap<>();
+ }
+ }
+
+ public void deleteVariable(String userName, String name) throws ParsingException {
+ try {
+ getVariable(userName, name);
+ jdbcTemplate.update("DELETE FROM billing.variables WHERE userName = ? AND name = ?", userName, name);
+ } catch (EmptyResultDataAccessException e) {
+ throw new ParsingException("Can't delete");
+ }
+ }
+
+ public void addVariable(String userName, String name, Double value, String expression) throws ParsingException {
+ jdbcTemplate.update("MERGE INTO billing.variables VALUES (?, ?, ?, ?)", userName, name, value, expression);
+ }
+
+ public Function getFunction(String userName, String function) {
+ return jdbcTemplate.queryForObject(
+ "SELECT userName, name, arguments, expression FROM billing.functions WHERE userName = ? AND name = ?",
+ new Object[]{userName, function},
+ (ResultSet resultSet, int numberOfRow) -> {
+ String name = resultSet.getString("name");
+ List arguments = Arrays.asList(resultSet.getString("arguments").split(" "));
+ String expression = resultSet.getString("expression");
+ return new Function(userName, name, arguments, expression);
+ }
+ );
+ }
+
+ public Map getFunctions(String userName) {
+ try {
+ return jdbcTemplate.queryForObject(
+ "SELECT userName, name, arguments, expression FROM billing.functions WHERE userName = ?",
+ new Object[]{userName},
+ (ResultSet resultSet, int numberOfRow) -> {
+ Map map = new HashMap<>();
+ while (true) {
+ String name = resultSet.getString("name");
+ List arguments = Arrays.asList(resultSet.getString("arguments").split(" "));
+ String expression = resultSet.getString("expression");
+ map.put(name, new Function(userName, name, arguments, expression));
+ if (!resultSet.next()) {
+ break;
+ }
+ }
+ return map;
+ }
+ );
+ } catch (EmptyResultDataAccessException e) {
+ return new HashMap<>();
+ }
+ }
+
+ public void deleteFunction(String userName, String name) throws ParsingException {
+ try {
+ getFunction(userName, name);
+ jdbcTemplate.update("DELETE FROM billing.functions WHERE userName = ? AND name = ?", userName, name);
+ } catch (EmptyResultDataAccessException e) {
+ throw new ParsingException("Can't delete");
+ }
+ }
+
+ public void addFunction(String userName, String name, List arguments, String expression)
+ throws ParsingException {
+ jdbcTemplate.update("MERGE INTO billing.functions VALUES (?, ?, ?, ?)", userName, name, arguments, expression);
+ }
+}
\ No newline at end of file
diff --git a/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/BillingDatabaseConfiguration.java b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/BillingDatabaseConfiguration.java
new file mode 100644
index 000000000..8f25a70af
--- /dev/null
+++ b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/BillingDatabaseConfiguration.java
@@ -0,0 +1,26 @@
+package ru.mipt.java2016.homework.g596.kozlova.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.g596.kozlova.task4.jdbcUrl}") String jdbcUrlAdress,
+ @Value("${ru.mipt.java2016.homework.g596.kozlova.task4.userName:}") String userName,
+ @Value("${ru.mipt.java2016.homework.g596.kozlova.task4.password:}") String password
+ ) {
+ HikariConfig config = new HikariConfig();
+ config.setDriverClassName(org.h2.Driver.class.getName());
+ config.setJdbcUrl(jdbcUrlAdress);
+ config.setUsername(userName);
+ config.setPassword(password);
+ return new HikariDataSource(config);
+ }
+}
\ No newline at end of file
diff --git a/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/BillingUser.java b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/BillingUser.java
new file mode 100644
index 000000000..1b1fb42f0
--- /dev/null
+++ b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/BillingUser.java
@@ -0,0 +1,53 @@
+package ru.mipt.java2016.homework.g596.kozlova.task4;
+
+public class BillingUser {
+
+ private final String userName;
+ private final String password;
+ private final boolean enabled;
+
+ public BillingUser(String u, String p, boolean e) {
+ if (u.equals(null) || p.equals(null)) {
+ throw new IllegalArgumentException("Incorrect data");
+ }
+ userName = u;
+ password = p;
+ enabled = e;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ @Override
+ public String toString() {
+ return "BillingUser{ userName='" + userName + "\', password='" + password + '\'' + ", enabled=" + enabled + "}";
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ BillingUser that = (BillingUser) obj;
+ if (!userName.equals(that.userName) || !password.equals(that.password) || enabled != that.enabled) {
+ return false;
+ }
+ return true;
+ }
+
+ @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-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/Calculator.java b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/Calculator.java
new file mode 100644
index 000000000..252fb19bc
--- /dev/null
+++ b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/Calculator.java
@@ -0,0 +1,363 @@
+package ru.mipt.java2016.homework.g596.kozlova.task4;
+
+import ru.mipt.java2016.homework.base.task1.ParsingException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.LinkedList;
+
+import static java.lang.Character.digit;
+import static java.lang.Character.isDigit;
+import static java.lang.Character.isLetter;
+import static java.lang.Character.isWhitespace;
+
+public class Calculator {
+
+ private static final Logger LOG = LoggerFactory.getLogger(Calculator.class);
+
+ public static final Calculator INSTANCE = new Calculator();
+
+ public double calculate(Map variables, String expression) throws ParsingException {
+ if (expression == null || isSplitedNumbers(expression) || expression.equals("")) {
+ throw new ParsingException("Bad expressions");
+ }
+ return calc(variables, expression);
+ }
+
+ private double calc(Map variables, String expression)
+ throws ParsingException {
+ LinkedList operations = new LinkedList<>();
+ LinkedList numbers = new LinkedList<>();
+ expression = expression.replaceAll("\\s", "");
+ expression = "(" + expression + ")";
+ double lastNumber = 0;
+ boolean readNumber = false;
+ boolean readVariable = false;
+ String variable = "";
+ boolean isPoint = false;
+ boolean isSomeAfterPoint = false;
+ double afterPoint = 1;
+ boolean canBeUnary = true;
+ boolean needJump = false;
+ int jump = -1;
+ for (int i = 0; i < expression.length(); ++i) {
+ if (needJump) {
+ if (i < jump) {
+ continue;
+ }
+ needJump = false;
+ }
+ afterPoint /= 10;
+ char currentSymbol = expression.charAt(i);
+ if (isDigit(currentSymbol) || currentSymbol == '.' || isLetter(currentSymbol)) {
+ canBeUnary = false;
+ if (isDigit(currentSymbol)) {
+ readNumber = true;
+ if (isPoint) {
+ isSomeAfterPoint = true;
+ lastNumber += afterPoint * digit(currentSymbol, 10);
+ continue;
+ }
+ lastNumber = lastNumber * 10 + digit(currentSymbol, 10);
+ continue;
+ }
+ if (isLetter(currentSymbol)) {
+ readVariable = true;
+ variable += currentSymbol;
+ continue;
+ }
+ if (isPoint || !readNumber) {
+ throw new ParsingException("Bad expression");
+ }
+ isPoint = true;
+ afterPoint = 1;
+ continue;
+ }
+ if (readNumber || !isPossibleSymbol(currentSymbol)) {
+ if ((isPoint && !isSomeAfterPoint) || !isPossibleSymbol(currentSymbol)) {
+ throw new ParsingException("Bad expression");
+ }
+ numbers.push(lastNumber);
+ lastNumber = 0;
+ isPoint = false;
+ readNumber = false;
+ }
+ if (readVariable) {
+ if (!variables.containsKey(variable)) {
+ if (variable.equals("rnd")) {
+ int position = i;
+ if (expression.charAt(position) != '(' || position + 1 >= expression.length()
+ || expression.charAt(position + 1) != ')') {
+ throw new ParsingException("Bad expression");
+ }
+ numbers.push(Math.random());
+ variable = "";
+ readVariable = false;
+ needJump = true;
+ jump = position + 2;
+ continue;
+ }
+ if (isFunctionWithOneParameter(variable)) {
+ StringBuilder parameter = new StringBuilder();
+ int position = actionsIfOneParameter(expression, i, parameter);
+ numbers.push(calcFunctionWithOneParameter(variable,
+ calc(variables, parameter.toString())));
+ variable = "";
+ readVariable = false;
+ needJump = true;
+ jump = position;
+ continue;
+ }
+ if (isFunctionWithTwoParameters(variable)) {
+ StringBuilder firstParameter = new StringBuilder();
+ StringBuilder secondParameter = new StringBuilder();
+ int position = actionsIfTwoParameters(expression, i, firstParameter, secondParameter);
+ LOG.debug(firstParameter + " " + secondParameter);
+ numbers.push(calcFunctionWithTwoParameters(variable,
+ calc(variables, firstParameter.toString()),
+ calc(variables, secondParameter.toString())));
+ variable = "";
+ readVariable = false;
+ needJump = true;
+ jump = position;
+ continue;
+ }
+ throw new ParsingException("Bad expression");
+ }
+ numbers.push(Double.parseDouble(variables.get(variable)));
+ variable = "";
+ readVariable = false;
+ }
+ if (canBeUnary && (currentSymbol == '+' || currentSymbol == '-')) {
+ if (currentSymbol == '+') {
+ currentSymbol = '&';
+ } else {
+ currentSymbol = '@';
+ }
+ }
+ if (currentSymbol == '(') {
+ operations.push(currentSymbol);
+ canBeUnary = true;
+ continue;
+ }
+ if (currentSymbol == ')') {
+ char last = '^';
+ while (operations.size() != 0) {
+ last = operations.pop();
+ if (last == '(') {
+ break;
+ }
+ calcSimpleOperation(last, numbers);
+ }
+ canBeUnary = false;
+ if (last != '(') {
+ LOG.debug(Integer.toString(i));
+ throw new ParsingException("Bad expression");
+ }
+ continue;
+ }
+ while (operations.size() != 0) {
+ char back = operations.pop();
+ if (getPriorityOperation(back) >= getPriorityOperation(currentSymbol)) {
+ calcSimpleOperation(back, numbers);
+ } else {
+ operations.push(back);
+ break;
+ }
+ }
+ operations.push(currentSymbol);
+ canBeUnary = true;
+ }
+ if (numbers.size() != 1 || operations.size() != 0) {
+ throw new ParsingException("Bad expression");
+ }
+ double answer = numbers.pop();
+ LOG.debug(Double.toString(answer) + " " + expression);
+ return answer;
+ }
+
+ private boolean isFunctionWithOneParameter(String f) {
+ return f.equals("sin") || f.equals("cos") || f.equals("tg") || f.equals("sqrt") ||
+ f.equals("abs") || f.equals("sign") || f.equals("log2");
+ }
+
+ private boolean isFunctionWithTwoParameters(String f) {
+ return (f.equals("pow") || f.equals("log") || f.equals("max") || f.equals("min"));
+ }
+
+ private void calcSimpleOperation(char symbol, LinkedList numbers) throws ParsingException {
+ if (isUnary(symbol)) {
+ if (numbers.size() < 1) {
+ throw new ParsingException("Not enough operands for unary operation");
+ }
+ if (symbol == '@') {
+ double x = numbers.pop();
+ numbers.push(-x);
+ }
+ return;
+ }
+ if (numbers.size() < 2) {
+ throw new ParsingException("Not enough operands for operation");
+ }
+ double x = numbers.pop();
+ double y = numbers.pop();
+ if (symbol == '+') {
+ numbers.push(y + x);
+ }
+ if (symbol == '-') {
+ numbers.push(y - x);
+ }
+ if (symbol == '*') {
+ numbers.push(y * x);
+ }
+ if (symbol == '/') {
+ numbers.push(y / x);
+ }
+ }
+
+ private Double calcFunctionWithOneParameter(String f, Double x) {
+ if (f.equals("sin")) {
+ return Math.sin(x);
+ }
+ if (f.equals("cos")) {
+ return Math.cos(x);
+ }
+ if (f.equals("tg")) {
+ return Math.tan(x);
+ }
+ if (f.equals("sqrt")) {
+ return Math.sqrt(x);
+ }
+ if (f.equals("abs")) {
+ return Math.abs(x);
+ }
+ if (f.equals("sign")) {
+ return Math.signum(x);
+ }
+ if (f.equals("log2")) {
+ return Math.log(x) / Math.log(2);
+ }
+ return 0.0;
+ }
+
+ private double calcFunctionWithTwoParameters(String f, Double x, Double y) {
+ if (f.equals("pow")) {
+ return Math.pow(x, y);
+ }
+ if (f.equals("log")) {
+ return Math.log(x) / Math.log(y);
+ }
+ if (f.equals("max")) {
+ return Math.max(x, y);
+ }
+ if (f.equals("min")) {
+ return Math.min(x, y);
+ }
+ return 0.0;
+ }
+
+ private int getPriorityOperation(char symbol) {
+ if (isUnary(symbol)) {
+ return 2;
+ }
+ if (symbol == '+' || symbol == '-') {
+ return 0;
+ }
+ if (symbol == '*' || symbol == '/') {
+ return 1;
+ }
+ return -1;
+ }
+
+ private boolean isUnary(char symbol) {
+ return (symbol == '&' || symbol == '@'); // & := +, @ := -
+ }
+
+ private boolean isPossibleSymbol(char symbol) {
+ return (symbol == '+' || symbol == '-' || symbol == '*' || symbol == '/' || symbol == '(' || symbol == ')');
+ }
+
+ int actionsIfOneParameter(String expression, int i, StringBuilder parameter)
+ throws ParsingException {
+ int position = i;
+ if (expression.charAt(position) != '(') {
+ throw new ParsingException("Bad expression");
+ }
+ int newBalance = 1;
+ position += 1;
+ while (newBalance != 0 && position < expression.length()) {
+ if (expression.charAt(position) == '(') {
+ newBalance += 1;
+ }
+ if (expression.charAt(position) == ')') {
+ newBalance -= 1;
+ }
+ if (newBalance != 0) {
+ parameter.append(expression.charAt(position));
+ }
+ position += 1;
+ }
+ if (position == expression.length() && newBalance != 0) {
+ throw new ParsingException("Bad expression");
+ }
+ return position;
+ }
+
+ int actionsIfTwoParameters(String expression, int i, StringBuilder firstParameter, StringBuilder secondParameter)
+ throws ParsingException {
+ int position = i;
+ boolean secondAlready = false;
+ if (expression.charAt(position) != '(') {
+ throw new ParsingException("Bad expression");
+ }
+ int newBalance = 1;
+ position += 1;
+ while (newBalance != 0 && position < expression.length()) {
+ if (expression.charAt(position) == '(') {
+ newBalance += 1;
+ }
+ if (expression.charAt(position) == ')') {
+ newBalance -= 1;
+ }
+ if (newBalance != 0) {
+ if (newBalance == 1 && expression.charAt(position) == ',') {
+ secondAlready = true;
+ } else {
+ if (!secondAlready) {
+ firstParameter.append(expression.charAt(position));
+ } else {
+ secondParameter.append(expression.charAt(position));
+ }
+ }
+ }
+ position += 1;
+ }
+ if (position == expression.length() && newBalance != 0) {
+ throw new ParsingException("Bad expression");
+ }
+ return position;
+ }
+
+ private boolean isSplitedNumbers(String expression) {
+ Character lastSymbol = '^';
+ for (int i = 0; i < expression.length(); ++i) {
+ if ((isDigit(lastSymbol) || lastSymbol == '.' || isLetter(lastSymbol)) &&
+ (isDigit(expression.charAt(i)) || expression.charAt(i) == '.' ||
+ isLetter(expression.charAt(i))) &&
+ !(isDigit(expression.charAt(i - 1)) || expression.charAt(i - 1) == '.' ||
+ isLetter(expression.charAt(i - 1)))) {
+ return true;
+ }
+ if (!isWhitespace(expression.charAt(i))) {
+ lastSymbol = expression.charAt(i);
+ }
+ }
+ return false;
+ }
+
+ public boolean isPredefinedFunction(String f) {
+ return isFunctionWithOneParameter(f) || isFunctionWithTwoParameters(f) || f.equals("rnd");
+ }
+}
+
diff --git a/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/CalculatorController.java b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/CalculatorController.java
new file mode 100644
index 000000000..d85a5b68f
--- /dev/null
+++ b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/CalculatorController.java
@@ -0,0 +1,126 @@
+package ru.mipt.java2016.homework.g596.kozlova.task4;
+
+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.RestController;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RequestBody;
+import ru.mipt.java2016.homework.base.task1.ParsingException;
+
+import java.util.List;
+import java.util.Map;
+
+import static java.lang.Character.isDigit;
+import static java.lang.Character.isLetter;
+
+@RestController
+@RequestMapping(produces = "text/plain")
+public class CalculatorController {
+
+ private static final Logger LOG = LoggerFactory.getLogger(CalculatorController.class);
+
+ @Autowired
+ private Calculator calculator;
+ @Autowired
+ private BillingDao billingDao;
+
+ @RequestMapping(path = "/variable/{variableName}", method = RequestMethod.GET)
+ public String getVariable(Authentication authentication, @PathVariable String variableName)
+ throws ParsingException {
+ return billingDao.getVariable(authentication.getName(), variableName).getExpression();
+ }
+
+ @RequestMapping(path = "/variable/{variableName}", method = RequestMethod.PUT, consumes = "text/plain")
+ public void putVariable(Authentication authentication, @PathVariable String variableName,
+ @RequestBody String expression) throws ParsingException {
+ billingDao.addVariable(authentication.getName(), variableName,
+ calculator.calculate(billingDao.getVariables(authentication.getName()), expression), expression);
+ }
+
+ @RequestMapping(path = "/variable/{variableName}", method = RequestMethod.DELETE)
+ public void deleteVariable(Authentication authentication, @PathVariable String variableName)
+ throws ParsingException {
+ billingDao.deleteVariable(authentication.getName(), variableName);
+ }
+
+ @RequestMapping(path = "/variable", method = RequestMethod.GET, produces = "text/plain")
+ public String getVariables(Authentication authentication) throws ParsingException {
+ StringBuilder result = new StringBuilder();
+ for (Map.Entry entry : billingDao.getVariables(authentication.getName()).entrySet()) {
+ result.append(entry.getKey());
+ result.append("\n");
+ }
+ return result.toString();
+ }
+
+ @RequestMapping(path = "/function/{functionName}", method = RequestMethod.GET)
+ public String getFunction(Authentication authentication, @PathVariable String functionName)
+ throws ParsingException {
+ if (calculator.isPredefinedFunction(functionName)) {
+ throw new ParsingException("This function is already defined");
+ }
+ return billingDao.getFunction(authentication.getName(), functionName).getExpression();
+ }
+
+ @RequestMapping(path = "/function/{functionName}", method = RequestMethod.PUT, consumes = "text/plain")
+ public void putFunction(Authentication authentication, @PathVariable String functionName,
+ @RequestParam(value = "arguments", defaultValue = "") List arguments,
+ @RequestBody String expression) throws ParsingException {
+ if (!checkName(functionName)) {
+ throw new ParsingException("Invalid name");
+ }
+ if (calculator.isPredefinedFunction(functionName)) {
+ throw new ParsingException("This function is already defined");
+ }
+ billingDao.addFunction(authentication.getName(), functionName, arguments, expression);
+ }
+
+ @RequestMapping(path = "/function/{functionName}", method = RequestMethod.DELETE)
+ public void deleteFunction(Authentication authentication, @PathVariable String functionName)
+ throws ParsingException {
+ billingDao.deleteFunction(authentication.getName(), functionName);
+ }
+
+ @RequestMapping(path = "/function/", method = RequestMethod.GET)
+ public String getFunctions(Authentication authentication) throws ParsingException {
+ StringBuilder result = new StringBuilder();
+ for (Map.Entry entry : billingDao.getFunctions(authentication.getName()).entrySet()) {
+ result.append(entry.getKey());
+ result.append("\n");
+ }
+ return result.toString();
+ }
+
+ @RequestMapping(path = "/eval", method = RequestMethod.POST, consumes = "text/plain")
+ public String eval(Authentication authentication, @RequestBody String expression) throws ParsingException {
+ LOG.debug("Evaluation request: [" + expression + "]");
+ double result = calculator.calculate(billingDao.getVariables(authentication.getName()), expression);
+ LOG.trace("Result: " + Double.toString(result));
+ return Double.toString(result);
+ }
+
+ @RequestMapping(path = "/newUser/{userName}", method = RequestMethod.PUT, consumes = "text/plain")
+ public void newUser(@PathVariable String userName, @RequestBody String password) throws ParsingException {
+ LOG.debug("New user: [" + userName + ' ' + password + "]");
+ if (billingDao.createNewUser(userName, password, true)) {
+ LOG.trace("Success");
+ } else {
+ LOG.trace("Fail");
+ }
+ }
+
+ private static boolean checkName(String name) {
+ for (int i = 0; i < name.length(); ++i) {
+ char ch = name.charAt(i);
+ if (!(isLetter(ch) || ch != '_' || isDigit(ch))) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/Function.java b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/Function.java
new file mode 100644
index 000000000..29fbc4b53
--- /dev/null
+++ b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/Function.java
@@ -0,0 +1,40 @@
+package ru.mipt.java2016.homework.g596.kozlova.task4;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Function {
+
+ private static final Logger LOG = LoggerFactory.getLogger(Variable.class);
+
+ private String userName;
+ private String name;
+ private List arguments = new ArrayList<>();
+ private String expression;
+
+ public Function(String u, String n, List a, String e) {
+ userName = u;
+ name = n;
+ arguments = a;
+ expression = e;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public List getArguments() {
+ return arguments;
+ }
+
+ public String getExpression() {
+ return expression;
+ }
+}
\ No newline at end of file
diff --git a/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/SecurityServiceConfiguration.java b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/SecurityServiceConfiguration.java
new file mode 100644
index 000000000..70ac97207
--- /dev/null
+++ b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/SecurityServiceConfiguration.java
@@ -0,0 +1,58 @@
+package ru.mipt.java2016.homework.g596.kozlova.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()
+ .antMatchers("/variable/**").authenticated()
+ .antMatchers("/function/**").authenticated()
+ .anyRequest().permitAll();
+ }
+
+ @Autowired
+ public void registerGlobalAuthentication(AuthenticationManagerBuilder authentication) throws Exception {
+ LOG.info("Registering global user details service");
+ authentication.userDetailsService(userName -> {
+ try {
+ BillingUser user = billingDao.loadUser(userName);
+ return new User(
+ user.getUserName(),
+ user.getPassword(),
+ Collections.singletonList(() -> "AUTHENTICATION")
+ );
+ } catch (EmptyResultDataAccessException e) {
+ LOG.warn("No such user: " + userName);
+ throw new UsernameNotFoundException(userName);
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/Variable.java b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/Variable.java
new file mode 100644
index 000000000..bcfd85310
--- /dev/null
+++ b/homework-g596-kozlova/src/main/java/ru/mipt/java2016/homework/g596/kozlova/task4/Variable.java
@@ -0,0 +1,37 @@
+package ru.mipt.java2016.homework.g596.kozlova.task4;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Variable {
+
+ private static final Logger LOG = LoggerFactory.getLogger(Variable.class);
+
+ private String userName;
+ private String name;
+ private double value;
+ private String expression;
+
+ public Variable(String u, String n, double v, String e) {
+ userName = u;
+ name = n;
+ value = v;
+ expression = e;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Double getValue() {
+ return value;
+ }
+
+ public String getExpression() {
+ return expression;
+ }
+}
\ No newline at end of file