diff --git a/homework-g596-gerasimov/pom.xml b/homework-g596-gerasimov/pom.xml
index c7b25564f..ffe7babd7 100644
--- a/homework-g596-gerasimov/pom.xml
+++ b/homework-g596-gerasimov/pom.xml
@@ -11,6 +11,11 @@
4.0.0
homework-g596-gerasimov
+
+
+ 1.4.2.RELEASE
+
+
ru.mipt.java2016
@@ -27,6 +32,32 @@
guava
20.0
+
+
+ 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
+
diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/Application.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/Application.java
new file mode 100644
index 000000000..5450382f1
--- /dev/null
+++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/Application.java
@@ -0,0 +1,41 @@
+package ru.mipt.java2016.homework.g596.gerasimov.task4;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.Banner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import ru.mipt.java2016.homework.g596.gerasimov.task4.NewCalculator.NewCalculator;
+
+/**
+ * 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 = Application.class)
+public class Application {
+
+ @Bean
+ public NewCalculator calculator() {
+ return new NewCalculator();
+ }
+
+ @Bean
+ public EmbeddedServletContainerCustomizer customizer(
+ @Value("${ru.mipt.java2016.homework.g596.gerasimov.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);
+ }
+}
diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingDao.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingDao.java
new file mode 100644
index 000000000..16db2750d
--- /dev/null
+++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingDao.java
@@ -0,0 +1,223 @@
+package ru.mipt.java2016.homework.g596.gerasimov.task4;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Vector;
+import javax.annotation.PostConstruct;
+import javax.sql.DataSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DuplicateKeyException;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Repository;
+
+@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("DROP SCHEMA IF EXISTS billing");
+ 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,"
+ + " CONSTRAINT PK_variable PRIMARY KEY (username, name))");
+ jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS billing.functions "
+ + "(username VARCHAR, name VARCHAR, args VARCHAR, expression VARCHAR,"
+ + " CONSTRAINT PK_function PRIMARY KEY (username, name))");
+ jdbcTemplate.execute("DELETE FROM billing.users WHERE username = 'username'");
+ jdbcTemplate.update("INSERT INTO billing.users VALUES ('username', 'password', 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}, new RowMapper() {
+ @Override
+ public BillingUser mapRow(ResultSet rs, int rowNum) throws SQLException {
+ return new BillingUser(rs.getString("username"), rs.getString("password"),
+ rs.getBoolean("enabled"));
+ }
+ });
+ }
+
+ public boolean addUser(String username, String password) {
+ LOG.trace("Adding user " + username);
+ try {
+ jdbcTemplate.update("INSERT INTO billing.users VALUES ('" + username + "', '" + password
+ + "', TRUE)");
+ } catch (DuplicateKeyException exception) {
+ LOG.debug(exception.getMessage());
+ return false;
+ }
+ LOG.trace("User " + username + " was successfully added");
+ return true;
+ }
+
+ public boolean addVariable(BillingVariable variable) {
+ try {
+ jdbcTemplate.update("Insert INTO billing.variables VALUES ('" + variable.getUsername()
+ + "', '" + variable.getName() + "', " + variable.getValue() + ", '" + variable
+ .getExpression() + "')");
+ } catch (DuplicateKeyException exception) {
+ LOG.debug(exception.getMessage());
+ return false;
+ }
+ return true;
+ }
+
+ public BillingVariable getVariable(String username, String name) {
+ LOG.trace("Querying for variable " + username + ":" + name);
+ return jdbcTemplate.queryForObject(
+ "SELECT username, name, value, expression FROM billing.variables WHERE "
+ + "username = '" + username + "' AND name = '" + name + "'",
+ new RowMapper() {
+ @Override
+ public BillingVariable mapRow(ResultSet rs, int rowNum) throws SQLException {
+ return new BillingVariable(rs.getString("username"), rs.getString("name"),
+ rs.getDouble("value"), rs.getNString("expression"));
+ }
+ });
+ }
+
+ public List getAllVariables(String username) {
+ LOG.trace("Querying for all variables for " + username);
+ return jdbcTemplate
+ .query("SELECT username, name, value, expression FROM billing.variables WHERE username = '"
+ + username + "'",
+ new RowMapper() {
+ @Override
+ public BillingVariable mapRow(ResultSet rs, int rowNum) throws SQLException {
+ return new BillingVariable(rs.getString("username"), rs.getString("name"),
+ rs.getDouble("value"), rs.getNString("expression"));
+ }
+ });
+ }
+
+ public boolean deleteVariable(String username, String name) {
+ LOG.trace("Deleting variable " + username + ":" + name);
+ try {
+ jdbcTemplate.update("DELETE FROM billing.variables WHERE " + "username = '" + username
+ + "' AND name = '" + name + "'");
+ } catch (Exception exception) {
+ LOG.debug(exception.getMessage());
+ return false;
+ }
+ LOG.trace("Variable " + username + ":" + name + " was successfully deleted");
+ return true;
+ }
+
+ public boolean deleteAllVariables(String username) {
+ LOG.trace("Deleting variables of " + username);
+ try {
+ jdbcTemplate.update("DELETE FROM billing.variables WHERE " + "username = '" + username
+ + "'");
+ } catch (Exception exception) {
+ LOG.debug(exception.getMessage());
+ return false;
+ }
+ LOG.trace("Variables of " + username + " were successfully deleted");
+ return true;
+ }
+
+ public BillingFunction getFunction(String username, String name) {
+ LOG.trace("Querying for function " + username + ":" + name);
+ return jdbcTemplate.queryForObject(
+ "SELECT username, name, args, expression FROM billing.functions WHERE "
+ + "username = '" + username + "' AND name = '" + name + "'",
+ new RowMapper() {
+ @Override
+ public BillingFunction mapRow(ResultSet rs, int rowNum) throws SQLException {
+ return new BillingFunction(rs.getString("username"), rs.getString("name"),
+ decodeArgs(rs.getString("args")), rs.getNString("expression"));
+ }
+ });
+ }
+
+
+ public boolean addFunction(BillingFunction function) {
+ try {
+ jdbcTemplate.update("Insert INTO billing.functions VALUES ('" + function.getUsername()
+ + "', '" + function.getName() + "', '" + encodeArgs(function.getArgsName()) +
+ "', '" + function.getExpression() + "')");
+ } catch (DuplicateKeyException exception) {
+ LOG.debug(exception.getMessage());
+ return false;
+ }
+ return true;
+ }
+
+ public List getAllFunctions(String username) {
+ LOG.trace("Querying for all functions for " + username);
+ return jdbcTemplate
+ .query("SELECT username, name, args, expression FROM billing.functions WHERE "
+ + "username = '" + username + "'",
+ new RowMapper() {
+ @Override
+ public BillingFunction mapRow(ResultSet rs, int rowNum) throws SQLException {
+ return new BillingFunction(rs.getString("username"), rs.getString("name"),
+ decodeArgs(rs.getString("args")), rs.getNString("expression"));
+ }
+ });
+ }
+
+ public boolean deleteFunction(String username, String name) {
+ LOG.trace("Deleting variable " + username + ":" + name);
+ try {
+ jdbcTemplate.update("DELETE FROM billing.functions WHERE " + "username = '" + username
+ + "' AND name = '" + name + "'");
+ } catch (Exception exception) {
+ LOG.debug(exception.getMessage());
+ return false;
+ }
+ LOG.trace("Function " + username + ":" + name + " was successfully deleted");
+ return true;
+ }
+
+
+ private Vector decodeArgs(String code) {
+ List argsName = new Vector<>();
+ StringBuilder stringBuilder = new StringBuilder();
+
+ for (char currentChar : code.toCharArray()) {
+ if (currentChar == ',') {
+ argsName.add(stringBuilder.toString());
+ stringBuilder.setLength(0);
+ } else {
+ stringBuilder.append(currentChar);
+ }
+ }
+ argsName.add(stringBuilder.toString());
+
+ return new Vector(argsName);
+ }
+
+ private String encodeArgs(List argsName) {
+ StringBuilder stringBuilder = new StringBuilder();
+
+ for (String arg : argsName) {
+ stringBuilder.append(arg + ",");
+ }
+ stringBuilder.deleteCharAt(stringBuilder.length() - 1);
+
+ return stringBuilder.toString();
+ }
+}
\ No newline at end of file
diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingDatabaseConfiguration.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingDatabaseConfiguration.java
new file mode 100644
index 000000000..003866e10
--- /dev/null
+++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingDatabaseConfiguration.java
@@ -0,0 +1,24 @@
+package ru.mipt.java2016.homework.g596.gerasimov.task4;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import javax.sql.DataSource;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class BillingDatabaseConfiguration {
+ @Bean
+ public DataSource billingDataSource(
+ @Value("${ru.mipt.java2016.homework.g596.gerasimov.task4.jdbcUrl}") String jdbcUrl,
+ @Value("${ru.mipt.java2016.homework.g596.gerasimov.task4.username:}") String username,
+ @Value("${ru.mipt.java2016.homework.g596.gerasimov.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-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingFunction.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingFunction.java
new file mode 100644
index 000000000..35d695616
--- /dev/null
+++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingFunction.java
@@ -0,0 +1,65 @@
+package ru.mipt.java2016.homework.g596.gerasimov.task4;
+
+import java.util.List;
+import java.util.Vector;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import ru.mipt.java2016.homework.base.task1.ParsingException;
+import ru.mipt.java2016.homework.g596.gerasimov.task4.NewCalculator.NewCalculator;
+
+/**
+ * Created by geras-artem on 20.12.16.
+ */
+public class BillingFunction {
+ private final String username;
+ private final String name;
+ private final Vector argsName;
+ private final String expression;
+
+ public BillingFunction(String username, String name, Vector argsName, String expression)
+ throws IllegalArgumentException {
+ if (name == null) {
+ throw new IllegalArgumentException("Empty function name");
+ }
+ this.username = username;
+ this.name = name;
+ this.argsName = argsName;
+ this.expression = expression;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public List getArgsName() {
+ return argsName;
+ }
+
+ public String getExpression() {
+ return expression;
+ }
+
+ public double calculate(List arguments, List functions,
+ NewCalculator calculator) throws ParsingException {
+ if (arguments.size() != argsName.size()) {
+ throw new ParsingException("Wrong expression");
+ }
+
+ String newExpression = expression.replaceAll("\\s", "");
+ newExpression = "(" + newExpression + ")";
+
+ for (int i = 0; i < arguments.size(); ++i) {
+ Pattern pattern =
+ Pattern.compile("([-+*/(),])" + argsName.get(i) + "([-+*/(),])");
+ Matcher matcher = pattern.matcher(newExpression);
+ newExpression =
+ matcher.replaceAll("$1" + arguments.get(i).toString() + "$2");
+ }
+
+ return calculator.calculate(newExpression, functions);
+ }
+}
diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingUser.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingUser.java
new file mode 100644
index 000000000..2ce04649d
--- /dev/null
+++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingUser.java
@@ -0,0 +1,66 @@
+package ru.mipt.java2016.homework.g596.gerasimov.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-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingVariable.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingVariable.java
new file mode 100644
index 000000000..9b83d95c5
--- /dev/null
+++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/BillingVariable.java
@@ -0,0 +1,38 @@
+package ru.mipt.java2016.homework.g596.gerasimov.task4;
+
+/**
+ * Created by geras-artem on 19.12.16.
+ */
+public class BillingVariable {
+ private final String username;
+ private final String name;
+ private final double value;
+ private final String expression;
+
+ public BillingVariable(String username, String name, double value, String expression)
+ throws IllegalArgumentException {
+ if (name == null) {
+ throw new IllegalArgumentException("Empty variable name");
+ }
+ this.username = username;
+ this.name = name;
+ this.value = value;
+ this.expression = expression;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public double getValue() {
+ return value;
+ }
+
+ public String getExpression() {
+ return expression;
+ }
+}
diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/CalculatorController.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/CalculatorController.java
new file mode 100644
index 000000000..358d13ce1
--- /dev/null
+++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/CalculatorController.java
@@ -0,0 +1,195 @@
+package ru.mipt.java2016.homework.g596.gerasimov.task4;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Vector;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+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.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import ru.mipt.java2016.homework.base.task1.ParsingException;
+import ru.mipt.java2016.homework.g596.gerasimov.task4.NewCalculator.NewCalculator;
+
+@RestController
+public class CalculatorController {
+ private static final Logger LOG = LoggerFactory.getLogger(CalculatorController.class);
+ @Autowired private NewCalculator calculator;
+ @Autowired private BillingDao billingDao;
+
+ @RequestMapping(path = "/ping", method = RequestMethod.GET, produces = "text/plain")
+ public String echo() {
+ return "OK\n";
+ }
+
+ @RequestMapping(path = "/eval", method = RequestMethod.POST, consumes = "text/plain",
+ produces = "text/plain")
+ public String eval(Authentication authentication, @RequestBody String expression)
+ throws ParsingException {
+ LOG.debug("Evaluation request: [" + expression + "]");
+ double result;
+ try {
+ expression = reformatExpression(authentication.getName(), expression);
+ result = calculator.calculate(expression, billingDao.getAllFunctions(authentication.getName()));
+ } catch (ParsingException exception) {
+ LOG.trace(exception.getMessage());
+ return exception.getMessage() + "\n";
+ }
+ LOG.trace("Result: " + result);
+ return Double.toString(result) + "\n";
+ }
+
+ @RequestMapping(path = "/variable/{name}", method = RequestMethod.GET, produces = "text/plain")
+ public String getVariable(Authentication authentication, @PathVariable String name) {
+ LOG.debug("Get request for variable: " + authentication.getName() + ":" + name);
+ return billingDao.getVariable(authentication.getName(), name).getExpression() + "\n";
+ }
+
+ @RequestMapping(path = "/variable", method = RequestMethod.GET, produces = "text/plain")
+ public String getAllVariable(Authentication authentication) {
+ LOG.debug("Get request for ALL variables: " + authentication.getName());
+ StringBuilder stringBuilder = new StringBuilder();
+ List variables = billingDao.getAllVariables(authentication.getName());
+
+ if (variables.size() == 0) {
+ return "There are no variables\n";
+ }
+
+ for (BillingVariable var : variables) {
+ stringBuilder.append(var.getName() + ":" + var.getValue() + ":" + var.getExpression()
+ + "\n");
+ }
+
+ LOG.trace("Get request completed");
+
+ return stringBuilder.toString();
+ }
+
+ @RequestMapping(path = "/variable/{name}", method = RequestMethod.PUT, consumes = "text/plain",
+ produces = "text/plain")
+ public String addVariable(Authentication authentication, @PathVariable String name,
+ @RequestBody String expression) {
+ LOG.debug("Add request for variable: " + authentication.getName() + ":" + name);
+ String newExpression = reformatExpression(authentication.getName(), expression);
+ BillingVariable variable;
+
+ try {
+ variable = new BillingVariable(authentication.getName(), name,
+ calculator.calculate(newExpression, billingDao
+ .getAllFunctions(authentication.getName())), expression);
+ } catch (ParsingException exception) {
+ LOG.error(exception.getMessage());
+ return exception.getMessage() + "\n";
+ }
+
+ if (billingDao.addVariable(variable)) {
+ LOG.trace("Variable successfully added: " + authentication.getName() + ":" + name);
+ return "New variable successfully added\n";
+ }
+
+ LOG.trace("Variable already exists: " + authentication.getName() + ":" + name);
+ return "Variable already exists\n";
+ }
+
+ @RequestMapping(path = "/variable/{name}", method = RequestMethod.DELETE,
+ consumes = "text/plain", produces = "text/plain")
+ public String deleteVariable(Authentication authentication, @PathVariable String name) {
+ LOG.debug("Delete request for variable: " + authentication.getName() + ":" + name);
+
+ if (billingDao.deleteVariable(authentication.getName(), name)) {
+ LOG.trace("Variable was successfully deleted: " + authentication.getName() + ":" + name);
+ return "Variable successfully deleted\n";
+ }
+
+ LOG.trace("Variable does't exists: " + authentication.getName() + ":" + name);
+ return "Variable doesn't exist\n";
+ }
+
+
+ @RequestMapping(path = "/variable", method = RequestMethod.DELETE, produces = "text/plain")
+ public String deleteAllVariables(Authentication authentication) {
+ LOG.debug("Delete request for ALL variables: " + authentication.getName());
+
+ if (billingDao.deleteAllVariables(authentication.getName())) {
+ LOG.trace("Variables were successfully deleted: " + authentication.getName());
+ return "Variables successfully deleted\n";
+ }
+
+ LOG.trace("No variables available: " + authentication.getName());
+ return "No variables for user " + authentication.getName() + "\n";
+ }
+
+ @RequestMapping(path = "/function/{name}", method = RequestMethod.GET, produces = "text/plain")
+ public String getFunction(Authentication authentication, @PathVariable String name) {
+ LOG.debug("Get request for function: " + authentication.getName() + ":" + name);
+
+ BillingFunction function = billingDao.getFunction(authentication.getName(), name);
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append(function.getName() + "(");
+
+ for (String arg : function.getArgsName()) {
+ stringBuilder.append(arg + ",");
+ }
+ stringBuilder.deleteCharAt(stringBuilder.length() - 1);
+ stringBuilder.append(") = " + function.getExpression() + "\n");
+
+ return stringBuilder.toString();
+ }
+
+ @RequestMapping(path = "/function/{name}", method = RequestMethod.PUT, consumes = "text/plain",
+ produces = "text/plain")
+ public String addFunction(Authentication authentication, @PathVariable String name,
+ @RequestBody String expression, @RequestParam(value = "args") String args) {
+ LOG.debug("Add request for function: " + authentication.getName() + ":" + name);
+
+ Vector arguments = new Vector(Arrays.asList(args.split(",")));
+ BillingFunction function = new BillingFunction(authentication.getName(), name,
+ arguments, expression);
+
+ if (billingDao.addFunction(function)) {
+ LOG.trace("Function successfully added: " + authentication.getName() + ":" + name);
+ return "New function successfully added\n";
+ }
+
+ LOG.trace("Function already exists: " + authentication.getName() + ":" + name);
+ return "Function already exists\n";
+ }
+
+ @RequestMapping(path = "/function/{name}", method = RequestMethod.DELETE,
+ consumes = "text/plain", produces = "text/plain")
+ public String deleteFunction(Authentication authentication, @PathVariable String name) {
+ LOG.debug("Delete request for function: " + authentication.getName() + ":" + name);
+
+ if (billingDao.deleteFunction(authentication.getName(), name)) {
+ LOG.trace("Function was successfully deleted: " + authentication.getName() + ":" + name);
+ return "Function successfully deleted\n";
+ }
+
+ LOG.trace("Function does't exists: " + authentication.getName() + ":" + name);
+ return "Function doesn't exist\n";
+ }
+
+
+ public String reformatExpression(String username, String expression) {
+ expression = expression.replaceAll("\\s", "");
+ expression = "(" + expression + ")";
+ List variables = billingDao.getAllVariables(username);
+
+ for (BillingVariable currentVariable : variables) {
+ Pattern pattern =
+ Pattern.compile("([-+*/(),])" + currentVariable.getName() + "([-+*/(),])");
+ Matcher matcher = pattern.matcher(expression);
+ expression =
+ matcher.replaceAll("$1" + Double.toString(currentVariable.getValue()) + "$2");
+ }
+
+ return expression;
+ }
+}
diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/BracketToken.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/BracketToken.java
new file mode 100644
index 000000000..08217dbd3
--- /dev/null
+++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/BracketToken.java
@@ -0,0 +1,25 @@
+package ru.mipt.java2016.homework.g596.gerasimov.task4.NewCalculator;
+
+/**
+ * Created by geras-artem on 19.12.16.
+ */
+public class BracketToken extends Token {
+ private boolean opening;
+
+ public BracketToken(char c) {
+ switch (c) {
+ case '(':
+ opening = true;
+ break;
+ case ')':
+ opening = false;
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ public boolean isOpening() {
+ return opening;
+ }
+}
diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/FunctionToken.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/FunctionToken.java
new file mode 100644
index 000000000..32396dbcc
--- /dev/null
+++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/FunctionToken.java
@@ -0,0 +1,110 @@
+package ru.mipt.java2016.homework.g596.gerasimov.task4.NewCalculator;
+
+import java.util.List;
+import java.util.Random;
+import java.util.Vector;
+import ru.mipt.java2016.homework.base.task1.ParsingException;
+import ru.mipt.java2016.homework.g596.gerasimov.task4.BillingFunction;
+
+/**
+ * Created by geras-artem on 19.12.16.
+ */
+public class FunctionToken extends Token {
+ private final String functionName;
+
+ private final Vector arguments;
+
+ private final double value;
+
+ public FunctionToken(String functionName, Vector arguments, List funcs,
+ NewCalculator calculator) throws ParsingException {
+ this.functionName = functionName;
+ this.arguments = arguments;
+ this.value = calculateValue(funcs, calculator);
+ }
+
+ private double calculateValue(List funcs, NewCalculator calculator) throws ParsingException {
+ Double[] args = arguments.toArray(new Double[arguments.size()]);
+
+ switch (functionName) {
+ case ("sin"):
+ if (arguments.size() != 1) {
+ throw new ParsingException("Wrong function arguments");
+ }
+ return Math.sin(args[0]);
+ case ("cos"):
+ if (arguments.size() != 1) {
+ throw new ParsingException("Wrong function arguments");
+ }
+ return Math.cos(args[0]);
+ case ("tg"):
+ if (arguments.size() != 1) {
+ throw new ParsingException("Wrong function arguments");
+ }
+ return Math.tan(args[0]);
+ case ("sqrt"):
+ if (arguments.size() != 1) {
+ throw new ParsingException("Wrong function arguments");
+ }
+ return Math.sqrt(args[0]);
+ case ("pow"):
+ if (arguments.size() != 2) {
+ throw new ParsingException("Wrong function arguments");
+ }
+ return Math.pow(args[0], args[1]);
+ case ("abs"):
+ if (arguments.size() != 1) {
+ throw new ParsingException("Wrong function arguments");
+ }
+ return Math.abs(args[0]);
+ case ("sign"):
+ if (arguments.size() != 1) {
+ throw new ParsingException("Wrong function arguments");
+ }
+ return Math.signum(args[0]);
+ case ("log"):
+ if (arguments.size() != 2) {
+ throw new ParsingException("Wrong function arguments");
+ }
+ return Math.log(args[0]) / Math.log(args[1]);
+ case ("log2"):
+ if (arguments.size() != 1) {
+ throw new ParsingException("Wrong function arguments");
+ }
+ return Math.log(args[0]) / Math.log(2);
+ case ("rnd"):
+ if (arguments.size() != 0) {
+ throw new ParsingException("Wrong function arguments");
+ }
+ return new Random().nextDouble();
+ case ("max"):
+ if (arguments.size() != 2) {
+ throw new ParsingException("Wrong function arguments");
+ }
+ return Math.max(args[0], args[1]);
+ case ("min"):
+ if (arguments.size() != 2) {
+ throw new ParsingException("Wrong function arguments");
+ }
+ return Math.min(args[0], args[1]);
+ default:
+ BillingFunction function = null;
+ for (BillingFunction temp : funcs) {
+ if (temp.getName().equals(functionName)) {
+ function = temp;
+ break;
+ }
+ }
+
+ if (function == null) {
+ throw new ParsingException("Wrong function name: " + functionName);
+ }
+
+ return function.calculate(arguments, funcs, calculator);
+ }
+ }
+
+ public double getValue() {
+ return value;
+ }
+}
diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/NewCalculator.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/NewCalculator.java
new file mode 100644
index 000000000..7d71393f3
--- /dev/null
+++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/NewCalculator.java
@@ -0,0 +1,189 @@
+package ru.mipt.java2016.homework.g596.gerasimov.task4.NewCalculator;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Stack;
+import java.util.Vector;
+import ru.mipt.java2016.homework.base.task1.ParsingException;
+import ru.mipt.java2016.homework.g596.gerasimov.task4.BillingFunction;
+
+/**
+ * Created by geras-artem on 12.10.16.
+ */
+
+public class NewCalculator {
+ public double calculate(String expression, List funcs) throws ParsingException {
+ if (expression == null) {
+ throw new ParsingException("Wrong expression");
+ }
+ expression = expression.replaceAll("[\\s]", "");
+ ArrayList iExpr = tokenize(expression, funcs);
+ if (iExpr.size() == 0) {
+ throw new ParsingException("Wrong expression");
+ }
+ return calculation(iExpr);
+ }
+
+ private ArrayList tokenize(String expression, List funcs) throws ParsingException {
+ ArrayList res = new ArrayList<>();
+ char currentChar;
+ boolean gotNum = false;
+ boolean gotUOp = false;
+
+ for (int i = 0; i < expression.length(); ++i) {
+ currentChar = expression.charAt(i);
+
+ if (Character.isDigit(currentChar)) {
+ StringBuilder sNum = new StringBuilder();
+ boolean metDot = false;
+ while (i < expression.length() && (Character.isDigit(currentChar) || (
+ currentChar == '.' && !metDot))) {
+ sNum.append(currentChar);
+ if (currentChar == '.') {
+ metDot = true;
+ }
+ ++i;
+ if (i < expression.length()) {
+ currentChar = expression.charAt(i);
+ }
+ }
+ --i;
+ gotNum = true;
+ gotUOp = false;
+ res.add(new NumToken(Double.parseDouble(sNum.toString())));
+ } else if (currentChar == '(' || currentChar == ')') {
+ if (currentChar == '(' && res.size() > 0 && res
+ .get(res.size() - 1) instanceof BracketToken && ((BracketToken) res
+ .get(res.size() - 1)).isOpening()) {
+ throw new ParsingException("Wrong expression");
+ }
+ gotNum = !(currentChar == '(');
+ gotUOp = false;
+ res.add(new BracketToken(currentChar));
+ } else if (currentChar == '+' || currentChar == '-' || currentChar == '*'
+ || currentChar == '/') {
+ if (!gotNum) {
+ if (gotUOp) {
+ throw new ParsingException("Wrong expression");
+ }
+ switch (currentChar) {
+ case '+':
+ currentChar = '#';
+ gotUOp = true;
+ break;
+ case '-':
+ currentChar = '&';
+ gotUOp = true;
+ break;
+ default:
+ throw new ParsingException("Wrong expression");
+ }
+ }
+ gotNum = false;
+ res.add(new OperatorToken(currentChar));
+ } else {
+ StringBuilder functionName = new StringBuilder();
+
+ ++i;
+ for (; i < expression.length() && currentChar != '('; ++i) {
+ functionName.append(currentChar);
+ currentChar = expression.charAt(i);
+ }
+
+ if (i == expression.length()) {
+ throw new ParsingException("Wrong expression");
+ }
+
+ int tempBracketBalance = 1;
+
+ Vector arguments = new Vector<>();
+ StringBuilder argument = new StringBuilder();
+
+ for (; i < expression.length() && tempBracketBalance > 0; ++i) {
+ currentChar = expression.charAt(i);
+ if (currentChar == ',' && tempBracketBalance == 1) {
+ arguments.addElement(calculate(argument.toString(), funcs));
+ argument.setLength(0);
+ continue;
+ }
+ if (currentChar == '(') {
+ ++tempBracketBalance;
+ }
+ if (currentChar == ')') {
+ --tempBracketBalance;
+ if (tempBracketBalance == 0) {
+ arguments.addElement(calculate(argument.toString(), funcs));
+ break;
+ }
+ }
+ argument.append(currentChar);
+ }
+
+ if (i == expression.length() && tempBracketBalance != 0) {
+ throw new ParsingException("Wrong expression");
+ }
+
+ gotNum = true;
+ gotUOp = false;
+ res.add(new FunctionToken(functionName.toString(), arguments, funcs, this));
+ }
+ }
+
+ return res;
+ }
+
+ private double calculation(ArrayList iExpr) throws ParsingException {
+ Stack numbers = new Stack<>();
+ Stack operators = new Stack<>();
+ int bracketBalance = 0;
+
+ for (Token tmp : iExpr) {
+ if (tmp instanceof NumToken) {
+ numbers.push(((NumToken) tmp).getValue());
+ } else if (tmp instanceof FunctionToken) {
+ numbers.push(((FunctionToken) tmp).getValue());
+ } else if (tmp instanceof OperatorToken) {
+ Operator op = ((OperatorToken) tmp).getOperator();
+ while (operators.size() > 0 && operators.peek() instanceof OperatorToken) {
+ Operator prev = ((OperatorToken) operators.peek()).getOperator();
+ if ((op.getPriority() <= prev.getPriority() && op.getIsLA()) || (
+ op.getPriority() < prev.getPriority())) {
+ prev.use(numbers);
+ operators.pop();
+ } else {
+ break;
+ }
+ }
+
+ operators.push(tmp);
+ } else {
+ boolean isOpening = ((BracketToken) tmp).isOpening();
+ if (isOpening) {
+ operators.push(tmp);
+ ++bracketBalance;
+ } else {
+ --bracketBalance;
+ if (bracketBalance < 0) {
+ throw new ParsingException("Wrong expression");
+ }
+ while (operators.peek() instanceof OperatorToken) {
+ ((OperatorToken) operators.pop()).getOperator().use(numbers);
+ }
+ operators.pop();
+ }
+ }
+ }
+ while (!operators.empty()) {
+ ((OperatorToken) operators.pop()).getOperator().use(numbers);
+ }
+
+ if (bracketBalance != 0) {
+ throw new ParsingException("Wrong expression");
+ }
+ if (numbers.size() != 1) {
+ throw new ParsingException("Wrong expression");
+ }
+
+ return numbers.pop();
+ }
+}
diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/NumToken.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/NumToken.java
new file mode 100644
index 000000000..c557e5786
--- /dev/null
+++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/NumToken.java
@@ -0,0 +1,17 @@
+package ru.mipt.java2016.homework.g596.gerasimov.task4.NewCalculator;
+
+/**
+ * Created by geras-artem on 19.12.16.
+ */
+public class NumToken extends Token {
+ private double value;
+
+ public NumToken(double value) {
+ this.value = value;
+ }
+
+ public double getValue() {
+ return value;
+ }
+}
+
diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/Operator.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/Operator.java
new file mode 100644
index 000000000..e666c0a62
--- /dev/null
+++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/Operator.java
@@ -0,0 +1,70 @@
+package ru.mipt.java2016.homework.g596.gerasimov.task4.NewCalculator;
+
+import java.util.Stack;
+import ru.mipt.java2016.homework.base.task1.ParsingException;
+
+/**
+ * Created by geras-artem on 19.12.16.
+ */
+public enum Operator {
+
+ PLUS(1, 2, true), MINUS(1, 2, true), MULTIPLY(2, 2, true), DIVIDE(2, 2, true), U_PLUS(3, 1,
+ false), U_MINUS(3, 1, false);
+
+ private int priority;
+ private int valency;
+ private boolean isLA;
+
+ Operator(int priority, int valency, boolean isLA) {
+ this.priority = priority;
+ this.valency = valency;
+ this.isLA = isLA;
+ }
+
+ public void use(Stack nums) throws ParsingException {
+ if (nums.size() < valency) {
+ throw new ParsingException("Wrong expression");
+ }
+
+ double tmp;
+ switch (this) {
+ case PLUS:
+ tmp = nums.pop();
+ tmp += nums.pop();
+ break;
+ case MINUS:
+ tmp = -nums.pop();
+ tmp += nums.pop();
+ break;
+ case MULTIPLY:
+ tmp = nums.pop();
+ tmp *= nums.pop();
+ break;
+ case DIVIDE:
+ tmp = 1 / nums.pop();
+ tmp *= nums.pop();
+ break;
+ case U_PLUS:
+ tmp = nums.pop();
+ break;
+ case U_MINUS:
+ tmp = -nums.pop();
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+ nums.push(tmp);
+ }
+
+ public int getPriority() {
+ return priority;
+ }
+
+ public int getValency() {
+ return valency;
+ }
+
+ public boolean getIsLA() {
+ return isLA;
+ }
+}
diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/OperatorToken.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/OperatorToken.java
new file mode 100644
index 000000000..bd01c957d
--- /dev/null
+++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/OperatorToken.java
@@ -0,0 +1,38 @@
+package ru.mipt.java2016.homework.g596.gerasimov.task4.NewCalculator;
+
+/**
+ * Created by geras-artem on 19.12.16.
+ */
+public class OperatorToken extends Token {
+ private Operator op;
+
+ public OperatorToken(char c) {
+ switch (c) {
+ case '+':
+ op = Operator.PLUS;
+ break;
+ case '-':
+ op = Operator.MINUS;
+ break;
+ case '*':
+ op = Operator.MULTIPLY;
+ break;
+ case '/':
+ op = Operator.DIVIDE;
+ break;
+ case '#':
+ op = Operator.U_PLUS;
+ break;
+ case '&':
+ op = Operator.U_MINUS;
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ public Operator getOperator() {
+ return op;
+ }
+}
+
diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/Token.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/Token.java
new file mode 100644
index 000000000..1fb4781af
--- /dev/null
+++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/NewCalculator/Token.java
@@ -0,0 +1,7 @@
+package ru.mipt.java2016.homework.g596.gerasimov.task4.NewCalculator;
+
+/**
+ * Created by geras-artem on 19.12.16.
+ */
+abstract class Token {
+}
diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/RegistrationController.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/RegistrationController.java
new file mode 100644
index 000000000..af788f995
--- /dev/null
+++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/RegistrationController.java
@@ -0,0 +1,33 @@
+package ru.mipt.java2016.homework.g596.gerasimov.task4;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Created by geras-artem on 18.12.16.
+ */
+
+@RestController
+public class RegistrationController {
+ private static final Logger LOG = LoggerFactory.getLogger(RegistrationController.class);
+
+ @Autowired private BillingDao billingDao;
+
+ @RequestMapping(path = "/registration/{username}", method = RequestMethod.POST)
+ public String registration(@PathVariable String username,
+ @RequestParam(value = "password") String password) {
+ LOG.debug("Adding request for user: " + username);
+ if (!billingDao.addUser(username, password)) {
+ LOG.trace("Error");
+ return "User already exists\n";
+ }
+ LOG.trace("Success");
+ return "User successfully added\nUsername: " + username + "\nPassword: " + password + "\n";
+ }
+}
diff --git a/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/SecurityServiceConfiguration.java b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/SecurityServiceConfiguration.java
new file mode 100644
index 000000000..96e3a767b
--- /dev/null
+++ b/homework-g596-gerasimov/src/main/java/ru/mipt/java2016/homework/g596/gerasimov/task4/SecurityServiceConfiguration.java
@@ -0,0 +1,46 @@
+package ru.mipt.java2016.homework.g596.gerasimov.task4;
+
+import java.util.Collections;
+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;
+
+@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);
+ }
+ });
+ }
+}