diff --git a/homework-g597-dmitrieva/pom.xml b/homework-g597-dmitrieva/pom.xml
index 0a9633f8b..2c755a54d 100644
--- a/homework-g597-dmitrieva/pom.xml
+++ b/homework-g597-dmitrieva/pom.xml
@@ -10,6 +10,12 @@
4.0.0
homework-g597-dmitrieva
+ pom
+ 1.0.0
+
+
+ 1.4.2.RELEASE
+
@@ -18,6 +24,38 @@
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
diff --git a/homework-g597-dmitrieva/src/main/java/ru/mipt/java2016/homework/g597/dmitrieva/task4/BillingDao.java b/homework-g597-dmitrieva/src/main/java/ru/mipt/java2016/homework/g597/dmitrieva/task4/BillingDao.java
new file mode 100644
index 000000000..77bf013ff
--- /dev/null
+++ b/homework-g597-dmitrieva/src/main/java/ru/mipt/java2016/homework/g597/dmitrieva/task4/BillingDao.java
@@ -0,0 +1,255 @@
+package ru.mipt.java2016.homework.g597.dmitrieva.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 ru.mipt.java2016.homework.base.task1.ParsingException;
+
+import javax.annotation.PostConstruct;
+import javax.sql.DataSource;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.*;
+
+@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)");
+ //jdbcTemplate.update("INSERT INTO billing.users VALUES ('username', 'password', TRUE)");
+
+ jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS billing.functions" +
+ "(username VARCHAR, nameOfFunction VARCHAR, arguments VARCHAR, " +
+ "expression VARCHAR, PRIMARY KEY (username, nameOfFunction))");
+ //jdbcTemplate.update("INSERT INTO billing.functions VALUES ('username','sin', 'a', 'sin(a)')");
+ //jdbcTemplate.update("INSERT INTO billing.functions VALUES ('username','cos', 'a', 'cos(a)')");
+ //jdbcTemplate.update("INSERT INTO billing.functions VALUES ('username','tg', 'a', 'tg(a)')");
+ //jdbcTemplate.update("INSERT INTO billing.functions VALUES ('username','sqrt', 'a', 'sqrt(a)')");
+ //jdbcTemplate.update("INSERT INTO billing.functions VALUES ('username','pow', 'm, e', 'pow(m, e)')");
+ //jdbcTemplate.update("INSERT INTO billing.functions VALUES ('username','abs', 'a', 'abs(a)')");
+ //jdbcTemplate.update("INSERT INTO billing.functions VALUES ('username','sign', 'a', 'sigh(a)')");
+ //jdbcTemplate.update("INSERT INTO billing.functions VALUES ('username','log', 'a, n', 'log(a, n)')");
+ //jdbcTemplate.update("INSERT INTO billing.functions VALUES ('username', 'log2', 'a', 'log2(a)')");
+ //jdbcTemplate.update("INSERT INTO billing.functions VALUES ('username', 'rnd', '', 'rnd()')");
+ //jdbcTemplate.update("INSERT INTO billing.functions VALUES ('username','max', 'a, b', 'max(a, b)')");
+ //jdbcTemplate.update("INSERT INTO billing.functions VALUES ('username','min', 'a, b', 'min(a,b)')");
+
+ jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS billing.variables " +
+ "(username VARCHAR, nameOfVariable VARCHAR, valueOfVariable DOUBLE NOT NULL, " +
+ " PRIMARY KEY (username, nameOfVariable))");
+
+ }
+
+ 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 void addUser(String username, String password) {
+ LOG.trace("Adding user " + username);
+ jdbcTemplate.update("INSERT INTO billing.users VALUES ('" + username + "', '" + password + "', TRUE)");
+ }
+
+ public Double getVariable(String username, String variableName) {
+ return jdbcTemplate.queryForObject("SELECT username, nameOfVariable, valueOfVariable " +
+ "FROM billing.variables WHERE username = ? AND nameOfVariable = ?",
+ new Object[]{username, variableName},
+ new RowMapper() {
+ @Override
+ public Double mapRow(ResultSet resultSet, int rowNum) throws SQLException {
+ return resultSet.getDouble("valueOfVariable");
+ }
+ }
+ );
+ }
+
+ Map getVariables(String username) {
+ try {
+ return jdbcTemplate.queryForObject(
+ "SELECT username, nameOfVariable, valueOfVariable FROM billing.variables WHERE username = ?",
+ new Object[]{username},
+ new RowMapper>() {
+ @Override
+ public TreeMap mapRow(ResultSet resultSet, int rowNum) throws SQLException {
+ TreeMap selection = new TreeMap();
+ while (true) {
+ selection.put(resultSet.getString("nameOfVariable"),
+ resultSet.getDouble("valueOfVariable"));
+ if (!resultSet.next()) {
+ break;
+ }
+ }
+ return selection;
+ }
+ }
+ );
+ } catch (EmptyResultDataAccessException e) {
+ return new TreeMap<>();
+ }
+ }
+
+ boolean deleteVariable(String username, String nameOfVariable) {
+ try {
+ getVariable(username, nameOfVariable);
+ jdbcTemplate.update("DELETE FROM billing.variables WHERE username = ? AND nameOfVariable = ?",
+ new Object[]{username, nameOfVariable});
+ return true;
+ } catch (EmptyResultDataAccessException e) {
+ return false;
+ }
+ }
+
+ public void addVariable(String username, String nameOfVariable, Double valueOfVariable) throws ParsingException {
+ try {
+ getVariable(username, nameOfVariable);
+ jdbcTemplate.update("DELETE FROM billing.variables WHERE username = ? AND nameOfVariable = ?",
+ new Object[]{username, nameOfVariable});
+ jdbcTemplate.update("INSERT INTO billing.variables VALUES (?, ?, ?)",
+ new Object[]{username, nameOfVariable, valueOfVariable});
+ } catch (EmptyResultDataAccessException e) {
+ jdbcTemplate.update("INSERT INTO billing.variables VALUES (?, ?, ?)",
+ new Object[]{username, nameOfVariable, valueOfVariable});
+ }
+ }
+
+ public Function getFunction(String username, String nameOfFunction) {
+ return jdbcTemplate.queryForObject(
+ "SELECT username, nameOfFunction, arguments, expression " +
+ "FROM billing.functions WHERE username = ? AND nameOfFunction = ?",
+ new Object[]{username, nameOfFunction},
+ new RowMapper() {
+ @Override
+ public Function mapRow(ResultSet resultSet, int rowNum) throws SQLException {
+ String nameOfFunction = resultSet.getString("nameOfFunction");
+ List arguments = Arrays.asList(resultSet.getString("arguments").split(" "));
+ String expression = resultSet.getString("expression");
+ return new Function(nameOfFunction, arguments, expression);
+ }
+ }
+ );
+ }
+
+
+ TreeMap getFunctions(String username) {
+ try {
+ return jdbcTemplate.queryForObject("SELECT username, nameOfFunction, arguments, expression" +
+ " FROM billing.functions WHERE username = ?",
+ new Object[]{username},
+ new RowMapper>() {
+ @Override
+ public TreeMap mapRow(ResultSet resultSet, int rowNum) throws SQLException {
+ TreeMap selection = new TreeMap();
+ while (true) {
+ String nameOfFunction = resultSet.getString("nameOfFunction");
+ List arguments = Arrays.asList(resultSet.getString("arguments").split(" "));
+ String expression = resultSet.getString("expression");
+ selection.put(nameOfFunction, new Function(nameOfFunction, arguments, expression));
+ if (!resultSet.next()) {
+ break;
+ }
+ }
+ return selection;
+ }
+ });
+ } catch (EmptyResultDataAccessException e) {
+ return new TreeMap<>();
+ }
+ }
+
+ boolean deleteFunction(String username, String nameOfFunction) {
+ try {
+ getFunction(username, nameOfFunction);
+ jdbcTemplate.update("DELETE FROM billing.functions WHERE username = ? AND nameOfFunction = ?",
+ new Object[]{username, nameOfFunction});
+ return true;
+ } catch (EmptyResultDataAccessException e) {
+ return false;
+ }
+ }
+
+ void addFunction(String username, String nameOfFunction, List arguments, String expression) {
+ // Заменяем в функции все вхождения переменных на их значения
+ int beginIndexOfVariable = 0;
+ int endIndexOfVariable = 0;
+ boolean isReadingVariable = false;
+ for (int i = 0; i < expression.length(); i++) {
+ // Нашли что-то, что начинается с буквы -- возможно, это переменная
+ if (Character.isLetter(expression.charAt(i)) && !isReadingVariable) {
+ beginIndexOfVariable = i;
+ endIndexOfVariable = i; // ???
+ isReadingVariable = true;
+ continue;
+ }
+ // находимся в процессе чтения переменной (если это она)
+ if ((Character.isLetterOrDigit(expression.charAt(i)) || expression.charAt(i) == '_') && isReadingVariable) {
+ endIndexOfVariable = i;
+ continue;
+ }
+ if (!(Character.isLetterOrDigit(expression.charAt(i)) || expression.charAt(i) == '_')
+ && isReadingVariable) {
+ isReadingVariable = false;
+ String variable = expression.substring(beginIndexOfVariable, endIndexOfVariable + 1);
+ // Если мы нашли не переменную, а какую-то функцию, то ничего с ней делать не хотим
+ if (getFunctions(username).containsKey(variable)) {
+ continue;
+ }
+ // Если какую-то переменную мы нашли, но в списке переменных это пользователя ее нет,
+ // значит, это просто аргумент функции и можно расслабиться
+ if (!getVariables(username).containsKey(variable)) {
+ continue;
+ }
+ // Если же это действительно переменная, добавленная пользователем,
+ // то получаем ее значение
+ String value = getVariable(username, variable).toString();
+ // Заменяем ее первое вхождение на значение
+ expression.replaceFirst(variable, value);
+ // Дальше обновляем счетчик и снова ищем какую-нибудь переменную
+ i = 0;
+ }
+ }
+ try {
+ getFunction(username, nameOfFunction);
+ jdbcTemplate.update("DELETE FROM billing.functions WHERE username = ? AND nameOfFunction = ?",
+ new Object[]{username, nameOfFunction});
+ String stringOfArguments = String.join(" ", arguments);
+ jdbcTemplate.update("INSERT INTO billing.functions VALUES (?, ?, ?, ?)",
+ new Object[]{username, nameOfFunction, stringOfArguments, expression});
+ } catch (EmptyResultDataAccessException e) {
+ String stringArguments = String.join(" ", arguments);
+ jdbcTemplate.update("INSERT INTO billing.functions VALUES (?, ?, ?, ?)",
+ new Object[]{username, nameOfFunction, stringArguments, expression});
+ }
+ }
+}
diff --git a/homework-g597-dmitrieva/src/main/java/ru/mipt/java2016/homework/g597/dmitrieva/task4/BillingDatabaseConfiguration.java b/homework-g597-dmitrieva/src/main/java/ru/mipt/java2016/homework/g597/dmitrieva/task4/BillingDatabaseConfiguration.java
new file mode 100644
index 000000000..e72ea2ddd
--- /dev/null
+++ b/homework-g597-dmitrieva/src/main/java/ru/mipt/java2016/homework/g597/dmitrieva/task4/BillingDatabaseConfiguration.java
@@ -0,0 +1,26 @@
+package ru.mipt.java2016.homework.g597.dmitrieva.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.g597.dmitriyeva.task4.jdbcUrl:jdbc:h2:~/test}") String jdbcUrl,
+ @Value("${ru.mipt.java2016.homework.g597.dmitriyeva.task4.username:}") String username,
+ @Value("${ru.mipt.java2016.homework.g597.dmitriyeva.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);
+ }
+}
diff --git a/homework-g597-dmitrieva/src/main/java/ru/mipt/java2016/homework/g597/dmitrieva/task4/BillingUser.java b/homework-g597-dmitrieva/src/main/java/ru/mipt/java2016/homework/g597/dmitrieva/task4/BillingUser.java
new file mode 100644
index 000000000..a5db16883
--- /dev/null
+++ b/homework-g597-dmitrieva/src/main/java/ru/mipt/java2016/homework/g597/dmitrieva/task4/BillingUser.java
@@ -0,0 +1,69 @@
+package ru.mipt.java2016.homework.g597.dmitrieva.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;
+ }
+}
diff --git a/homework-g597-dmitrieva/src/main/java/ru/mipt/java2016/homework/g597/dmitrieva/task4/CalculatorController.java b/homework-g597-dmitrieva/src/main/java/ru/mipt/java2016/homework/g597/dmitrieva/task4/CalculatorController.java
new file mode 100644
index 000000000..8d287ceb3
--- /dev/null
+++ b/homework-g597-dmitrieva/src/main/java/ru/mipt/java2016/homework/g597/dmitrieva/task4/CalculatorController.java
@@ -0,0 +1,194 @@
+package ru.mipt.java2016.homework.g597.dmitrieva.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.*;
+import ru.mipt.java2016.homework.base.task1.ParsingException;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+@RestController
+public class CalculatorController {
+ private static final Logger LOG = LoggerFactory.getLogger(CalculatorController.class);
+ @Autowired
+ private StringCalculator calculator;
+
+ @Autowired
+ private BillingDao billingDao;
+
+ @RequestMapping(path = "/ping", method = RequestMethod.GET, produces = "text/plain")
+ public String echo() {
+ return "DRATUTI\n";
+ }
+
+ @RequestMapping(path = "/", method = RequestMethod.GET, produces = "text/html")
+ public String main(@RequestParam(required = false) String name) {
+ if (name == null) {
+ name = "world";
+ }
+ return "" + "IrinaPsinaApp" +
+ "Hello, " + name + "!
" + "";
+ }
+
+ /*
+ * Получить выражение, обозначенное переменной с указанным именем.
+ */
+ @RequestMapping(path = "/variable/{nameOfVariable}", method = RequestMethod.GET, produces = "text/plain")
+ public String getVariable(Authentication authentication, @PathVariable String nameOfVariable) {
+ String ourName = authentication.getName();
+ Double result = billingDao.getVariable(ourName, nameOfVariable);
+ return nameOfVariable + " = " + result + "\n";
+ }
+
+ /*
+ * Получить список имен всех переменных в сервисе.
+ */
+ @RequestMapping(path = "/variable", method = RequestMethod.GET, produces = "text/plain")
+ public String getVariables(Authentication authentication) {
+ String username = authentication.getName();
+ Map result = billingDao.getVariables(username);
+ return String.join(", ", result.keySet()) + "\n" + "";
+ }
+
+ /*
+ * Удалить переменную с заданным именем.
+ */
+ @RequestMapping(path = "/variable/{nameOfVariable}", method = RequestMethod.DELETE, produces = "text/plain")
+ public String deleteVariable(Authentication authentication, @PathVariable String nameOfVariable) {
+ String username = authentication.getName();
+ boolean deleteSucceeded = billingDao.deleteVariable(username, nameOfVariable);
+ if (deleteSucceeded) {
+ return nameOfVariable + " has been deleted\n";
+ } else {
+ return nameOfVariable + " does not exists\n";
+ }
+ }
+
+ /*
+ * Присвоить переменной новое выражение.
+ */
+
+ @RequestMapping(path = "/variable/{varName}", method = RequestMethod.PUT,
+ consumes = "*/*;charset=UTF-8", produces = "text/plain")
+ public String addVariable(Authentication authentication, @PathVariable String varName,
+ @RequestBody String valueOfVariable) throws ParsingException {
+ String username = authentication.getName();
+ billingDao.addVariable(username, varName, Double.parseDouble(valueOfVariable));
+ return "Variable " + varName + " has been added\n";
+ }
+
+
+ /*
+ * Получить выражение, обозначенное функцией с указанным именем.
+ * Также возвращает список аргументов функции. Нельзя получить выражение для предопределенных функций.
+ */
+
+ @RequestMapping(path = "/function/{nameOfFunction}", method = RequestMethod.GET, produces = "text/plain")
+ public String getFunction(Authentication authentication, @PathVariable String nameOfFunction) {
+ String username = authentication.getName();
+ Function result = billingDao.getFunction(username, nameOfFunction);
+ return nameOfFunction + "(" + String.join(", ", result.getArguments()) + ")"
+ + " = " + result.getExpression() + "\n";
+ }
+
+ /*
+ * Удалить функцию с заданным именем. Предопределенные функции нельзя удалить.
+ */
+ @RequestMapping(path = "/function/{nameOfFunction}", method = RequestMethod.DELETE, produces = "text/plain")
+ public String deleteFunction(Authentication authentication, @PathVariable String nameOfFunction) {
+ String username = authentication.getName();
+ boolean success = billingDao.deleteFunction(username, nameOfFunction);
+ if (success) {
+ return nameOfFunction + " has been deleted\n";
+ } else {
+ return nameOfFunction + " does 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(","));
+ billingDao.addFunction(username, name, arguments, expression);
+ return "Function " + name + " has been added\n";
+ }
+
+ /*
+ * Получить список имен всех пользовательских функций в сервисе.
+ */
+ @RequestMapping(path = "/function", method = RequestMethod.GET, produces = "text/plain")
+ public String getFunctions(Authentication authentication) {
+ String username = authentication.getName();
+ TreeMap map1 = billingDao.getFunctions("username");
+ TreeMap map2 = billingDao.getFunctions(username);
+ TreeMap result = new TreeMap();
+ result.putAll(map1);
+ result.putAll(map2);
+ return String.join(", ", result.keySet()) + "\n";
+ }
+
+
+ /*
+ * Рассчитать значение выражения.
+ */
+ @RequestMapping(path = "/eval", method = RequestMethod.POST,
+ consumes = "*/*;charset=UTF-8", produces = "text/plain")
+ public String eval(Authentication authentication, @RequestBody String expression) throws ParsingException {
+ try {
+ LOG.debug("Evaluation request: [" + expression + "]");
+ TreeMap allFunctionsMap = billingDao.getFunctions(authentication.getName());
+ int beginIndexOfVariable = 0;
+ int endIndexOfVariable = 0;
+ boolean isReadingVariable = false;
+ for (int i = 0; i < expression.length(); i++) {
+ // Нашли что-то, что начинается с буквы -- возможно, это переменная
+ if (Character.isLetter(expression.charAt(i)) && !isReadingVariable) {
+ beginIndexOfVariable = i;
+ endIndexOfVariable = i;
+ isReadingVariable = true;
+ continue;
+ }
+ // находимся в процессе чтения переменной (если это она)
+ if ((Character.isLetterOrDigit(expression.charAt(i)) || expression.charAt(i) == '_')
+ && isReadingVariable) {
+ endIndexOfVariable = i;
+ continue;
+ }
+ if (!(Character.isLetterOrDigit(expression.charAt(i)) || expression.charAt(i) == '_')
+ && isReadingVariable) {
+ isReadingVariable = false;
+ String variable = expression.substring(beginIndexOfVariable, endIndexOfVariable + 1);
+ // Если мы нашли не переменную, а какую-то функцию, то ничего с ней делать не хотим
+ if (allFunctionsMap.containsKey(variable)) {
+ continue;
+ }
+ // Получаем значение переменной
+ String value = billingDao.getVariable(authentication.getName(), variable).toString();
+ // Заменяем ее первое вхождение на значение
+ expression = expression.replaceFirst(variable, value);
+ // Дальше обновляем счетчик и снова ищем какую-нибудь переменную
+ i = 0;
+ }
+ }
+ double result = calculator.calculateWithFunctions(expression, allFunctionsMap);
+ //double result = calculator.calculate(expression);
+ LOG.trace("Result: " + result);
+ return "The result of expression: " + expression + " has been calculated\n" + "Result: " + Double.toString(result) + "\n";
+ } catch (ParsingException e) {
+ throw new IllegalArgumentException("OSTANOVIS', POKA OSTANOVKA NE BUDET POSLEDNEY\n");
+ }
+ }
+}
diff --git a/homework-g597-dmitrieva/src/main/java/ru/mipt/java2016/homework/g597/dmitrieva/task4/Function.java b/homework-g597-dmitrieva/src/main/java/ru/mipt/java2016/homework/g597/dmitrieva/task4/Function.java
new file mode 100644
index 000000000..a58b1560a
--- /dev/null
+++ b/homework-g597-dmitrieva/src/main/java/ru/mipt/java2016/homework/g597/dmitrieva/task4/Function.java
@@ -0,0 +1,32 @@
+package ru.mipt.java2016.homework.g597.dmitrieva.task4;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Created by irinadmitrieva on 21.12.16.
+ */
+public class Function {
+
+ private List arguments = new ArrayList<>();
+ private String name;
+ private String expression;
+ private int numberOfArguments;
+
+ public Function(String name, List arguments, String expression) {
+ this.name = name;
+ this.arguments = arguments;
+ this.expression = expression;
+ numberOfArguments = arguments.size();
+ }
+
+ public List getArguments() {
+ return arguments;
+ }
+
+ public String getExpression() {
+ return expression;
+ }
+}
\ No newline at end of file
diff --git a/homework-g597-dmitrieva/src/main/java/ru/mipt/java2016/homework/g597/dmitrieva/task4/IrinaPsinaApplication.java b/homework-g597-dmitrieva/src/main/java/ru/mipt/java2016/homework/g597/dmitrieva/task4/IrinaPsinaApplication.java
new file mode 100644
index 000000000..641cce47e
--- /dev/null
+++ b/homework-g597-dmitrieva/src/main/java/ru/mipt/java2016/homework/g597/dmitrieva/task4/IrinaPsinaApplication.java
@@ -0,0 +1,41 @@
+package ru.mipt.java2016.homework.g597.dmitrieva.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;
+
+/**
+ * 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 = IrinaPsinaApplication.class)
+public class IrinaPsinaApplication {
+
+ @Bean
+ public StringCalculator calculator() {
+ return new StringCalculator();
+ }
+
+ @Bean
+ public EmbeddedServletContainerCustomizer customizer(
+ @Value("${ru.mipt.java2016.homework.g597.dmitriyeva.task4.httpPort:9001}") int port) {
+ return container -> container.setPort(port);
+ }
+
+ public static void main(String[] args) {
+ SpringApplication application = new SpringApplication(IrinaPsinaApplication.class);
+ application.setBannerMode(Banner.Mode.OFF);
+ application.run(args);
+ }
+}
diff --git a/homework-g597-dmitrieva/src/main/java/ru/mipt/java2016/homework/g597/dmitrieva/task4/SecurityServiceConfiguration.java b/homework-g597-dmitrieva/src/main/java/ru/mipt/java2016/homework/g597/dmitrieva/task4/SecurityServiceConfiguration.java
new file mode 100644
index 000000000..f0fb0e413
--- /dev/null
+++ b/homework-g597-dmitrieva/src/main/java/ru/mipt/java2016/homework/g597/dmitrieva/task4/SecurityServiceConfiguration.java
@@ -0,0 +1,55 @@
+package ru.mipt.java2016.homework.g597.dmitrieva.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/**", "/variable/**", "/function/**").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.warn("No such user: " + username);
+ throw new UsernameNotFoundException(username);
+ }
+ });
+ }
+}
diff --git a/homework-g597-dmitrieva/src/main/java/ru/mipt/java2016/homework/g597/dmitrieva/task4/StringCalculator.java b/homework-g597-dmitrieva/src/main/java/ru/mipt/java2016/homework/g597/dmitrieva/task4/StringCalculator.java
new file mode 100644
index 000000000..609a7fdbc
--- /dev/null
+++ b/homework-g597-dmitrieva/src/main/java/ru/mipt/java2016/homework/g597/dmitrieva/task4/StringCalculator.java
@@ -0,0 +1,362 @@
+package ru.mipt.java2016.homework.g597.dmitrieva.task4;
+
+
+import ru.mipt.java2016.homework.base.task1.Calculator;
+import ru.mipt.java2016.homework.base.task1.ParsingException;
+
+import java.util.*;
+
+/**
+ * Created by macbook on 10.10.16.
+ */
+
+public class StringCalculator implements Calculator {
+
+ private final static Set SYMBOLS =
+ new TreeSet<>(Arrays.asList('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.'));
+ private final static Set OPERATORS = new TreeSet<>(Arrays.asList("+", "-", "*", "/"));
+ private static Map BaseFunctions;
+
+ static {
+ BaseFunctions = new HashMap<>();
+ BaseFunctions.put("sin", 1);
+ BaseFunctions.put("cos", 1);
+ BaseFunctions.put("tg", 1);
+ BaseFunctions.put("sqrt", 1);
+ BaseFunctions.put("pow", 2);
+ BaseFunctions.put("abs", 1);
+ BaseFunctions.put("sign", 1);
+ BaseFunctions.put("log", 1);
+ BaseFunctions.put("log2", 1);
+ BaseFunctions.put("rnd", 0);
+ BaseFunctions.put("max", 2);
+ BaseFunctions.put("min", 2);
+ }
+
+ private TreeMap AllFunctions = new TreeMap<>();
+
+ @Override
+ public double calculate(String expression)
+ throws ParsingException {
+ if (expression == null) {
+ throw new ParsingException("The string doesn't exist");
+ }
+ return calculateReversedPolish(toReversedPolish(expression));
+ }
+
+ public double calculateWithFunctions(String expression, TreeMap functions)
+ throws ParsingException {
+ if (expression == null) {
+ throw new ParsingException("The string doesn't exist");
+ }
+ AllFunctions = functions;
+ return calculateReversedPolish(toReversedPolish(expression));
+ }
+
+ // Возвращает приоритет операции
+ private int getPriority(String operator) throws ParsingException {
+ if (operator.equals("(") || operator.equals(")")) {
+ return 0;
+ }
+ if (operator.equals("+") || operator.equals("-")) {
+ return 1;
+ }
+ if (operator.equals("*") || operator.equals("/")) {
+ return 2;
+ }
+ if (operator.equals("&")) {
+ return 3;
+ }
+ if (AllFunctions.containsKey(operator)) {
+ return 3;
+ }
+ throw new ParsingException("Invalid symbol");
+ }
+
+ // Возвращает ассоциативность операции
+ String getAssociativity(String operator) throws ParsingException {
+ if (OPERATORS.contains(operator)) {
+ return "left";
+ }
+ if (AllFunctions.keySet().contains(operator)) {
+ return "right";
+ } else {
+ throw new ParsingException("Do not know such function or operator");
+ }
+ }
+
+ // Переводит инфиксную запись в постфиксную.
+ private String toReversedPolish(String expression) throws ParsingException {
+ boolean isUnaryOperation = true;
+ StringBuilder postfixLine =
+ new StringBuilder(); // Арифметическое выражение в обратной нотации.
+ Stack stack = new Stack<>(); // Стек операторов.
+ stack.push("(");
+ if (expression.length() == 0) {
+ throw new ParsingException("The line is empty");
+ }
+ for (int i = 0; i < expression.length(); i++) {
+ StringBuilder currentSymbol = new StringBuilder();
+ currentSymbol.append(expression.charAt(i));
+
+ StringBuilder functionName = new StringBuilder();
+
+ // Если пробельный символ, то игнориурем.
+ if ((currentSymbol.charAt(0) == ' ') || currentSymbol.charAt(0) == ',' ||currentSymbol.equals("\t") || currentSymbol.equals("\n")) {
+ postfixLine.append(' ');
+ continue;
+ }
+ //Если символ является цифрой или точкой, то добавляем его к выходной строке.
+ if (SYMBOLS.contains(currentSymbol.charAt(0))) {
+ postfixLine.append(currentSymbol);
+ isUnaryOperation = false;
+ } else if (currentSymbol.charAt(0) == '(') {
+ // Если символ является открывающей скобкой, помещаем его в стек.
+ stack.push(currentSymbol.toString());
+ postfixLine.append(' ').append(' ');
+ isUnaryOperation = true;
+
+ //Если символ является оператором
+ } else {
+ if (OPERATORS.contains(currentSymbol.toString())) {
+ // Если это унарный минус
+ if (isUnaryOperation) {
+ if (currentSymbol.equals("-")) {
+ while (!stack.empty()) {
+ if (getPriority(currentSymbol.toString()) < getPriority(stack.lastElement())) {
+ postfixLine.append(' ').append(stack.pop()).append(' ');
+ } else {
+ break;
+ }
+ }
+ stack.push("&");
+ postfixLine.append(' ').append(' ');
+ isUnaryOperation = false;
+ } else {
+ throw new ParsingException("Invalid expression");
+ }
+ } else { // если это бинарный оператор
+ isUnaryOperation = true;
+ //то пока приоритет этого оператора меньше или равен приоритету оператора,
+ // находящегося на вершине стека, выталкиваем верхний элементы стека в выходную строку.
+ while (!stack.empty()) {
+ if (getPriority(currentSymbol.toString()) <= getPriority(stack.lastElement())) {
+ postfixLine.append(' ').append(stack.pop()).append(' ');
+ } else {
+ break;
+ }
+ }
+ postfixLine.append(' ').append(' ');
+ stack.push(currentSymbol.toString());
+ }
+ // если встретили букву, то хотим считывать имя функции, пока можем
+ } else if (Character.isLetter(currentSymbol.charAt(0))) {
+ functionName.append(currentSymbol);
+ ++i;
+ currentSymbol = new StringBuilder();
+ currentSymbol.append(expression.charAt(i));
+ // !!!! здесь еще надо что-нибудь прикрутить на случай, если i больше длины expression
+ while (Character.isLetterOrDigit(currentSymbol.charAt(0)) && i < expression.length()) {
+ functionName.append(currentSymbol);
+ ++i;
+ currentSymbol = new StringBuilder();
+ currentSymbol.append(expression.charAt(i));
+ }
+ if (i >= expression.length()) {
+ throw new ParsingException("Out of range of expression");
+ }
+ // следующим за именем функции символом должна быть открывающая скобка,
+ // иначе -- некорретное выражение
+ if (currentSymbol.charAt(0) == '(') {
+ stack.push(currentSymbol.toString());
+ postfixLine.append(' ').append(' ');
+ isUnaryOperation = true;
+ } else {
+ throw new ParsingException("Invalid expression, couldn't find its arguments");
+ }
+
+ if (!AllFunctions.containsKey(functionName.toString())) {
+ throw new ParsingException("Do not have such function");
+ }
+ //пока приоритет этого оператора меньше приоритета оператора,
+ // находящегося на вершине стека, выталкиваем верхний элементы стека в выходную строку.
+ while (!stack.empty()) {
+ if (getPriority(functionName.toString()) < getPriority(stack.lastElement())) {
+ postfixLine.append(' ').append(stack.pop()).append(' ');
+ } else {
+ break;
+ }
+ }
+ postfixLine.append(' ').append(' ');
+ stack.push(functionName.toString());
+ } else if (currentSymbol.charAt(0) == ')') {
+ // Если символ является закрывающей скобкой: до тех пор, пока верхним элементом
+ // стека не станет открывающая скобка,выталкиваем элементы из стека в выходную строку.
+ isUnaryOperation = false;
+ while (!stack.empty() && !(stack.lastElement().equals("("))) {
+ postfixLine.append(' ');
+ postfixLine.append(stack.pop()).append(' ');
+ }
+ // Если в стеке не осталось открывающейся скобки
+ // то в выражении не согласованы скобки.
+ if (stack.empty()) {
+ throw new ParsingException("Invalid expression");
+ }
+ stack.pop(); // Убираем из стека соответствующую открывающую скобку.
+ postfixLine.append(' ').append(' ');
+ } else {
+ throw new ParsingException("Invalid symbol");
+ }
+ }
+ }
+ // Когда входная строка закончилась, выталкиваем все символы из стека в выходную строку.
+ while (!(stack.lastElement().equals("(")) && !stack.empty()) {
+ postfixLine.append(' ');
+ postfixLine.append(stack.lastElement()).append(' ');
+ stack.pop();
+ }
+ postfixLine.append(' ');
+ // Если в конце стек остался пуст, то в выражении не согласованы скобки
+ // (ибо в начале мы в стек пихали одну открывающую скобку, которая должна была остаться)
+ if (stack.empty()) {
+ throw new ParsingException("Invalid expression");
+ }
+ stack.pop(); // Удалим скобку, добавленную в самом начале, если все хорошо.
+ return postfixLine.toString();
+ }
+
+ //Считает значение элементарного выражения.
+ private Double countAtomicOperation(Character operation, Double a, Double b)
+ throws ParsingException {
+ switch (operation) {
+ case '+':
+ return a + b;
+ case '-':
+ return b - a;
+ case '*':
+ return a * b;
+ case '/':
+ return b / a;
+ default:
+ throw new ParsingException("Invalid symbol");
+ }
+ }
+
+ // Вычисление выражения в постфиксной записи.
+ private double calculateReversedPolish(String postfixLine) throws ParsingException {
+ Stack stack = new Stack<>(); // Стек операторов.
+ final Random RANDOM = new Random();
+ List tokens = Arrays.asList(postfixLine.split(" "));
+
+ for (int i = 0; i < tokens.size(); i++) {
+
+ String currentString = tokens.get(i);
+ if (currentString.isEmpty()) {
+ continue;
+ }
+ if (SYMBOLS.contains(currentString.charAt(0))) {
+ stack.push(Double.parseDouble(currentString));
+ continue;
+ }
+ if (BaseFunctions.containsKey(currentString)) {
+
+ switch (currentString) {
+ case "sin": {
+ stack.push(Math.sin(stack.pop()));
+ break;
+ }
+ case "cos": {
+ stack.push(Math.cos(stack.pop()));
+ break;
+ }
+ case "tg": {
+ stack.push(Math.tan(stack.pop()));
+ break;
+ }
+ case "sqrt": {
+ stack.push(Math.sqrt(stack.pop()));
+ break;
+ }
+ case "abs": {
+ stack.push(Math.abs(stack.pop()));
+ break;
+ }
+ case "sign": {
+ stack.push(Math.signum(stack.pop()));
+ break;
+ }
+ case "log": {
+ // log(x)/log(y) = log_y(x)
+ Double x = stack.pop();
+ Double y = stack.pop();
+ stack.push(Math.log(x) / Math.log(y));
+ break;
+ }
+ case "pow": {
+ Double x = stack.pop();
+ Double y = stack.pop();
+ stack.push(Math.pow(y, x));
+ break;
+ }
+ case "log2": {
+ stack.push(Math.log(stack.pop()) / Math.log(2));
+ break;
+ }
+ case "rnd": {
+ stack.push(RANDOM.nextDouble());
+ break;
+ }
+ case "max": {
+ Double x = stack.pop();
+ Double y = stack.pop();
+ stack.push(Math.max(x, y));
+ break;
+ }
+ case "min": {
+ Double x = stack.pop();
+ Double y = stack.pop();
+ stack.push(Math.min(x, y));
+ break;
+ }
+ }
+ continue;
+ }
+
+ if (AllFunctions.containsKey(currentString)) {
+ Function currentFunction = AllFunctions.get(currentString);
+ String expressionForFunction = currentFunction.getExpression();
+ List argumentOfFunction = currentFunction.getArguments();
+ int amountOfArguments = argumentOfFunction.size();
+ // проходимся по списку аргументов и делаем replace all, вместо параметров подставляем чиселски со стека
+ // потом делаем calculate от полученного expressiobForFunction
+ for (int j = amountOfArguments - 1; j >= 0; j--) {
+ String argument = argumentOfFunction.get(j);
+ Double valueOfArgument = stack.pop();
+ expressionForFunction = expressionForFunction.replaceAll(argument, valueOfArgument.toString());
+ }
+ Double resultForFunction = calculate(expressionForFunction);
+ stack.push(resultForFunction);
+ }
+
+ if (currentString.charAt(0) == '&') {
+ Double a;
+ a = stack.pop();
+ stack.push(-1 * a);
+ }
+ if (OPERATORS.contains(currentString)) {
+ Double a;
+ Double b;
+ a = stack.pop();
+ b = stack.pop();
+ stack.push(countAtomicOperation(currentString.charAt(0), a, b));
+ }
+ }
+ // В конце в стеке должен был остаться один элемент, который является ответом.
+ if (stack.size() == 1) {
+ return stack.lastElement();
+ } else {
+ // Если нет, то случилось что-то плохое
+ throw new ParsingException("Invalid expression");
+ }
+ }
+}
\ No newline at end of file