diff --git a/homework-g597-vasilyev/pom.xml b/homework-g597-vasilyev/pom.xml index c96bf1049..2dd429437 100644 --- a/homework-g597-vasilyev/pom.xml +++ b/homework-g597-vasilyev/pom.xml @@ -9,6 +9,10 @@ 4.0.0 + + 1.4.2.RELEASE + + homework-g597-vasilyev @@ -22,5 +26,42 @@ 1.0.0 test + + + + 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} + + + org.springframework.boot + spring-boot-starter-aop + ${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 + \ No newline at end of file diff --git a/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/BuiltinCommand.java b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/BuiltinCommand.java new file mode 100644 index 000000000..a5cf2fb8f --- /dev/null +++ b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/BuiltinCommand.java @@ -0,0 +1,30 @@ +package ru.mipt.java2016.homework.g597.vasilyev.task1; + +import ru.mipt.java2016.homework.base.task1.ParsingException; + +import java.util.Stack; +import java.util.function.Function; + +/** + * Created by mizabrik on 21.12.16. + */ +public class BuiltinCommand implements Command { + private final Function function; + private final int valency; + + public BuiltinCommand(Function function, int valency) { + this.function = function; + this.valency = valency; + } + + @Override + public void apply(Stack stack) throws ParsingException { + Double[] args = new Double[valency]; + + for (int i = valency - 1; i >= 0; --i) { + args[i] = stack.pop(); + } + + stack.push(function.apply(args)); + } +} diff --git a/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/Command.java b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/Command.java new file mode 100644 index 000000000..4a6eba04e --- /dev/null +++ b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/Command.java @@ -0,0 +1,12 @@ +package ru.mipt.java2016.homework.g597.vasilyev.task1; + +import ru.mipt.java2016.homework.base.task1.ParsingException; + +import java.util.Stack; + +/** + * Created by mizabrik on 19.12.16. + */ +public interface Command { + void apply(Stack stack) throws ParsingException; +} \ No newline at end of file diff --git a/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/ExpressionTokenizer.java b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/ExpressionTokenizer.java new file mode 100644 index 000000000..5bd5a01fe --- /dev/null +++ b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/ExpressionTokenizer.java @@ -0,0 +1,148 @@ +package ru.mipt.java2016.homework.g597.vasilyev.task1; + +import ru.mipt.java2016.homework.base.task1.ParsingException; + +import java.util.Map; + +/** + * Created by mizabrik on 18.12.16. + */ +public class ExpressionTokenizer { + public enum TokenType { + OPENING_BRACKET, CLOSING_BRACKET, NUMBER, IDENTIFIER, OPERATOR, COMMA + } + + private final String expression; + private final Map operators; + private int position = 0; + + public ExpressionTokenizer(String expression, Map operators) { + this.expression = expression; + this.operators = operators; + skipSpaces(); + } + + public TokenType peekNextType() throws ParsingException { + if (hasChar()) { + char c = peekChar(); + + if (Character.isDigit(c)) { + return TokenType.NUMBER; + } else if (Character.isAlphabetic(c)) { + return TokenType.IDENTIFIER; + } else if (c == '(') { + return TokenType.OPENING_BRACKET; + } else if (c == ')') { + return TokenType.CLOSING_BRACKET; + } else if (operators.containsKey(c)) { + return TokenType.OPERATOR; + } else if (c == ',') { + return TokenType.COMMA; + } else { + throw new ParsingException("Unexpected character " + c); + } + } else { + return null; + } + } + + public double nextNumber() throws ParsingException { + if (peekNextType() != TokenType.NUMBER) { + throw new ParsingException("No number at postition " + Integer.toString(position)); + } + + double number = 0.0; + + while (hasDigit()) { + number *= 10; + number += Character.getNumericValue(nextChar()); + } + + if (hasChar() && peekChar() == '.') { + nextChar(); + + double fraction = 1; + while (hasDigit()) { + fraction /= 10; + number += Character.getNumericValue(nextChar()) * fraction; + } + } + + skipSpaces(); + return number; + } + + public String nextIdentifier() throws ParsingException { + if (peekNextType() != TokenType.IDENTIFIER) { + throw new ParsingException("No identifier at position " + Integer.toString(position)); + } + + StringBuilder builder = new StringBuilder(); + while (hasAlphanumeric()) { + builder.append(nextChar()); + } + + skipSpaces(); + return builder.toString(); + } + + public char nextBracket() throws ParsingException { + if (peekNextType() != TokenType.OPENING_BRACKET && peekNextType() != TokenType.CLOSING_BRACKET) { + throw new ParsingException("No bracket at position " + Integer.toString(position)); + } + + char bracket = nextChar(); + + skipSpaces(); + return bracket; + } + + public Operator nextOperator() throws ParsingException { + if (peekNextType() != TokenType.OPERATOR) { + throw new ParsingException("No operator at position " + Integer.toString(position)); + } + + Operator operator = operators.get(nextChar()); + + skipSpaces(); + return operator; + } + + public char nextComma() throws ParsingException { + if (peekNextType() != TokenType.COMMA) { + throw new ParsingException("No comma at position " + Integer.toString(position)); + } + + char comma = nextChar(); + + skipSpaces(); + return comma; + } + + + private boolean hasChar() { + return position < expression.length(); + } + + private boolean hasAlphanumeric() { + return hasChar() && (Character.isAlphabetic(peekChar()) || hasDigit()); + } + + private boolean hasDigit() { + return hasChar() && Character.isDigit(peekChar()); + } + + private char nextChar() { + return expression.charAt(position++); + } + + private char peekChar() { + return expression.charAt(position); + } + + private void skipSpaces() { + while (hasChar() && Character.isWhitespace(peekChar())) { + nextChar(); + } + } +} \ No newline at end of file diff --git a/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/ExtendableCalculator.java b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/ExtendableCalculator.java new file mode 100644 index 000000000..8c5b3db8a --- /dev/null +++ b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/ExtendableCalculator.java @@ -0,0 +1,13 @@ +package ru.mipt.java2016.homework.g597.vasilyev.task1; + +import ru.mipt.java2016.homework.base.task1.Calculator; +import ru.mipt.java2016.homework.base.task1.ParsingException; + +/** + * Created by mizabrik on 21.12.16. + */ +public interface ExtendableCalculator extends Calculator { + boolean supportsFunction(String name); + + double calculate(String expression, Scope scope) throws ParsingException; +} diff --git a/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/FunctionCommand.java b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/FunctionCommand.java new file mode 100644 index 000000000..f21bc6b34 --- /dev/null +++ b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/FunctionCommand.java @@ -0,0 +1,21 @@ +package ru.mipt.java2016.homework.g597.vasilyev.task1; + +import ru.mipt.java2016.homework.base.task1.ParsingException; + +import java.util.Stack; + +/** + * Created by mizabrik on 21.12.16. + */ +public class FunctionCommand implements Command { + private Command command; + + public FunctionCommand(Command command) { + this.command = command; + } + + @Override + public void apply(Stack stack) throws ParsingException { + command.apply(stack); + } +} diff --git a/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/MapScope.java b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/MapScope.java new file mode 100644 index 000000000..f6111cd06 --- /dev/null +++ b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/MapScope.java @@ -0,0 +1,24 @@ +package ru.mipt.java2016.homework.g597.vasilyev.task1; + +import java.util.Map; + +/** + * Created by mizabrik on 21.12.16. + */ +public class MapScope implements Scope { + private Map map; + + public MapScope(Map map) { + this.map = map; + } + + @Override + public Command getCommand(String name) { + return map.get(name); + } + + @Override + public boolean hasCommand(String name) { + return map.containsKey(name); + } +} diff --git a/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/Operator.java b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/Operator.java index bd4b8f646..249da9d10 100644 --- a/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/Operator.java +++ b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/Operator.java @@ -1,13 +1,14 @@ package ru.mipt.java2016.homework.g597.vasilyev.task1; -import java.util.Stack; import ru.mipt.java2016.homework.base.task1.ParsingException; +import java.util.Stack; + /** * Created by mizabrik on 10.10.16. * Arithmetic operator class. */ -enum Operator { +enum Operator implements Command { ADD(2, 2, true), SUBTRACT(2, 2, true), MULTIPLY(1, 2, true), @@ -25,7 +26,8 @@ enum Operator { } // Apply operator to stack - void apply(Stack numbers) throws ParsingException { + @Override + public void apply(Stack numbers) throws ParsingException { // Too few arguments if (numbers.size() < valency) { throw new ParsingException("Too few operands"); diff --git a/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/OverridingScope.java b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/OverridingScope.java new file mode 100644 index 000000000..2830cf709 --- /dev/null +++ b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/OverridingScope.java @@ -0,0 +1,34 @@ +package ru.mipt.java2016.homework.g597.vasilyev.task1; + +import java.util.HashMap; +import java.util.Map; + +/** + * Created by mizabrik on 21.12.16. + */ +public class OverridingScope implements Scope { + private Scope origin; + private Map overrides = new HashMap<>(); + + public OverridingScope(Scope origin) { + this.origin = origin; + } + + public void addOverride(String name, Command command) { + overrides.put(name, command); + } + + @Override + public Command getCommand(String name) { + if (overrides.containsKey(name)) { + return overrides.get(name); + } else { + return origin.getCommand(name); + } + } + + @Override + public boolean hasCommand(String name) { + return overrides.containsKey(name) || origin.hasCommand(name); + } +} diff --git a/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/PushNumberCommand.java b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/PushNumberCommand.java new file mode 100644 index 000000000..fabe0e2b0 --- /dev/null +++ b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/PushNumberCommand.java @@ -0,0 +1,21 @@ +package ru.mipt.java2016.homework.g597.vasilyev.task1; + +import ru.mipt.java2016.homework.base.task1.ParsingException; + +import java.util.Stack; + +/** + * Created by mizabrik on 19.12.16. + */ +public class PushNumberCommand implements Command { + private final double number; + + public PushNumberCommand(double number) { + this.number = number; + } + + @Override + public void apply(Stack stack) throws ParsingException { + stack.push(number); + } +} diff --git a/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/Scope.java b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/Scope.java new file mode 100644 index 000000000..a3bb6e47f --- /dev/null +++ b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/Scope.java @@ -0,0 +1,10 @@ +package ru.mipt.java2016.homework.g597.vasilyev.task1; + +/** + * Created by mizabrik on 21.12.16. + */ +public interface Scope { + Command getCommand(String name); + + boolean hasCommand(String name); +} diff --git a/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/ShuntingYardCalculator.java b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/ShuntingYardCalculator.java index e20e59a2e..4c1799990 100644 --- a/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/ShuntingYardCalculator.java +++ b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/ShuntingYardCalculator.java @@ -1,223 +1,227 @@ package ru.mipt.java2016.homework.g597.vasilyev.task1; -import java.util.ArrayList; -import java.util.Stack; -import ru.mipt.java2016.homework.base.task1.Calculator; import ru.mipt.java2016.homework.base.task1.ParsingException; +import java.util.*; + /** * Created by mizabrik on 08.10.16. * Implementation using Dijkstra shunting algorithm with two stacks. */ -class ShuntingYardCalculator implements Calculator { +public class ShuntingYardCalculator implements ExtendableCalculator { + private static final Map OPERATORS = new HashMap<>(); + private static final Map BUILTINS = new HashMap<>(); + + static { + OPERATORS.put('+', Operator.ADD); + OPERATORS.put('-', Operator.SUBTRACT); + OPERATORS.put('*', Operator.MULTIPLY); + OPERATORS.put('/', Operator.DIVIDE); + + BUILTINS.put("sin", new BuiltinCommand((Double[] args) -> Math.sin(args[0]), 1)); + BUILTINS.put("cos", new BuiltinCommand((Double[] args) -> Math.cos(args[0]), 1)); + BUILTINS.put("tg", new BuiltinCommand((Double[] args) -> Math.tan(args[0]), 1)); + BUILTINS.put("sqrt", new BuiltinCommand((Double[] args) -> Math.sqrt(args[0]), 1)); + BUILTINS.put("pow", new BuiltinCommand((Double[] args) -> Math.pow(args[0], args[1]), 2)); + BUILTINS.put("abs", new BuiltinCommand((Double[] args) -> Math.abs(args[0]), 1)); + BUILTINS.put("sign", new BuiltinCommand((Double[] args) -> Math.signum(args[0]), 1)); + BUILTINS.put("log", new BuiltinCommand((Double[] args) -> Math.log(args[1]) / Math.log(args[0]), 2)); + BUILTINS.put("log2", new BuiltinCommand((Double[] args) -> Math.log(args[0]) / Math.log(2), 1)); + BUILTINS.put("log2", new BuiltinCommand((Double[] args) -> Math.log(args[0]) / Math.log(2), 1)); + BUILTINS.put("max", new BuiltinCommand((Double[] args) -> Math.max(args[0], args[1]), 2)); + BUILTINS.put("min", new BuiltinCommand((Double[] args) -> Math.min(args[0], args[1]), 2)); + BUILTINS.put("rnd", new BuiltinCommand((Double[] args) -> Math.random(), 0)); + } + + public static void main(String[] args) { + try { + ExtendableCalculator calc = new ShuntingYardCalculator(); + Scanner s = new Scanner(System.in); + s.useLocale(Locale.US); // use dots for fractions + s.useDelimiter("[,() \n]+"); + Map definitions = new HashMap<>(); + Scope scope = new MapScope(definitions); + + while (s.hasNext()) { + String command = s.next(); + + if (command.equals("var")) { + String variable = s.next(); + s.next("="); + Double value = s.nextDouble(); + definitions.put(variable, new PushNumberCommand(value)); + } else if (command.equals("def")) { + String function = s.next(); + ArrayList functionArgs = new ArrayList<>(); + while (!s.hasNext("=")) { + functionArgs.add(s.next()); + } + s.next("="); + definitions.put(function, new UserCommand(s.nextLine(), functionArgs.toArray(new String[0]), + calc, scope)); + } else if (command.equals("eval")) { + System.out.println(calc.calculate(s.nextLine(), scope)); + } else { + System.out.println(command + ": no such command"); + s.nextLine(); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + // Calculate expression. public double calculate(String expression) throws ParsingException { + return calculate(expression, new MapScope(new HashMap<>())); + } + + @Override + public boolean supportsFunction(String name) { + return BUILTINS.containsKey(name); + } + + // Calculate expression with custom scope + public double calculate(String expression, Scope scope) throws ParsingException { if (expression == null) { throw new ParsingException("Null expression"); } - ArrayList parsed = parse(expression); - if (parsed.size() == 0) { + ArrayList commands = parse(expression, scope); + if (commands.size() == 0) { throw new ParsingException("Empty expression"); } - return evaluate(parsed); + return evaluate(commands); } - // Tokenize expression - private ArrayList parse(String expr) throws ParsingException { - ArrayList result = new ArrayList<>(); - - char c; - for (int i = 0; i < expr.length(); ++i) { - c = expr.charAt(i); - if (Character.isDigit(c)) { - double number = Character.getNumericValue(c); - double fraction = 1; - ++i; - while (i < expr.length() && Character.isDigit(expr.charAt(i))) { - c = expr.charAt(i); - number *= 10; - number += Character.getNumericValue(c); - ++i; - } - if (i < expr.length() && expr.charAt(i) == '.') { - ++i; - while (i < expr.length() && Character.isDigit(expr.charAt(i))) { - fraction /= 10; - number += Character.getNumericValue(expr.charAt(i)) * fraction; - ++i; - } - } - --i; // increased by loop expression - result.add(new NumberToken(number)); - } else if (Character.isWhitespace(c)) { - continue; - } else if (isOperatorSymbol(c)) { - result.add(new OperatorToken(c)); - } else if (c == '(' || c == ')') { - if (c == ')' && result.size() > 0 && result.get(result.size() - 1) instanceof BracketToken - && ((BracketToken) result.get(result.size() - 1)).getType() - == Bracket.OPENING) { - throw new ParsingException("Empty brackets"); - } - result.add(new BracketToken(c)); - } else { - throw new ParsingException("Illegal character"); - } - } - return result; - } + // Tokenize expression + private ArrayList parse(String expression, Scope scope) throws ParsingException { + ArrayList result = new ArrayList<>(); + Stack commandStack = new Stack<>(); + ExpressionTokenizer tokenizer = new ExpressionTokenizer(expression, OPERATORS); - // Get result of expression coded by consequence of tokens. - private double evaluate(ArrayList infix) throws ParsingException { - Stack operators = new Stack<>(); - Stack numbers = new Stack<>(); - operators.push(new BracketToken('(')); - infix.add(new BracketToken(')')); - int bracketBalance = 1; + int bracketBalance = 0; boolean gotOperand = false; + ExpressionTokenizer.TokenType type; + for (type = tokenizer.peekNextType(); type != null; type = tokenizer.peekNextType()) { + switch (type) { + case NUMBER: + result.add(new PushNumberCommand(tokenizer.nextNumber())); + popFunctions(result, commandStack); + + gotOperand = true; + break; + case OPERATOR: + Operator operator = tokenizer.nextOperator(); + if (!gotOperand) { + switch (operator) { + case ADD: + operator = Operator.UNARY_PLUS; + break; + case SUBTRACT: + operator = Operator.UNARY_MINUS; + break; + default: + throw new ParsingException("No operand for operator"); + } + } else { + popFunctions(result, commandStack); + } - for (Token token : infix) { - if (token instanceof NumberToken) { - numbers.push(((NumberToken) token).getNumber()); - gotOperand = true; - } else if (token instanceof OperatorToken) { - Operator operator = ((OperatorToken) token).getOperator(); - if (!gotOperand) { - switch (operator) { - case ADD: - operator = Operator.UNARY_PLUS; + while (!commandStack.empty() && commandStack.peek() instanceof Operator) { + Operator previousOperator = (Operator) commandStack.peek(); + if (previousOperator.priority < operator.priority + || (operator.hasLeftAssociativity && previousOperator.priority == operator.priority)) { + result.add(previousOperator); + commandStack.pop(); + } else { break; - case SUBTRACT: - operator = Operator.UNARY_MINUS; - break; - default: - throw new ParsingException("Illegal expression"); + } } - token = new OperatorToken(operator); - } - gotOperand = false; - while (operators.size() > 0 && operators.peek() instanceof OperatorToken) { - Operator previousOperator = ((OperatorToken) operators.peek()).getOperator(); - if (previousOperator.priority < operator.priority - || (operator.hasLeftAssociativity && previousOperator.priority == operator.priority)) { - previousOperator.apply(numbers); - operators.pop(); + commandStack.push(operator); + + gotOperand = false; + break; + case OPENING_BRACKET: + ++bracketBalance; + tokenizer.nextBracket(); + commandStack.push(null); + + gotOperand = false; + break; + case CLOSING_BRACKET: + if (bracketBalance == 0) { + throw new ParsingException("Bad bracket balance"); + } + --bracketBalance; + tokenizer.nextBracket(); + + while (commandStack.peek() != null) { + result.add(commandStack.pop()); + } + commandStack.pop(); + popFunctions(result, commandStack); + + gotOperand = true; + break; + case IDENTIFIER: + String identifier = tokenizer.nextIdentifier(); + if (BUILTINS.containsKey(identifier)) { + commandStack.push(new FunctionCommand(BUILTINS.get(identifier))); + } else if (scope.hasCommand(identifier)) { + commandStack.push(new FunctionCommand(scope.getCommand(identifier))); } else { - break; + throw new ParsingException("Unknown identifier " + identifier); } - } - operators.push(token); - } else { - Bracket bracket = ((BracketToken) token).getType(); - switch (bracket) { - case OPENING: - operators.push(token); - gotOperand = false; - ++bracketBalance; - break; - case CLOSING: - if (bracketBalance == 0) { - throw new ParsingException("Bad bracket balance"); - } - while (operators.peek() instanceof OperatorToken) { - ((OperatorToken) operators.pop()).getOperator().apply(numbers); - } - operators.pop(); - --bracketBalance; - gotOperand = true; - break; - default: - throw new IllegalStateException(); - } + + gotOperand = true; // well, possibly + break; + case COMMA: + tokenizer.nextComma(); + while (!commandStack.empty() && commandStack.peek() != null) { + result.add(commandStack.pop()); + } + if (commandStack.empty()) { + throw new ParsingException("Misplaced comma"); + } + + gotOperand = false; + break; + default: + throw new ParsingException("Illegal character"); } } if (bracketBalance != 0) { throw new ParsingException("Bad bracket balance"); } - if (numbers.size() != 1) { - throw new ParsingException("Illegal expression"); - } - infix.remove(infix.size() - 1); - - return numbers.pop(); - } - - // Apply operation according to operator to stack of numbers - private boolean isOperatorSymbol(char c) { - return c == '+' || c == '-' || c == '*' || c == '/'; - } - - private enum Bracket { - OPENING, CLOSING - } - - private class Token { - } - - private class NumberToken extends Token { - private double number; - private NumberToken(double number) { - this.number = number; + while (!commandStack.empty()) { + result.add(commandStack.pop()); } - private double getNumber() { - return number; - } + return result; } - private class BracketToken extends Token { - private Bracket type; - - private BracketToken(char bracket) { - switch (bracket) { - case '(': - type = Bracket.OPENING; - break; - case ')': - type = Bracket.CLOSING; - break; - default: - throw new IllegalArgumentException(); - } - } - - private Bracket getType() { - return type; + private void popFunctions(ArrayList result, Stack stack) { + while (!stack.empty() && stack.peek() instanceof FunctionCommand) { + result.add(stack.pop()); } } - private class OperatorToken extends Token { - private Operator operator; + // Get result of expression coded by consequence of tokens. + private double evaluate(ArrayList commands) throws ParsingException { + Stack stack = new Stack<>(); - private OperatorToken(Operator operator) { - this.operator = operator; + for (Command command : commands) { + command.apply(stack); } - private OperatorToken(char operatorChar) { - switch (operatorChar) { - case '+': - operator = Operator.ADD; - break; - case '-': - operator = Operator.SUBTRACT; - break; - case '*': - operator = Operator.MULTIPLY; - break; - case '/': - operator = Operator.DIVIDE; - break; - default: - throw new IllegalArgumentException(); - } + if (stack.size() != 1) { + throw new ParsingException("Illegal expression"); } - private Operator getOperator() { - return operator; - } + return stack.pop(); } } diff --git a/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/UserCommand.java b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/UserCommand.java new file mode 100644 index 000000000..bb28a4493 --- /dev/null +++ b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task1/UserCommand.java @@ -0,0 +1,33 @@ +package ru.mipt.java2016.homework.g597.vasilyev.task1; + +import ru.mipt.java2016.homework.base.task1.ParsingException; + +import java.util.Stack; + +/** + * Created by mizabrik on 21.12.16. + */ +public class UserCommand implements Command { + private ExtendableCalculator calculator; + private String expression; + private String[] args; + private Scope oldScope; + + public UserCommand(String expression, String[] args, ExtendableCalculator calculator, Scope scope) { + this.expression = expression; + this.args = args; + this.calculator = calculator; + this.oldScope = scope; + } + + @Override + public void apply(Stack stack) throws ParsingException { + OverridingScope scope = new OverridingScope(oldScope); + + for (int i = args.length - 1; i >= 0; --i) { + scope.addOverride(args[i], new PushNumberCommand(stack.pop())); + } + + stack.push(calculator.calculate(expression, scope)); + } +} diff --git a/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task4/CalculatorController.java b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task4/CalculatorController.java new file mode 100644 index 000000000..f7b9b06b1 --- /dev/null +++ b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task4/CalculatorController.java @@ -0,0 +1,140 @@ +package ru.mipt.java2016.homework.g597.vasilyev.task4; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; +import ru.mipt.java2016.homework.base.task1.ParsingException; +import ru.mipt.java2016.homework.g597.vasilyev.task1.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * Created by mizabrik on 21.12.16. + */ + +@RestController +public class CalculatorController { + @Autowired + private ExtendableCalculator calculator; + + @Autowired + private CalculatorDao calculatorDao; + + @RequestMapping(value = "/variable/", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE) + public String getVariablesList(Authentication authentication) { + StringBuilder response = new StringBuilder(); + for (UserVariable variable : calculatorDao.getVariables(authentication.getName())) { + response.append(variable.getName()) + .append(" = ") + .append(variable.getValue()) + .append('\n'); + } + return response.toString(); + } + + @RequestMapping(value = "/variable/{variableName}", method = RequestMethod.GET, + produces = MediaType.TEXT_PLAIN_VALUE) + public String getVariableValue(Authentication authentication, @PathVariable String variableName) { + return Double.toString(calculatorDao.getVariableValue(authentication.getName(), variableName)); + } + + @RequestMapping(value = "/variable/{variableName}", method = RequestMethod.PUT, + consumes = MediaType.TEXT_PLAIN_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + public String setVariableValue(Authentication authentication, + @PathVariable String variableName, + @RequestBody String expression) throws ParsingException { + if (calculator.supportsFunction(variableName)) { + return builtinMesssage(variableName); + } + double value = calculator.calculate(expression, userScope(authentication.getName())); + if (calculatorDao.setVariableValue(authentication.getName(), variableName, value)) { + return "Variable added.\n"; + } else { + return variableName + " is a function.\n"; + } + } + + @RequestMapping(value = "/variable/{variableName}", method = RequestMethod.DELETE, + produces = MediaType.TEXT_PLAIN_VALUE) + public String deleteVariable(Authentication authentication, @PathVariable String variableName) { + calculatorDao.deleteVariable(authentication.getName(), variableName); + return "Deleted variable.\n"; + } + + @RequestMapping(value = "/function/", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE) + public String getFunctionList(Authentication authentication) { + StringBuilder response = new StringBuilder(); + for (UserFunction function : calculatorDao.getFunctions(authentication.getName())) { + response.append(function.toString()); + response.append('\n'); + } + return response.toString(); + } + + @RequestMapping(value = "/function/{functionName}", method = RequestMethod.GET, + produces = MediaType.TEXT_PLAIN_VALUE) + public String getFunction(Authentication authentication, @PathVariable String functionName) { + if (calculator.supportsFunction(functionName)) { + return functionName + " is a builtin.\n"; + } + return calculatorDao.getFunction(authentication.getName(), functionName).toString(); + } + + @RequestMapping(value = "/function/{functionName}", method = RequestMethod.PUT, + consumes = MediaType.TEXT_PLAIN_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + public String setVariableValue(Authentication authentication, + @PathVariable String functionName, + @RequestParam String[] args, + @RequestBody String expression) { + if (calculator.supportsFunction(functionName)) { + return builtinMesssage(functionName); + } + + if (calculatorDao.setFunction(authentication.getName(), new UserFunction(functionName, expression, args))) { + return "Function added.\n"; + } else { + return functionName + " is a variable.\n"; + } + } + + @RequestMapping(value = "/function/{functionName}", method = RequestMethod.DELETE, + produces = MediaType.TEXT_PLAIN_VALUE) + public String deleteFunction(Authentication authentication, @PathVariable String functionName) { + if (calculator.supportsFunction(functionName)) { + return "Builtins can not be deleted.\n"; + } + calculatorDao.deleteFunction(authentication.getName(), functionName); + return "Deleted function.\n"; + } + + @RequestMapping(value = "/eval/", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE) + public String evaluate(Authentication authentication, @RequestBody String expression) throws ParsingException { + if (authentication.isAuthenticated()) { + return Double.toString(calculator.calculate(expression, userScope(authentication.getName()))) + " "; + } else { + return Double.toString(calculator.calculate(expression)) + " "; + } + } + + private Scope userScope(String username) { + Map definitions = new HashMap<>(); + Scope scope = new MapScope(definitions); + + for (UserVariable variable : calculatorDao.getVariables(username)) { + definitions.put(variable.getName(), new PushNumberCommand(variable.getValue())); + } + + for (UserFunction function : calculatorDao.getFunctions(username)) { + definitions.put(function.getName(), + new UserCommand(function.getExpression(), function.getArgs(), calculator, scope)); + } + + return scope; + } + + private String builtinMesssage(String identifier) { + return "Builtin function " + identifier + " is unmodifiable."; + } +} diff --git a/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task4/CalculatorDao.java b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task4/CalculatorDao.java new file mode 100644 index 000000000..bf12c9c66 --- /dev/null +++ b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task4/CalculatorDao.java @@ -0,0 +1,159 @@ +package ru.mipt.java2016.homework.g597.vasilyev.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 org.springframework.transaction.annotation.Transactional; + +import javax.annotation.PostConstruct; +import javax.sql.DataSource; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +/** + * Created by mizabrik on 21.12.16. + */ + +@Repository +public class CalculatorDao { + @Autowired + private DataSource dataSource; + + private JdbcTemplate jdbcTemplate; + + private final Logger logger = LoggerFactory.getLogger(CalculatorDao.class); + + @PostConstruct + public void postConstruct() { + jdbcTemplate = new JdbcTemplate(dataSource); + initSchema(); + getFunction("root", "preceq"); + } + + public CalculatorUser loadUser(String username) throws EmptyResultDataAccessException { + return jdbcTemplate.queryForObject( + "SELECT username, password FROM calculator.user WHERE username = ?", + new Object[]{username}, + new RowMapper() { + @Override + public CalculatorUser mapRow(ResultSet rs, int rowNum) throws SQLException { + return new CalculatorUser(rs.getString("username"), rs.getString("password")); + } + } + ); + } + + public List getVariables(String username) { + return jdbcTemplate.query( + "SELECT name, value FROM calculator.variable WHERE owner = ?", + new UserVariableMapper(), username); + } + + public double getVariableValue(String username, String variableName) throws EmptyResultDataAccessException { + return jdbcTemplate.queryForObject( + "SELECT value FROM calculator.variable WHERE owner = ? AND name = ?", + new Object[]{username, variableName}, + Double.class + ); + } + + public void deleteVariable(String username, String variableName) { + jdbcTemplate.update("DELETE FROM calculator.variable WHERE owner = ? AND name = ?", username, variableName); + logger.info("User '{}' deleted variable '{}'.", username, variableName); + } + + @Transactional + public boolean setVariableValue(String username, String variableName, Double value) { + if (countElements("function", username, variableName) == 0) { + jdbcTemplate.update("MERGE INTO calculator.variable (owner, name, value) VALUES (?, ?, ?)", + username, variableName, value); + logger.info("User '{}' has set variable '{}'.", username, variableName); + return true; + } else { + return false; + } + } + + public UserFunction getFunction(String username, String name) { + return jdbcTemplate.queryForObject( + "SELECT name, expression, arguments FROM calculator.function WHERE owner = ? AND name = ?", + new Object[]{username, name}, new UserFunctionMapper()); + } + + public List getFunctions(String username) { + return jdbcTemplate.query("SELECT name, expression, arguments FROM calculator.function WHERE owner = ?", + new UserFunctionMapper(), username); + } + + public boolean setFunction(String username, UserFunction function) { + if (countElements("variable", username, function.getName()) == 0) { + jdbcTemplate.update("MERGE INTO calculator.function (owner, name, expression, arguments)" + + " VALUES (?, ?, ?, ?)", + username, function.getName(), function.getExpression(), function.getArgs()); + logger.info("User '{}' has set function '{}'.", username, function.getName()); + return true; + } else { + return false; + } + } + + public void deleteFunction(String username, String name) { + jdbcTemplate.update("DELETE FROM calculator.function WHERE owner = ? AND name = ?", username, name); + logger.info("User '{}' has deleted function '{}'.", username, name); + } + + public CalculatorUser createUser(String username, String password) { + jdbcTemplate.update("INSERT INTO calculator.user (username, password) VALUES (?, ?)", username, password); + return new CalculatorUser(username, password); + } + + private static class UserFunctionMapper implements RowMapper { + @Override + public UserFunction mapRow(ResultSet resultSet, int i) throws SQLException { + return new UserFunction(resultSet.getString("name"), resultSet.getString("expression"), + arrayToString((Object[]) resultSet.getArray("arguments").getArray())); + } + } + + private static class UserVariableMapper implements RowMapper { + @Override + public UserVariable mapRow(ResultSet resultSet, int i) throws SQLException { + return new UserVariable(resultSet.getString("name"), resultSet.getDouble("value")); + } + } + + private void initSchema() { + jdbcTemplate.execute("CREATE SCHEMA IF NOT EXISTS calculator"); + jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS calculator.user " + + "(username VARCHAR PRIMARY KEY, password VARCHAR)"); + jdbcTemplate.update("MERGE INTO calculator.user (username, password) VALUES ('root', 'root')"); + jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS calculator.variable" + + "(owner VARCHAR, name VARCHAR, value DOUBLE," + + "PRIMARY KEY (name, owner)," + + "FOREIGN KEY (owner) REFERENCES calculator.user(username))"); + jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS calculator.function" + + "(owner VARCHAR, name VARCHAR, expression VARCHAR, arguments ARRAY," + + "PRIMARY KEY (name, owner)," + + "FOREIGN KEY (owner) REFERENCES calculator.user(username))"); + } + + private static String[] arrayToString(Object[] array) { + String[] strings = new String[array.length]; + for (int i = 0; i < array.length; ++i) { + strings[i] = (String) array[i]; + } + + return strings; + } + + private int countElements(String table, String owner, String name) { + return jdbcTemplate.queryForObject("SELECT COUNT(*) FROM calculator." + table + + " WHERE owner = ? AND name = ?", + new Object[]{owner, name}, Integer.class); + } +} diff --git a/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task4/CalculatorDatabaseConfiguration.java b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task4/CalculatorDatabaseConfiguration.java new file mode 100644 index 000000000..1da965d93 --- /dev/null +++ b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task4/CalculatorDatabaseConfiguration.java @@ -0,0 +1,29 @@ +package ru.mipt.java2016.homework.g597.vasilyev.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; + +/** + * Created by mizabrik on 21.12.16. + */ +@Configuration +public class CalculatorDatabaseConfiguration { + @Bean + public DataSource calculatorDataSource( + @Value("${ru.mipt.java2016.homework.g597.mizabrik.task4.dbUrl}") String dbUrl, + @Value("${ru.mipt.java2016.homework.g597.mizabrik.task4.dbUsername:}") String dbUsername, + @Value("${ru.mipt.java2016.homework.g597.mizabrik.task4.dbPassword:}") String dbPassword + ) { + HikariConfig config = new HikariConfig(); + config.setDriverClassName(org.h2.Driver.class.getName()); + config.setJdbcUrl(dbUrl); + config.setUsername(dbUsername); + config.setPassword(dbPassword); + return new HikariDataSource(config); + } +} diff --git a/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task4/CalculatorUser.java b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task4/CalculatorUser.java new file mode 100644 index 000000000..5b6a842a0 --- /dev/null +++ b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task4/CalculatorUser.java @@ -0,0 +1,60 @@ +package ru.mipt.java2016.homework.g597.vasilyev.task4; + +/** + * Created by mizabrik on 21.12.16. + */ +public class CalculatorUser { + private final String username; + private final String password; + + public CalculatorUser(String username, String password) { + 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; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + @Override + public String toString() { + return "BillingUser{" + + "username='" + username + '\'' + + ", password='" + password + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CalculatorUser that = (CalculatorUser) o; + + 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(); + return result; + } +} diff --git a/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task4/RestfulCalculatorApplication.java b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task4/RestfulCalculatorApplication.java new file mode 100644 index 000000000..1bccf45a6 --- /dev/null +++ b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task4/RestfulCalculatorApplication.java @@ -0,0 +1,26 @@ +package ru.mipt.java2016.homework.g597.vasilyev.task4; + +import org.springframework.boot.Banner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import ru.mipt.java2016.homework.g597.vasilyev.task1.ExtendableCalculator; +import ru.mipt.java2016.homework.g597.vasilyev.task1.ShuntingYardCalculator; + +/** + * Created by mizabrik on 21.12.16. + */ + +@SpringBootApplication +public class RestfulCalculatorApplication { + public static void main(String[] args) { + SpringApplication application = new SpringApplication(RestfulCalculatorApplication.class); + application.setBannerMode(Banner.Mode.OFF); + application.run(args); + } + + @Bean + ExtendableCalculator calculator() { + return new ShuntingYardCalculator(); + } +} diff --git a/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task4/SecurityServiceConfguration.java b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task4/SecurityServiceConfguration.java new file mode 100644 index 000000000..cd581ef62 --- /dev/null +++ b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task4/SecurityServiceConfguration.java @@ -0,0 +1,51 @@ +package ru.mipt.java2016.homework.g597.vasilyev.task4; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +import java.util.Collections; + +/** + * Created by mizabrik on 21.12.16. + */ +@Configuration +@EnableWebSecurity +public class SecurityServiceConfguration extends WebSecurityConfigurerAdapter { + @Autowired + private CalculatorDao calculatorDao; + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .httpBasic().realmName("Calculator").and() + .csrf().disable() + .authorizeRequests() + //.antMatchers("/eval/").authenticated() + .antMatchers("/variable/**").authenticated() + .antMatchers("/function/**").authenticated() + .anyRequest().permitAll(); + } + + @Autowired + public void registerGlobalAuthentication(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(username -> { + try { + CalculatorUser user = calculatorDao.loadUser(username); + return new User( + user.getUsername(), + user.getPassword(), + Collections.singletonList(() -> "AUTH") + ); + } catch (EmptyResultDataAccessException e) { + throw new UsernameNotFoundException(username); + } + }); + } +} diff --git a/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task4/UserFunction.java b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task4/UserFunction.java new file mode 100644 index 000000000..2d133e503 --- /dev/null +++ b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task4/UserFunction.java @@ -0,0 +1,39 @@ +package ru.mipt.java2016.homework.g597.vasilyev.task4; + +/** + * Created by mizabrik on 21.12.16. + */ +public class UserFunction { + private String name; + private String expression; + private String[] args; + + public UserFunction(String name, String expression, String[] args) { + this.name = name; + this.expression = expression; + this.args = args; + } + + public String getName() { + return name; + } + + public String getExpression() { + return expression; + } + + public String[] getArgs() { + return args; + } + + public String toString() { + StringBuilder builder = new StringBuilder(); + return builder.append(name) + .append('(') + .append(String.join(", ", args)) + .append(')') + .append(" = ") + .append(expression) + .toString(); + } +} diff --git a/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task4/UserVariable.java b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task4/UserVariable.java new file mode 100644 index 000000000..37aa867b4 --- /dev/null +++ b/homework-g597-vasilyev/src/main/java/ru/mipt/java2016/homework/g597/vasilyev/task4/UserVariable.java @@ -0,0 +1,22 @@ +package ru.mipt.java2016.homework.g597.vasilyev.task4; + +/** + * Created by mizabrik on 21.12.16. + */ +public class UserVariable { + private String name; + private double value; + + public UserVariable(String name, double value) { + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public double getValue() { + return value; + } +}