diff --git a/lab-01-expr-calc/src/com/_30something/expr_calc/BinOpKind.java b/lab-01-expr-calc/src/com/_30something/expr_calc/BinOpKind.java new file mode 100644 index 0000000..c60f298 --- /dev/null +++ b/lab-01-expr-calc/src/com/_30something/expr_calc/BinOpKind.java @@ -0,0 +1,9 @@ +package com._30something.expr_calc; + +public enum BinOpKind { + ADD, + SUBTRACT, + MULTIPLY, + DIVIDE, + DEFAULT, +} diff --git a/lab-01-expr-calc/src/com/_30something/expr_calc/BinaryExpression.java b/lab-01-expr-calc/src/com/_30something/expr_calc/BinaryExpression.java new file mode 100644 index 0000000..c6d9feb --- /dev/null +++ b/lab-01-expr-calc/src/com/_30something/expr_calc/BinaryExpression.java @@ -0,0 +1,7 @@ +package com._30something.expr_calc; + +public interface BinaryExpression extends Expression { + Expression getLeft(); + Expression getRight(); + BinOpKind getOperation(); +} diff --git a/lab-01-expr-calc/src/com/_30something/expr_calc/BinaryExpressionImpl.java b/lab-01-expr-calc/src/com/_30something/expr_calc/BinaryExpressionImpl.java new file mode 100644 index 0000000..bed67f4 --- /dev/null +++ b/lab-01-expr-calc/src/com/_30something/expr_calc/BinaryExpressionImpl.java @@ -0,0 +1,34 @@ +package com._30something.expr_calc; + +public class BinaryExpressionImpl implements BinaryExpression { + + private final Expression left; + private final Expression right; + private final BinOpKind operation; + + public BinaryExpressionImpl(Expression left, Expression right, BinOpKind operation) { + this.left = left; + this.right = right; + this.operation = operation; + } + + @Override + public Expression getLeft() { + return left; + } + + @Override + public Expression getRight() { + return right; + } + + @Override + public BinOpKind getOperation() { + return operation; + } + + @Override + public Object accept(ExpressionVisitor visitor) { + return visitor.visitBinaryExpression(this); + } +} diff --git a/lab-01-expr-calc/src/com/_30something/expr_calc/ComputeExpressionVisitor.java b/lab-01-expr-calc/src/com/_30something/expr_calc/ComputeExpressionVisitor.java new file mode 100644 index 0000000..1567553 --- /dev/null +++ b/lab-01-expr-calc/src/com/_30something/expr_calc/ComputeExpressionVisitor.java @@ -0,0 +1,53 @@ +package com._30something.expr_calc; + +import java.util.Map; + +public class ComputeExpressionVisitor implements ExpressionVisitor { + + Map map; + + public ComputeExpressionVisitor(Map map) { + this.map = map; + } + + @Override + public Object visitBinaryExpression(BinaryExpression expr) { + BinOpKind operation = expr.getOperation(); + Double leftRes = (Double) expr.getLeft().accept(this); + Double rightRes = (Double) expr.getRight().accept(this); + switch (operation) { + case ADD -> { + return leftRes + rightRes; + } + case SUBTRACT -> { + return leftRes - rightRes; + } + case MULTIPLY -> { + return leftRes * rightRes; + } + case DIVIDE -> { + if (rightRes == 0) throw new ArithmeticException("Division by zero found"); + return leftRes / rightRes; + } + case DEFAULT -> { + return 0; + } + } + return null; + } + + @Override + public Object visitLiteral(Literal expr) { + return expr.getValue(); + } + + @Override + public Object visitParenthesis(ParenthesisExpression expr) { + return expr.getExpr().accept(this); + } + + @Override + public Object visitVariable(Variable expr) { + return map.get(expr.getName()); + } +} diff --git a/lab-01-expr-calc/src/com/_30something/expr_calc/DebugRepresentationExpressionVisitor.java b/lab-01-expr-calc/src/com/_30something/expr_calc/DebugRepresentationExpressionVisitor.java new file mode 100644 index 0000000..2440074 --- /dev/null +++ b/lab-01-expr-calc/src/com/_30something/expr_calc/DebugRepresentationExpressionVisitor.java @@ -0,0 +1,36 @@ +package com._30something.expr_calc; + +public class DebugRepresentationExpressionVisitor implements ExpressionVisitor { + + @Override + public Object visitBinaryExpression(BinaryExpression expr) { + String leftRes = (String) expr.getLeft().accept(this); + String rightRes = (String) expr.getRight().accept(this); + String operationPrefix; + if (expr.getOperation() == BinOpKind.ADD) { + operationPrefix = "add"; + } else if (expr.getOperation() == BinOpKind.SUBTRACT) { + operationPrefix = "sub"; + } else if (expr.getOperation() == BinOpKind.MULTIPLY) { + operationPrefix = "mul"; + } else { + operationPrefix = "div"; + } + return operationPrefix + "(" + leftRes + ", " + rightRes + ")"; + } + + @Override + public Object visitLiteral(Literal expr) { + return "'" + expr.getValue() + "'"; + } + + @Override + public Object visitParenthesis(ParenthesisExpression expr) { + return "paran-expr(" + expr.getExpr().accept(this) + ")"; + } + + @Override + public Object visitVariable(Variable expr) { + return "var[" + expr.getName() + "]"; + } +} diff --git a/lab-01-expr-calc/src/com/_30something/expr_calc/DepthVisitor.java b/lab-01-expr-calc/src/com/_30something/expr_calc/DepthVisitor.java new file mode 100644 index 0000000..366338f --- /dev/null +++ b/lab-01-expr-calc/src/com/_30something/expr_calc/DepthVisitor.java @@ -0,0 +1,35 @@ +package com._30something.expr_calc; + +/** + * Visitor class used to count the depth of expression tree. + * In fact counts the distance from current vertex to the farthest leaf plus 1. + * This distance for root matches with the depth of tree. + * + * @author 30something + * @version 1.0 + */ + +public class DepthVisitor implements ExpressionVisitor { + + @Override + public Object visitBinaryExpression(BinaryExpression expr) { + Integer leftRes = (Integer) expr.getLeft().accept(this); + Integer rightRes = (Integer) expr.getRight().accept(this); + return Math.max(leftRes, rightRes) + 1; + } + + @Override + public Object visitLiteral(Literal expr) { + return 1; + } + + @Override + public Object visitParenthesis(ParenthesisExpression expr) { + return (Integer) expr.getExpr().accept(this) + 1; + } + + @Override + public Object visitVariable(Variable expr) { + return 1; + } +} diff --git a/lab-01-expr-calc/src/com/_30something/expr_calc/Expression.java b/lab-01-expr-calc/src/com/_30something/expr_calc/Expression.java new file mode 100644 index 0000000..43cf84c --- /dev/null +++ b/lab-01-expr-calc/src/com/_30something/expr_calc/Expression.java @@ -0,0 +1,5 @@ +package com._30something.expr_calc; + +public interface Expression { + Object accept(ExpressionVisitor visitor); +} diff --git a/lab-01-expr-calc/src/com/_30something/expr_calc/ExpressionParseException.java b/lab-01-expr-calc/src/com/_30something/expr_calc/ExpressionParseException.java new file mode 100644 index 0000000..5b2c7be --- /dev/null +++ b/lab-01-expr-calc/src/com/_30something/expr_calc/ExpressionParseException.java @@ -0,0 +1,7 @@ +package com._30something.expr_calc; + +public class ExpressionParseException extends Exception { + public ExpressionParseException(String message) { + super(message); + } +} diff --git a/lab-01-expr-calc/src/com/_30something/expr_calc/ExpressionVisitor.java b/lab-01-expr-calc/src/com/_30something/expr_calc/ExpressionVisitor.java new file mode 100644 index 0000000..a005f4e --- /dev/null +++ b/lab-01-expr-calc/src/com/_30something/expr_calc/ExpressionVisitor.java @@ -0,0 +1,8 @@ +package com._30something.expr_calc; + +public interface ExpressionVisitor { + Object visitBinaryExpression(BinaryExpression expr); + Object visitLiteral(Literal expr); + Object visitParenthesis(ParenthesisExpression expr); + Object visitVariable(Variable expr); +} diff --git a/lab-01-expr-calc/src/com/_30something/expr_calc/Literal.java b/lab-01-expr-calc/src/com/_30something/expr_calc/Literal.java new file mode 100644 index 0000000..88a7110 --- /dev/null +++ b/lab-01-expr-calc/src/com/_30something/expr_calc/Literal.java @@ -0,0 +1,5 @@ +package com._30something.expr_calc; + +public interface Literal extends Expression { + double getValue(); +} diff --git a/lab-01-expr-calc/src/com/_30something/expr_calc/LiteralImpl.java b/lab-01-expr-calc/src/com/_30something/expr_calc/LiteralImpl.java new file mode 100644 index 0000000..6d8785a --- /dev/null +++ b/lab-01-expr-calc/src/com/_30something/expr_calc/LiteralImpl.java @@ -0,0 +1,20 @@ +package com._30something.expr_calc; + +public class LiteralImpl implements Literal { + + private final Double value; + + public LiteralImpl(Double value) { + this.value = value; + } + + @Override + public Object accept(ExpressionVisitor visitor) { + return visitor.visitLiteral(this); + } + + @Override + public double getValue() { + return value; + } +} diff --git a/lab-01-expr-calc/src/com/_30something/expr_calc/Main.java b/lab-01-expr-calc/src/com/_30something/expr_calc/Main.java new file mode 100644 index 0000000..666adb9 --- /dev/null +++ b/lab-01-expr-calc/src/com/_30something/expr_calc/Main.java @@ -0,0 +1,37 @@ +package com._30something.expr_calc; + +import java.util.Map; +import java.util.Scanner; + +public class Main { + public static void main(String[] args) { + Scanner in = new Scanner(System.in); + ParserImpl parser = new ParserImpl(); + DebugRepresentationExpressionVisitor debugVisitor = new DebugRepresentationExpressionVisitor(); + DepthVisitor depthVisitor = new DepthVisitor(); + ToStringVisitor toStringVisitor = ToStringVisitor.INSTANCE; + boolean correctInput = false; + while (!correctInput) { + try { + System.out.print("Enter expression: "); + Expression expr = parser.parseExpression(in.nextLine()); + System.out.print("Tree: "); + System.out.println((String) (expr.accept(debugVisitor))); + System.out.print("Expr-tree depth: "); + System.out.println(expr.accept(depthVisitor)); + System.out.print("Reconstructed expression: "); + System.out.println((String) (expr.accept(toStringVisitor))); + RequestVisitor requestVisitor = new RequestVisitor(in); + expr.accept(requestVisitor); + Map variablesMap = requestVisitor.getVariablesMap(); + ComputeExpressionVisitor computeVisitor = new ComputeExpressionVisitor(variablesMap); + System.out.print("Result: "); + System.out.println(expr.accept(computeVisitor)); + correctInput = true; + } catch (Exception exc) { + System.out.println(exc.getMessage()); + System.out.println("Please, input the expression again"); + } + } + } +} diff --git a/lab-01-expr-calc/src/com/_30something/expr_calc/ParenthesisExpression.java b/lab-01-expr-calc/src/com/_30something/expr_calc/ParenthesisExpression.java new file mode 100644 index 0000000..8ff2b30 --- /dev/null +++ b/lab-01-expr-calc/src/com/_30something/expr_calc/ParenthesisExpression.java @@ -0,0 +1,5 @@ +package com._30something.expr_calc; + +public interface ParenthesisExpression extends Expression { + Expression getExpr(); +} diff --git a/lab-01-expr-calc/src/com/_30something/expr_calc/ParenthesisExpressionImpl.java b/lab-01-expr-calc/src/com/_30something/expr_calc/ParenthesisExpressionImpl.java new file mode 100644 index 0000000..8a4351b --- /dev/null +++ b/lab-01-expr-calc/src/com/_30something/expr_calc/ParenthesisExpressionImpl.java @@ -0,0 +1,20 @@ +package com._30something.expr_calc; + +public class ParenthesisExpressionImpl implements ParenthesisExpression { + + private final Expression childExpr; + + public ParenthesisExpressionImpl(Expression childExpr) { + this.childExpr = childExpr; + } + + @Override + public Object accept(ExpressionVisitor visitor) { + return visitor.visitParenthesis(this); + } + + @Override + public Expression getExpr() { + return childExpr; + } +} diff --git a/lab-01-expr-calc/src/com/_30something/expr_calc/Parser.java b/lab-01-expr-calc/src/com/_30something/expr_calc/Parser.java new file mode 100644 index 0000000..f12304d --- /dev/null +++ b/lab-01-expr-calc/src/com/_30something/expr_calc/Parser.java @@ -0,0 +1,12 @@ +package com._30something.expr_calc; + +public interface Parser { + /** + * Parses expression from the string. + * If the string doesn't represent a valid expression, then throws ExpressionParseException. + * + * @param input the input string. + * @return parsed expression tree. + */ + Expression parseExpression(String input) throws ExpressionParseException; +} diff --git a/lab-01-expr-calc/src/com/_30something/expr_calc/ParserImpl.java b/lab-01-expr-calc/src/com/_30something/expr_calc/ParserImpl.java new file mode 100644 index 0000000..50f1811 --- /dev/null +++ b/lab-01-expr-calc/src/com/_30something/expr_calc/ParserImpl.java @@ -0,0 +1,247 @@ +package com._30something.expr_calc; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Stack; + +public class ParserImpl implements Parser { + + public enum CharTypes { + NUMBER, + VARIABLE, + OPERATOR, + DEFAULT, + } + + public static class Token { + public String string; + public CharTypes type; + + public Token(String string, CharTypes type) { + this.string = string; + this.type = type; + } + } + + public CharTypes charType(char c) { + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) return CharTypes.VARIABLE; + if ((c >= '0' && c <= '9') || c == '.') return CharTypes.NUMBER; + if (c == '-' || c == '+' || c == '*' || c == '/' || c == '(' || c == ')') return CharTypes.OPERATOR; + return CharTypes.DEFAULT; + } + + public List tokenize(String input) { + List tokensList = new ArrayList<>(); + Token tempToken; + char[] chars = input.toCharArray(); + for (int currentPtr = 0; currentPtr < chars.length; ) { + CharTypes tokenType = charType(chars[currentPtr]); + if (tokenType == CharTypes.VARIABLE || tokenType == CharTypes.NUMBER) { + StringBuilder tokenName = new StringBuilder(); + while (currentPtr < chars.length && charType(chars[currentPtr]) == tokenType) { + tokenName.append(chars[currentPtr]); + currentPtr++; + } + tempToken = new Token(tokenName.toString(), tokenType); + tokensList.add(tempToken); + } else { + tempToken = new Token(Character.toString(chars[currentPtr]), tokenType); + tokensList.add(tempToken); + currentPtr++; + } + } + return tokensList; + } + + /** + * Validates order of tokens in expression and constructs new tokens. + * It translates variables like '-x' as '-1 * x'. + * But lefts numbers like '-123' as '-123' and ignores unary plus. + * + * @param tokens - raw tokens + * @return newTokens - verified tokens + * @throws ExpressionParseException - parsing exception + */ + public List verifyTokens(List tokens) throws ExpressionParseException { + List newTokens = new ArrayList<>(); + Token pastToken = new Token("", CharTypes.DEFAULT); + int leftBrackets = 0; + boolean unaryMinus = false; + for (Token token : tokens) { + if (Objects.equals(token.string, " ")) { + continue; + } + if (token.type == CharTypes.DEFAULT || Objects.equals(token.string, ".")) { + throw new ExpressionParseException("Unexpected token found: '" + token.string + "'"); + } + if (token.type == CharTypes.OPERATOR) { + if (Objects.equals(token.string, "(")) { + leftBrackets++; + if (unaryMinus) { + newTokens.add(new Token("-1", CharTypes.NUMBER)); + newTokens.add(new Token("*", CharTypes.OPERATOR)); + unaryMinus = false; + } else if (Objects.equals(pastToken.string, ")") || pastToken.type == CharTypes.VARIABLE || + pastToken.type == CharTypes.NUMBER) { + throw new ExpressionParseException( + "Wrong order of operators found (left bracket after right bracket or literal)"); + } + newTokens.add(token); + } else if (pastToken.type == CharTypes.DEFAULT) { + if (Objects.equals(token.string, "-")) { + unaryMinus = true; + } else if (!Objects.equals(token.string, "+")) { + throw new ExpressionParseException( + "Wrong order of operators found (operator in the start of expression)"); + } + } else if (Objects.equals(token.string, ")")) { + leftBrackets--; + if (leftBrackets < 0) { + throw new ExpressionParseException("Overflow with right brackets found"); + } + if (pastToken.type == CharTypes.OPERATOR && !Objects.equals(pastToken.string, ")")) { + throw new ExpressionParseException( + "Wrong order of operators found (right bracket not after literal or right bracket)"); + } + newTokens.add(token); + } else { + if (pastToken.type == CharTypes.OPERATOR) { + if (!Objects.equals(pastToken.string, ")") && !Objects.equals(pastToken.string, "(")) { + throw new ExpressionParseException( + "Wrong order of operators found (operator after operator)"); + } else if (Objects.equals(pastToken.string, "(")) { + if (Objects.equals(token.string, "*") || Objects.equals(token.string, "/")) { + throw new ExpressionParseException( + "Wrong order of operators found (wrong operator after left bracket)"); + } else if (Objects.equals(token.string, "-")) { + unaryMinus = true; + } + } else { + newTokens.add(token); + } + } else { + newTokens.add(token); + } + } + } else { + if (pastToken.type == CharTypes.NUMBER || pastToken.type == CharTypes.VARIABLE) { + throw new ExpressionParseException( + "Wrong order of operators found (literal after literal)"); + } + if (Objects.equals(pastToken.string, ")")) { + throw new ExpressionParseException( + "Wrong order of operators found (literal after right bracket)"); + } + if (token.type == CharTypes.NUMBER && token.string.chars().filter(c -> c == '.').count() > 1) { + throw new ExpressionParseException("Two dots in float number found: '" + token.string + "'"); + } + if (unaryMinus) { + if (token.type == CharTypes.NUMBER) { + newTokens.add(new Token("-" + token.string, token.type)); + } else { + newTokens.add(new Token("-1", CharTypes.NUMBER)); + newTokens.add(new Token("*", CharTypes.OPERATOR)); + newTokens.add(token); + } + unaryMinus = false; + } else { + newTokens.add(token); + } + } + pastToken = token; + } + if (pastToken.type != CharTypes.NUMBER && pastToken.type != CharTypes.VARIABLE && + !Objects.equals(pastToken.string, ")")) { + throw new ExpressionParseException("Wrong order of operators found (operator in the end of expression)"); + } + if (leftBrackets > 0) { + throw new ExpressionParseException("Overflow with left brackets found"); + } + if (newTokens.isEmpty()) { + throw new ExpressionParseException("Expression is empty or insignificant"); + } + return newTokens; + } + + public List buildPolishNotation(List tokens) { + Stack operators = new Stack<>(); + List newList = new ArrayList<>(); + for (Token token : tokens) { + if (token.type == CharTypes.OPERATOR) { + if (Objects.equals(token.string, "(")) { + operators.add(token); + newList.add(token); + } else if (Objects.equals(token.string, ")")) { + while (!Objects.equals(operators.peek().string, "(")) { + newList.add(operators.peek()); + operators.pop(); + } + operators.pop(); + newList.add(token); + } else if (Objects.equals(token.string, "*") || Objects.equals(token.string, "/")) { + while (!operators.empty() && (Objects.equals(operators.peek().string, "*") || + Objects.equals(operators.peek().string, "/"))) { + newList.add(operators.peek()); + operators.pop(); + } + operators.push(token); + } else { + while (!operators.empty() && !Objects.equals(operators.peek().string, "(")) { + newList.add(operators.peek()); + operators.pop(); + } + operators.push(token); + } + } else { + newList.add(token); + } + } + while (!operators.empty()) { + newList.add(operators.peek()); + operators.pop(); + } + return newList; + } + + public Expression buildExpression(List tokens) throws ExpressionParseException { + Stack expressions = new Stack<>(); + for (Token token : tokens) { + if (token.type == CharTypes.OPERATOR) { + if (Objects.equals(token.string, ")")) { + Expression lower_expr = expressions.peek(); + expressions.pop(); + expressions.push(new ParenthesisExpressionImpl(lower_expr)); + } else if (!Objects.equals(token.string, "(")) { + Expression right_expr = expressions.peek(); + expressions.pop(); + Expression left_expr = expressions.peek(); + expressions.pop(); + BinOpKind operation; + if (Objects.equals(token.string, "+")) operation = BinOpKind.ADD; + else if (Objects.equals(token.string, "-")) operation = BinOpKind.SUBTRACT; + else if (Objects.equals(token.string, "*")) operation = BinOpKind.MULTIPLY; + else operation = BinOpKind.DIVIDE; + expressions.push(new BinaryExpressionImpl(left_expr, right_expr, operation)); + } + } else if (token.type == CharTypes.NUMBER) { + expressions.push(new LiteralImpl(Double.parseDouble(token.string))); + } else { + expressions.push(new VariableImpl(token.string)); + } + } + if (expressions.size() > 1) { + // In case if method 'verifiedTokens' didn't find any errors in expression + throw new ExpressionParseException("Wrong order of operands found"); + } + return expressions.peek(); + } + + @Override + public Expression parseExpression(String input) throws ExpressionParseException { + List rawTokens = tokenize(input); + List verifiedTokens = verifyTokens(rawTokens); + List polishNotationTokens = buildPolishNotation(verifiedTokens); + return buildExpression(polishNotationTokens); + } +} diff --git a/lab-01-expr-calc/src/com/_30something/expr_calc/RequestVisitor.java b/lab-01-expr-calc/src/com/_30something/expr_calc/RequestVisitor.java new file mode 100644 index 0000000..d93a008 --- /dev/null +++ b/lab-01-expr-calc/src/com/_30something/expr_calc/RequestVisitor.java @@ -0,0 +1,55 @@ +package com._30something.expr_calc; + +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; + +public class RequestVisitor implements ExpressionVisitor { + + Map map = new HashMap<>(); + Scanner in; + + public RequestVisitor(Scanner in) { + this.in = in; + } + + @Override + public Object visitBinaryExpression(BinaryExpression expr) { + expr.getLeft().accept(this); + expr.getRight().accept(this); + return null; + } + + @Override + public Object visitLiteral(Literal expr) { + return null; + } + + @Override + public Object visitParenthesis(ParenthesisExpression expr) { + return expr.getExpr().accept(this); + } + + @Override + public Object visitVariable(Variable expr) { + String varName = expr.getName(); + if (!map.containsKey(varName)) { + boolean correctInput = false; + while (!correctInput) { + try { + System.out.printf("Enter value for '%s': ", varName); + map.put(varName, Double.parseDouble(in.nextLine())); + correctInput = true; + } catch (Exception exc) { + System.out.println("Unable to convert input string to value"); + System.out.println("Please input value again"); + } + } + } + return null; + } + + public Map getVariablesMap() { + return map; + } +} diff --git a/lab-01-expr-calc/src/com/_30something/expr_calc/Tests.java b/lab-01-expr-calc/src/com/_30something/expr_calc/Tests.java new file mode 100644 index 0000000..cfd57a9 --- /dev/null +++ b/lab-01-expr-calc/src/com/_30something/expr_calc/Tests.java @@ -0,0 +1,59 @@ +package com._30something.expr_calc; + +import java.io.*; +import java.util.Objects; +import java.util.Scanner; + +/** + * All tests are kept in tests.txt in root directory. One test consists of 3 lines. + * In order not to complicate the tests with the presence of variables, please don't use them. + * Tests format (4 lines): + * 'Entered expression' + * 'Expected parsed to string expression' + * 'Computed result' + * 'Line with one space' + */ +public class Tests { + public static void main(String[] args) { + try { + int testNumber = 0; + int successfulTests = 0; + FileReader input = new FileReader("lab-01-expr-calc/tests.txt"); + Scanner in = new Scanner(input); + Parser parser = new ParserImpl(); + ComputeExpressionVisitor computeVisitor = new ComputeExpressionVisitor(null); + ToStringVisitor toStringVisitor = ToStringVisitor.INSTANCE; + while (in.hasNextLine()) { + boolean successfulTest = true; + testNumber++; + String expr = in.nextLine(); + String expectedParsedExpr = in.nextLine(); + Double expectedResult = Double.parseDouble(in.nextLine()); + in.nextLine(); + Expression parsedExpr = parser.parseExpression(expr); + String parsedToStringExpr = (String) parsedExpr.accept(toStringVisitor); + if (!Objects.equals(expectedParsedExpr, parsedToStringExpr)) { + System.out.printf("Error found in parsing of expression in test #%d%n", testNumber); + System.out.printf("Expected parsed expression: %s%n", expectedParsedExpr); + System.out.printf("Received parsed expression: %s%n", parsedToStringExpr); + successfulTest = false; + } + Double result = (Double) parsedExpr.accept(computeVisitor); + if (!expectedResult.equals(result)) { + System.out.printf("Error found in computing result of expression in test #%d%n", testNumber); + System.out.printf("Expected result: %s%n", expectedResult); + System.out.printf("Received result: %s%n", result); + successfulTest = false; + } + if (successfulTest) { + successfulTests++; + } + } + System.out.printf("Successfully checked tests: %d/%d", successfulTests, testNumber); + input.close(); + } catch (Exception exc) { + System.out.println("Error occurred while reading/performing tests. Message:"); + System.out.println(exc.getMessage()); + } + } +} diff --git a/lab-01-expr-calc/src/com/_30something/expr_calc/ToStringVisitor.java b/lab-01-expr-calc/src/com/_30something/expr_calc/ToStringVisitor.java new file mode 100644 index 0000000..f9d44e0 --- /dev/null +++ b/lab-01-expr-calc/src/com/_30something/expr_calc/ToStringVisitor.java @@ -0,0 +1,40 @@ +package com._30something.expr_calc; + +public class ToStringVisitor implements ExpressionVisitor { + + public static final ToStringVisitor INSTANCE = new ToStringVisitor(); + + private ToStringVisitor() {} + + @Override + public Object visitBinaryExpression(BinaryExpression expr) { + String leftRes = (String) expr.getLeft().accept(this); + String rightRes = (String) expr.getRight().accept(this); + String operation; + if (expr.getOperation() == BinOpKind.ADD) { + operation = "+"; + } else if (expr.getOperation() == BinOpKind.SUBTRACT) { + operation = "-"; + } else if (expr.getOperation() == BinOpKind.MULTIPLY) { + operation = "*"; + } else { + operation = "/"; + } + return leftRes + " " + operation + " " + rightRes; + } + + @Override + public Object visitLiteral(Literal expr) { + return Double.toString(expr.getValue()); + } + + @Override + public Object visitParenthesis(ParenthesisExpression expr) { + return "(" + expr.getExpr().accept(this) + ")"; + } + + @Override + public Object visitVariable(Variable expr) { + return expr.getName(); + } +} diff --git a/lab-01-expr-calc/src/com/_30something/expr_calc/Variable.java b/lab-01-expr-calc/src/com/_30something/expr_calc/Variable.java new file mode 100644 index 0000000..e786ad6 --- /dev/null +++ b/lab-01-expr-calc/src/com/_30something/expr_calc/Variable.java @@ -0,0 +1,5 @@ +package com._30something.expr_calc; + +public interface Variable extends Expression { + String getName(); +} diff --git a/lab-01-expr-calc/src/com/_30something/expr_calc/VariableImpl.java b/lab-01-expr-calc/src/com/_30something/expr_calc/VariableImpl.java new file mode 100644 index 0000000..4a6ee34 --- /dev/null +++ b/lab-01-expr-calc/src/com/_30something/expr_calc/VariableImpl.java @@ -0,0 +1,20 @@ +package com._30something.expr_calc; + +public class VariableImpl implements Variable { + + private final String name; + + public VariableImpl(String name) { + this.name = name; + } + + @Override + public Object accept(ExpressionVisitor visitor) { + return visitor.visitVariable(this); + } + + @Override + public String getName() { + return name; + } +} diff --git a/lab-01-expr-calc/tests.txt b/lab-01-expr-calc/tests.txt new file mode 100644 index 0000000..e4c158d --- /dev/null +++ b/lab-01-expr-calc/tests.txt @@ -0,0 +1,80 @@ +2 + 2 * 2 +2.0 + 2.0 * 2.0 +6 + +8 * 7 - 65 * 0.1 +8.0 * 7.0 - 65.0 * 0.1 +49.5 + +(((5))) +(((5.0))) +5 + +939.18828 - 827738.18 / 38849 + 929123 - 388394 * (18828 + 38399.1) +939.18828 - 827738.18 / 38849.0 + 929123.0 - 388394.0 * (18828.0 + 38399.1) +-22225732236.51827 + +-5.23 +-5.23 +-5.23 + +(-2) * 9 / (6 - 7) +(-2.0) * 9.0 / (6.0 - 7.0) +18 + +0/0.1 +0.0 / 0.1 +0 + +0 + 0.0 / 0.001 - 1 +0.0 + 0.0 / 0.001 - 1.0 +-1 + +0+0 + 0 + 0/0.001 - (-26) +0.0 + 0.0 + 0.0 + 0.0 / 0.001 - (-26.0) +26 + +(825 * 192) + (-192.11 + 5992 / 1929.1 * 26) +(825.0 * 192.0) + (-192.11 + 5992.0 / 1929.1 * 26.0) +158288.64890311545 + +-(-(-((((25.1223 / 82993 - 9930)))))) +-1.0 * (-1.0 * (-1.0 * ((((25.1223 / 82993.0 - 9930.0)))))) +9929.999697296158 + +18820.1882 - 188293.1 * 18829.22 + 93849.1882 - (-19292.11) + 26 * 37394 + (223) + (+1992) - 26.18820 * 7162 / 19928 +18820.1882 - 188293.1 * 18829.22 + 93849.1882 - (-19292.11) + 26.0 * 37394.0 + (223.0) + (1992.0) - 26.1882 * 7162.0 / 19928.0 +-3544305793.3074775 + +((((((((((((((((((((((((((((((((((((((((((-2927.239 * 162373) + 17.1))))))))))))))))))))))))))))))))))))))))) +((((((((((((((((((((((((((((((((((((((((((-2927.239 * 162373.0) + 17.1))))))))))))))))))))))))))))))))))))))))) +-475304561.047 + +-98.12 - 3823 - (-1829) +-98.12 - 3823.0 - (-1829.0) +-2092.12 + +0/(-1) +0.0 / (-1.0) +-0 + +982938 - 2990494 / ((1882 - 277494) + (177282 * 266373 / 277383 - 15) * 1667283) +982938.0 - 2990494.0 / ((1882.0 - 277494.0) + (177282.0 * 266373.0 / 277383.0 - 15.0) * 1667283.0) +982937.9999894635 + +-0.111 * 1772 +-0.111 * 1772.0 +-196.692 + +10220 / 188293 / 2 / (-1) * (-1) * (0 - 1 + 1 - 1 + 1 - 1) +10220.0 / 188293.0 / 2.0 / (-1.0) * (-1.0) * (0.0 - 1.0 + 1.0 - 1.0 + 1.0 - 1.0) +-0.0271385553366296144838098 + +87 - (0.11 * (2883 + ( 1829 - 1992/1882))) +87.0 - (0.11 * (2883.0 + (1829.0 - 1992.0 / 1882.0))) +-431.20357066950055 + ++(-(+(-9921.11))) +(-1.0 * ((-9921.11))) +9921.11 + \ No newline at end of file diff --git a/lab-02-dependency-injection/build.gradle b/lab-02-dependency-injection/build.gradle new file mode 100644 index 0000000..4a012ea --- /dev/null +++ b/lab-02-dependency-injection/build.gradle @@ -0,0 +1,17 @@ +plugins { + id 'java-library' +} + +repositories { + mavenCentral() +} + +dependencies { + api 'javax.inject:javax.inject:1' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' +} + +test { + useJUnitPlatform() +} diff --git a/lab-02-dependency-injection/settings.gradle b/lab-02-dependency-injection/settings.gradle new file mode 100644 index 0000000..b68ca36 --- /dev/null +++ b/lab-02-dependency-injection/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'lab-02-dependency-injection' diff --git a/lab-02-dependency-injection/src/main/java/com/_30something/DI/ClassRegistrationException.java b/lab-02-dependency-injection/src/main/java/com/_30something/DI/ClassRegistrationException.java new file mode 100644 index 0000000..a4083d6 --- /dev/null +++ b/lab-02-dependency-injection/src/main/java/com/_30something/DI/ClassRegistrationException.java @@ -0,0 +1,7 @@ +package com._30something.DI; + +public class ClassRegistrationException extends Exception { + public ClassRegistrationException(String message) { + super(message); + } +} diff --git a/lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java b/lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java new file mode 100644 index 0000000..a6d7c6f --- /dev/null +++ b/lab-02-dependency-injection/src/main/java/com/_30something/DI/DI.java @@ -0,0 +1,117 @@ +package com._30something.DI; + +import java.security.AccessControlException; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.lang.reflect.*; +import java.util.*; + +public class DI { + private boolean registrationCompleted = false; + private final HashMap, Class> associatedImplementations = new HashMap<>(); + private final HashMap, Constructor> associatedConstructors = new HashMap<>(); + private final HashMap, Object> singletonsInstances = new HashMap<>(); + + public void registerClass(Class newClass) throws InterfaceRegistrationException, ClassRegistrationException { + if (registrationCompleted) { + throw new AccessControlException("Registration completed for current DI"); + } + if (newClass.isInterface()) { + throw new InterfaceRegistrationException("Interface registered without implementation"); + } + if (associatedConstructors.containsKey(newClass)) { + throw new ClassRegistrationException("Double class registration"); + } + List> constructors_list = Arrays.stream(newClass.getDeclaredConstructors()).toList(); + int injectedConstructorsCounter = 0; + Constructor supposedConstructor = null; + for (Constructor constructor : constructors_list) { + if (constructor.isAnnotationPresent(Inject.class)) { + injectedConstructorsCounter++; + supposedConstructor = constructor; + } + } + if (injectedConstructorsCounter == 0) { + throw new ClassRegistrationException("Injected constructor of " + newClass + " not found"); + } + if (injectedConstructorsCounter > 1) { + throw new ClassRegistrationException("Multiple injected constructors found in " + newClass); + } + if (!newClass.isAnnotationPresent(Singleton.class) && + !Objects.equals(Modifier.toString(supposedConstructor.getModifiers()), "public")) { + throw new ClassRegistrationException("Supposed constructor of " + newClass + " must be public only"); + } + associatedConstructors.put(newClass, supposedConstructor); + } + + public void registerClass(Class newInterface, Class newImplementation) + throws InterfaceRegistrationException, ClassRegistrationException { + if (registrationCompleted) { + throw new AccessControlException("Registration completed for current DI"); + } + if (newImplementation.isInterface()) { + throw new InterfaceRegistrationException("Attempt to register interface as implementation"); + } + if (!newInterface.isInterface()) { + throw new InterfaceRegistrationException("Attempt to register implementation for non-interface class"); + } + if (associatedImplementations.containsKey(newInterface)) { + throw new InterfaceRegistrationException("Attempt to register new implementation for interface"); + } + if (!Arrays.stream(newImplementation.getInterfaces()).toList().contains(newInterface)) { + throw new InterfaceRegistrationException("Implementation doesn't correspond to interface"); + } + if (!associatedConstructors.containsKey(newImplementation)) { + registerClass(newImplementation); + } + associatedImplementations.put(newInterface, newImplementation); + } + + public void completeRegistration() throws ClassRegistrationException { + if (registrationCompleted) { + return; + } + for (Constructor constructor : associatedConstructors.values()) { + for (Parameter parameter : constructor.getParameters()) { + if (!associatedConstructors.containsKey(parameter.getType()) && + !associatedImplementations.containsKey(parameter.getType())) { + throw new ClassRegistrationException( + "Arguments of injected constructor " + constructor + " aren't registered"); + } + } + if (!constructor.isAnnotationPresent(Inject.class)) { + throw new ClassRegistrationException("Constructor " + constructor + " must be marked with @Inject"); + } + } + registrationCompleted = true; + } + + public T resolveClass(Class newClass) throws ClassNotFoundException, InvocationTargetException, + InstantiationException, IllegalAccessException { + if (!registrationCompleted) { + throw new AccessControlException("Registration isn't completed for current DI"); + } + if (!associatedConstructors.containsKey(newClass) && !associatedImplementations.containsKey(newClass)) { + throw new ClassNotFoundException("Requested class not found"); + } + if (newClass.isInterface()) { + Class implementation = associatedImplementations.get(newClass); + return newClass.cast(resolveClass(implementation)); + } + if (singletonsInstances.containsKey(newClass)) { + return newClass.cast(singletonsInstances.get(newClass)); + } + ArrayList createdInstances = new ArrayList<>(); + Constructor constructor = associatedConstructors.get(newClass); + for (Parameter parameter : constructor.getParameters()) { + createdInstances.add(resolveClass(parameter.getType())); + } + constructor.setAccessible(true); + T newInstance = newClass.cast(constructor.newInstance(createdInstances.toArray())); + if (newClass.isAnnotationPresent(Singleton.class)) { + singletonsInstances.put(newClass, newInstance); + } + return newInstance; + } +} diff --git a/lab-02-dependency-injection/src/main/java/com/_30something/DI/InterfaceRegistrationException.java b/lab-02-dependency-injection/src/main/java/com/_30something/DI/InterfaceRegistrationException.java new file mode 100644 index 0000000..3e9d557 --- /dev/null +++ b/lab-02-dependency-injection/src/main/java/com/_30something/DI/InterfaceRegistrationException.java @@ -0,0 +1,7 @@ +package com._30something.DI; + +public class InterfaceRegistrationException extends Exception { + public InterfaceRegistrationException(String message) { + super(message); + } +} diff --git a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/InterfacesTests.java b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/InterfacesTests.java new file mode 100644 index 0000000..f031a51 --- /dev/null +++ b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/InterfacesTests.java @@ -0,0 +1,156 @@ +package com._30something_.tests.DI; + +import com._30something.DI.ClassRegistrationException; +import com._30something.DI.InterfaceRegistrationException; +import java.lang.reflect.InvocationTargetException; +import java.security.AccessControlException; + +import com._30something.DI.DI; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import javax.inject.Inject; + +interface Graph { + Integer getSize(); + void add(); +} + +interface Fake { + Integer getSize(); + void add(); +} + +class Tree implements Graph { + @Inject + public Integer size = 0; + @Inject + public String info; + + @Inject + public Tree() { + info = "Tree"; + } + + @Override + public Integer getSize() { + return size; + } + + @Override + public void add() { + size++; + } +} + +class SubTree { + public String newInfo; + + @Inject + public SubTree(Tree parentTree) { + newInfo = parentTree.toString(); + } +} + +class Path implements Graph { + @Inject + public Integer size = 0; + @Inject + public String info; + + @Inject + public Path() { + info = "Path"; + } + + @Override + public Integer getSize() { + return size; + } + + @Override + public void add() { + size++; + } +} + +class Cactus implements Graph { + @Inject + public Integer size = 0; + @Inject + public String info; + + public Cactus() { + info = "Cactus"; + } + + @Override + public Integer getSize() { + return size; + } + + @Override + public void add() { + size++; + } +} + +class Node { + @Inject + public Integer data; + + @Inject + public Node() { + data = 42; + } +} + +public class InterfacesTests { + @Test + public void interfacesTestsMain() throws ClassRegistrationException, InterfaceRegistrationException, + ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException { + DI interfacesDi = new DI(); + Assertions.assertThrows(InterfaceRegistrationException.class, () -> interfacesDi.registerClass(Graph.class)); + Assertions.assertThrows(InterfaceRegistrationException.class, () -> interfacesDi.registerClass(Fake.class)); + Assertions.assertThrows(InterfaceRegistrationException.class, + () -> interfacesDi.registerClass(Graph.class, Graph.class)); + Assertions.assertThrows(InterfaceRegistrationException.class, + () -> interfacesDi.registerClass(Tree.class, Path.class)); + Assertions.assertThrows(InterfaceRegistrationException.class, + () -> interfacesDi.registerClass(Tree.class, Graph.class)); + Assertions.assertThrows(ClassRegistrationException.class, + () -> interfacesDi.registerClass(Graph.class, Cactus.class)); + Assertions.assertThrows(InterfaceRegistrationException.class, + () -> interfacesDi.registerClass(Graph.class, Fake.class)); + Assertions.assertThrows(InterfaceRegistrationException.class, + () -> interfacesDi.registerClass(Fake.class, Tree.class)); + interfacesDi.registerClass(Tree.class); + interfacesDi.registerClass(Graph.class, Tree.class); + Assertions.assertThrows(InterfaceRegistrationException.class, + () -> interfacesDi.registerClass(Graph.class, Path.class)); + interfacesDi.registerClass(Path.class); + Assertions.assertThrows(AccessControlException.class, () -> interfacesDi.resolveClass(Tree.class)); + Assertions.assertThrows(AccessControlException.class, () -> interfacesDi.resolveClass(Graph.class)); + Assertions.assertThrows(AccessControlException.class, () -> interfacesDi.resolveClass(Path.class)); + interfacesDi.registerClass(Node.class); + interfacesDi.registerClass(SubTree.class); + interfacesDi.completeRegistration(); + Assertions.assertThrows(ClassNotFoundException.class, () -> interfacesDi.resolveClass(Fake.class)); + Assertions.assertDoesNotThrow(() -> { + Tree myTree = interfacesDi.resolveClass(Tree.class); + Tree newTree = (Tree) interfacesDi.resolveClass(Graph.class); + Assertions.assertNotNull(myTree); + Assertions.assertNotNull(newTree); + Assertions.assertEquals(myTree.getClass(), Tree.class); + Assertions.assertEquals(newTree.getClass(), Tree.class); + Assertions.assertEquals(myTree.size, 0); + Assertions.assertEquals(myTree.info, "Tree"); + Assertions.assertEquals(newTree.size, 0); + Assertions.assertEquals(newTree.info, "Tree"); + }); + Assertions.assertThrows(AccessControlException.class, () -> interfacesDi.registerClass(Cactus.class)); + Assertions.assertThrows(ClassNotFoundException.class, () -> interfacesDi.resolveClass(Cactus.class)); + Assertions.assertEquals(interfacesDi.resolveClass(Node.class).data, 42); + SubTree mySubtree = interfacesDi.resolveClass(SubTree.class); + Assertions.assertNotNull(mySubtree); + } +} diff --git a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MixedTests.java b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MixedTests.java new file mode 100644 index 0000000..b3934dd --- /dev/null +++ b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/MixedTests.java @@ -0,0 +1,143 @@ +package com._30something_.tests.DI; + +import com._30something.DI.ClassRegistrationException; +import com._30something.DI.InterfaceRegistrationException; +import java.lang.reflect.InvocationTargetException; +import java.security.AccessControlException; + +import com._30something.DI.DI; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import javax.inject.Inject; +import javax.inject.Singleton; + +class Class1 { + public Class2 class2; + public String info; + + @Inject + public Class1(Class2 class2) { + this.class2 = class2; + info = "Hello, I'm class1 :)"; + } +} + +interface Class2 { + String getInfo(); +} + +class Class3 implements Class2 { + @Inject + public Double specialImportantInfo; + + @Inject + public Class3() { + specialImportantInfo = 42.422442; + } + + @Override + public String getInfo() { + return "Hello, I'm class3 :)"; + } +} + +class Class4 { + public Class1 class1; + public Class5 class5; + + @Inject + public Class4() { + class1 = new Class1(new Class3()); + class5 = Class5.instance; + } +} + +@Singleton +class Class5 { + public static final Class5 instance = new Class5(); + + @Inject + private Class5() {} + + public Class5 getInstance() { + return instance; + } +} + +public class MixedTests { + @Test + public void mixedTestsFirst() throws ClassRegistrationException, InterfaceRegistrationException, + ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException { + DI myDi = new DI(); + Assertions.assertThrows(ClassRegistrationException.class, () -> myDi.registerClass(Cactus.class)); + myDi.registerClass(Bicycle2.class); + Assertions.assertThrows(ClassRegistrationException.class, () -> myDi.registerClass(Bicycle.class)); + Assertions.assertThrows(InterfaceRegistrationException.class, () -> myDi.registerClass(Graph.class)); + Assertions.assertThrows(InterfaceRegistrationException.class, + () -> myDi.registerClass(Graph.class, Factory.class)); + Assertions.assertThrows(InterfaceRegistrationException.class, + () -> myDi.registerClass(Graph.class, Graph.class)); + myDi.registerClass(Graph.class, Tree.class); + Assertions.assertThrows(ClassRegistrationException.class, myDi::completeRegistration); + myDi.registerClass(Car.class); + myDi.registerClass(Plane.class); + Assertions.assertThrows(AccessControlException.class, () -> myDi.resolveClass(Graph.class)); + myDi.registerClass(Factory.class); + Assertions.assertThrows(AccessControlException.class, () -> myDi.resolveClass(Tree.class)); + Assertions.assertThrows(ClassRegistrationException.class, myDi::completeRegistration); + myDi.registerClass(Train.class); + myDi.registerClass(Bus.class); + myDi.registerClass(Bicycle5.class); + myDi.completeRegistration(); + Assertions.assertDoesNotThrow(() -> { + myDi.completeRegistration(); + myDi.completeRegistration(); + Graph graphRealization = myDi.resolveClass(Graph.class); + Tree currentTree = myDi.resolveClass(Tree.class); + Assertions.assertEquals(graphRealization.getClass(), Tree.class); + Assertions.assertNotNull(graphRealization); + Assertions.assertNotNull(currentTree); + }); + Assertions.assertThrows(AccessControlException.class, () -> myDi.registerClass(Bicycle4.class)); + Assertions.assertThrows(AccessControlException.class, () -> myDi.registerClass(Bicycle4.class)); + Factory factory = myDi.resolveClass(Factory.class); + Bus bus = myDi.resolveClass(Bus.class); + Assertions.assertEquals(bus, factory.getBus()); + Car car = myDi.resolveClass(Car.class); + Assertions.assertNotEquals(car, myDi.resolveClass(Car.class)); + Assertions.assertNotEquals(car, factory.getCar()); + Assertions.assertNotEquals(car, myDi.resolveClass(Factory.class).getCar()); + } + + @Test + public void mixedTestsSecond() throws ClassRegistrationException, InterfaceRegistrationException, + ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException { + DI myDi = new DI(); + myDi.registerClass(Class4.class); + myDi.registerClass(Class1.class); + Assertions.assertThrows(ClassRegistrationException.class, myDi::completeRegistration); + myDi.registerClass(Class2.class, Class3.class); + Assertions.assertThrows(ClassRegistrationException.class, () -> myDi.registerClass(Class3.class)); + myDi.registerClass(Class5.class); + Assertions.assertThrows(ClassRegistrationException.class, () -> myDi.registerClass(Class5.class)); + myDi.completeRegistration(); + Assertions.assertThrows(AccessControlException.class, () -> myDi.registerClass(Class1.class)); + Assertions.assertThrows(AccessControlException.class, () -> myDi.registerClass(Class2.class, Class3.class)); + Class1 class1 = myDi.resolveClass(Class1.class); + Class2 class2 = myDi.resolveClass(Class2.class); + Class3 class3 = myDi.resolveClass(Class3.class); + Class4 class4 = myDi.resolveClass(Class4.class); + Class5 class5 = myDi.resolveClass(Class5.class); + Assertions.assertNotNull(class1); + Assertions.assertNotNull(class3); + Assertions.assertNotNull(class5); + Assertions.assertEquals(class5, myDi.resolveClass(Class5.class)); + Assertions.assertNotEquals(class2, class3); + Assertions.assertEquals(class2.getInfo(), "Hello, I'm class3 :)"); + Assertions.assertNotEquals(class2, myDi.resolveClass(Class2.class)); + Assertions.assertNotEquals(class4.class5, class5); + Assertions.assertEquals(class4.class5, myDi.resolveClass(Class4.class).class5); + Assertions.assertEquals(class4.class1.info, "Hello, I'm class1 :)"); + Assertions.assertEquals(class5.getInstance(), class4.class5); + } +} diff --git a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SimpleTests.java b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SimpleTests.java new file mode 100644 index 0000000..bec91e0 --- /dev/null +++ b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SimpleTests.java @@ -0,0 +1,223 @@ +package com._30something_.tests.DI; + +import com._30something.DI.ClassRegistrationException; +import com._30something.DI.InterfaceRegistrationException; +import java.lang.reflect.InvocationTargetException; +import java.security.AccessControlException; + +import com._30something.DI.DI; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import javax.inject.Inject; + +class Car { + public final int speed; + + @Inject + public Car() { + this.speed = 0; + } + + public String start() { + return "Car makes beep"; + } +} + +class Plane { + public int getWeight() { + return weight; + } + + public final int weight; + + @Inject + public Plane() { + this.weight = 42; + } +} + +class Train { + public final int weight = 10; + public final int height; + + @Inject + public Train() { + this.height = 15; + } +} + +class Bicycle { + public Bicycle() {} +} + +class Bicycle1 { + @Inject + private Bicycle1() {} +} + +class Bicycle2 { + private final Car supportCar; + + @Inject + public Bicycle2(Car supportCar) { + this.supportCar = supportCar; + } + + public Car getSupportCar() { + return supportCar; + } +} + +class Bicycle3 { + public final String type; + public final int model; + + @Inject + public Bicycle3(int model) { + this.model = model; + type = "Standard"; + } + + @Inject + public Bicycle3(String type, int model) { + this.type = type; + this.model = model; + } +} + +class Bicycle4 extends Bicycle2 { + @Inject + public Bicycle4(Car supportCar) { + super(supportCar); + } +} + +class Bicycle5 { + @Inject + public Integer gearsNumber; + + @Inject + public Bicycle5() { + gearsNumber = 10; + } +} + +class Bicycle6 { + @Inject + public Bicycle6() { + System.out.println("Bicycle no. 6 created ^_^"); + } +} + +class BicyclesCollection { + public Bicycle6 bicycle6; + public Bicycle5 bicycle5; + public Bicycle4 bicycle4; + public Bicycle2 bicycle2; + + @Inject + public BicyclesCollection(Bicycle6 bicycle6, Bicycle5 bicycle5, Bicycle4 bicycle4, Bicycle2 bicycle2) { + this.bicycle6 = bicycle6; + this.bicycle5 = bicycle5; + this.bicycle4 = bicycle4; + this.bicycle2 = bicycle2; + } +} + +public class SimpleTests { + @Test + public void testSimpleFirst() throws ClassRegistrationException, InterfaceRegistrationException, + ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException { + DI myDi = new DI(); + myDi.registerClass(Car.class); + myDi.registerClass(Plane.class); + myDi.registerClass(Train.class); + myDi.completeRegistration(); + Car myCar = myDi.resolveClass(Car.class); + Plane myPlane = myDi.resolveClass(Plane.class); + Train myTrain = myDi.resolveClass(Train.class); + Assertions.assertNotNull(myCar); + Assertions.assertNotNull(myPlane); + Assertions.assertNotNull(myTrain); + Assertions.assertEquals(myCar.start(), "Car makes beep"); + Assertions.assertEquals(myPlane.getWeight(), 42); + Assertions.assertEquals(myTrain.weight, 10); + } + + @Test + public void testSimpleSecond() throws ClassRegistrationException, InterfaceRegistrationException, + ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException { + DI myDi = new DI(); + myDi.registerClass(Car.class); + Car newCar = new Car(); + Assertions.assertThrows(ClassRegistrationException.class, () -> myDi.registerClass(Car.class)); + Assertions.assertThrows(ClassRegistrationException.class, () -> myDi.registerClass(Bicycle.class)); + Assertions.assertThrows(ClassRegistrationException.class, () -> myDi.registerClass(Bicycle1.class)); + Assertions.assertThrows(ClassRegistrationException.class, () -> myDi.registerClass(Bicycle3.class)); + Bicycle2 myBicycle = new Bicycle2(newCar); + Bicycle3 myStandardBicycle = new Bicycle3(42); + Bicycle3 myNewStandardBicycle = new Bicycle3("Cool", 42); + myDi.registerClass(Bicycle2.class); + Assertions.assertThrows(ClassRegistrationException.class, () -> myDi.registerClass(Bicycle3.class)); + Assertions.assertThrows(AccessControlException.class, () -> myDi.resolveClass(Car.class)); + myDi.completeRegistration(); + Assertions.assertThrows(AccessControlException.class, () -> myDi.registerClass(Car.class)); + Assertions.assertThrows(AccessControlException.class, () -> myDi.registerClass(Plane.class)); + Assertions.assertThrows(ClassNotFoundException.class, () -> myDi.resolveClass(Plane.class)); + Assertions.assertThrows(ClassNotFoundException.class, () -> myDi.resolveClass(Train.class)); + Bicycle2 newMyBicycle = myDi.resolveClass(Bicycle2.class); + Assertions.assertNotNull(newMyBicycle); + Assertions.assertEquals(myBicycle.getSupportCar(), newCar); + Assertions.assertNotNull(newMyBicycle.getSupportCar()); + Assertions.assertEquals(myStandardBicycle.model, myNewStandardBicycle.model); + Assertions.assertThrows(ClassNotFoundException.class, () -> myDi.resolveClass(Bicycle3.class)); + Assertions.assertEquals(myStandardBicycle.model, myNewStandardBicycle.model); + } + + @Test + public void testSimpleThird() throws ClassRegistrationException, InterfaceRegistrationException, + ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException { + DI firstDi = new DI(); + firstDi.registerClass(Bicycle2.class); + firstDi.registerClass(Plane.class); + firstDi.registerClass(Train.class); + Assertions.assertThrows(ClassRegistrationException.class, firstDi::completeRegistration); + Assertions.assertThrows(AccessControlException.class, () -> firstDi.resolveClass(Car.class)); + Assertions.assertThrows(AccessControlException.class, () -> firstDi.resolveClass(Bicycle2.class)); + Assertions.assertThrows(ClassRegistrationException.class, () -> firstDi.registerClass(Train.class)); + Assertions.assertThrows(ClassRegistrationException.class, () -> firstDi.registerClass(Plane.class)); + firstDi.registerClass(Car.class); + firstDi.completeRegistration(); + Assertions.assertThrows(AccessControlException.class, () -> firstDi.registerClass(Car.class)); + Assertions.assertThrows(AccessControlException.class, () -> firstDi.registerClass(Bicycle.class)); + Assertions.assertThrows(ClassNotFoundException.class, () -> firstDi.resolveClass(Bicycle.class)); + Assertions.assertThrows(AccessControlException.class, () -> firstDi.registerClass(Train.class)); + Bicycle2 newBicycle = new Bicycle2(firstDi.resolveClass(Car.class)); + Assertions.assertNotNull(newBicycle); + Assertions.assertEquals(newBicycle.getSupportCar().getClass(), Car.class); + DI secondDi = new DI(); + secondDi.registerClass(Bicycle4.class); + Assertions.assertThrows(ClassRegistrationException.class, () -> secondDi.registerClass(Bicycle4.class)); + Assertions.assertThrows(AccessControlException.class, () -> secondDi.resolveClass(Bicycle4.class)); + secondDi.registerClass(BicyclesCollection.class); + Assertions.assertThrows(ClassRegistrationException.class, secondDi::completeRegistration); + secondDi.registerClass(Bicycle6.class); + Assertions.assertThrows(ClassRegistrationException.class, secondDi::completeRegistration); + secondDi.registerClass(Bicycle5.class); + Assertions.assertThrows(ClassRegistrationException.class, secondDi::completeRegistration); + secondDi.registerClass(Bicycle2.class); + Assertions.assertThrows(ClassRegistrationException.class, () -> secondDi.registerClass(Bicycle4.class)); + Assertions.assertThrows(ClassRegistrationException.class, secondDi::completeRegistration); + secondDi.registerClass(Car.class); + secondDi.completeRegistration(); + BicyclesCollection collection = secondDi.resolveClass(BicyclesCollection.class); + Assertions.assertNotNull(collection); + collection.bicycle2 = secondDi.resolveClass(Bicycle2.class); + Assertions.assertNotNull(collection.bicycle2); + Assertions.assertEquals(collection.bicycle2.getSupportCar().speed, 0); + collection.bicycle6 = secondDi.resolveClass(Bicycle6.class); + Assertions.assertThrows(ClassNotFoundException.class, () -> secondDi.resolveClass(Bicycle3.class)); + collection.bicycle5 = secondDi.resolveClass(Bicycle5.class); + Assertions.assertEquals(collection.bicycle5.gearsNumber, 10); + } +} diff --git a/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SingletonTests.java b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SingletonTests.java new file mode 100644 index 0000000..4426bbe --- /dev/null +++ b/lab-02-dependency-injection/src/test/java/com/_30something_/tests/DI/SingletonTests.java @@ -0,0 +1,102 @@ +package com._30something_.tests.DI; + +import com._30something.DI.ClassRegistrationException; +import com._30something.DI.InterfaceRegistrationException; +import java.lang.reflect.InvocationTargetException; +import java.security.AccessControlException; + +import com._30something.DI.DI; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton +class Factory { + private final Car car; + private final Plane plane; + private final Train train; + private final Bicycle5 bicycle; + private final Bus bus; + public static final Factory instance = new Factory( + new Car(), new Train(), new Plane(), new Bicycle5(), Bus.instance); + + @Inject + private Factory(Car car, Train train, Plane plane, Bicycle5 bicycle5, Bus bus) { + this.car = car; + this.train = train; + this.plane = plane; + this.bicycle = bicycle5; + this.bus = bus; + } + + public Car getCar() { + return car; + } + + public Bus getBus() { + return bus; + } +} + +@Singleton +class Bus { + @Inject + private Integer capacity; + public static final Bus instance = new Bus(); + + @Inject + private Bus() { + this.capacity = 50; + } +} + +public class SingletonTests { + @Test + public void singletonTestsMain() throws ClassRegistrationException, InterfaceRegistrationException, + ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException { + DI singletonDI = new DI(); + singletonDI.registerClass(Car.class); + singletonDI.registerClass(Plane.class); + singletonDI.registerClass(Train.class); + singletonDI.registerClass(Bicycle5.class); + singletonDI.registerClass(Factory.class); + Assertions.assertThrows(ClassRegistrationException.class, () -> singletonDI.registerClass(Factory.class)); + Assertions.assertThrows(ClassRegistrationException.class, singletonDI::completeRegistration); + singletonDI.registerClass(Bus.class); + singletonDI.completeRegistration(); + Assertions.assertThrows(AccessControlException.class, () -> singletonDI.registerClass(Factory.class)); + Car myCar1 = singletonDI.resolveClass(Car.class); + Car myCar2 = singletonDI.resolveClass(Car.class); + Assertions.assertNotNull(myCar1); + Assertions.assertNotNull(myCar2); + Assertions.assertNotEquals(myCar1, myCar2); + Factory factory = singletonDI.resolveClass(Factory.class); + Factory newFactory = singletonDI.resolveClass(Factory.class); + Assertions.assertNotNull(factory); + Assertions.assertNotNull(newFactory); + Assertions.assertNotNull(Factory.instance); + Assertions.assertEquals(factory, newFactory); + Car myFactoryCar1 = factory.getCar(); + Car myFactoryCar2 = factory.getCar(); + Car myFactoryCar3 = newFactory.getCar(); + Assertions.assertNotNull(myFactoryCar1); + Assertions.assertNotNull(myFactoryCar2); + Assertions.assertNotNull(myFactoryCar3); + Assertions.assertEquals(myFactoryCar1, myFactoryCar2); + Assertions.assertEquals(myFactoryCar1, myFactoryCar3); + Assertions.assertEquals(myFactoryCar2, myFactoryCar2); + Assertions.assertThrows(AccessControlException.class, () -> singletonDI.registerClass(Bicycle.class)); + Assertions.assertThrows(ClassNotFoundException.class, () -> singletonDI.resolveClass(Bicycle.class)); + Bus bus1 = factory.getBus(); + Bus bus2 = factory.getBus(); + Assertions.assertNotNull(bus1); + Assertions.assertNotNull(bus2); + Assertions.assertEquals(bus1, bus2); + Bus bus3 = singletonDI.resolveClass(Bus.class); + Bus bus4 = singletonDI.resolveClass(Bus.class); + Assertions.assertNotNull(bus3); + Assertions.assertNotNull(bus4); + Assertions.assertEquals(bus3, bus4); + } +} diff --git a/lab-03-calc-gui/lab3_compose/build.gradle.kts b/lab-03-calc-gui/lab3_compose/build.gradle.kts new file mode 100644 index 0000000..7c80799 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/build.gradle.kts @@ -0,0 +1,8 @@ +group = "me.fedor" +version = "1.0" + +allprojects { + repositories { + mavenCentral() + } +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/BinOpKind.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/BinOpKind.java new file mode 100644 index 0000000..b36d21c --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/BinOpKind.java @@ -0,0 +1,9 @@ +package com._30something.lib_calc; + +public enum BinOpKind { + ADD, + SUBTRACT, + MULTIPLY, + DIVIDE, + DEFAULT, +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/BinaryExpression.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/BinaryExpression.java new file mode 100644 index 0000000..bc31600 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/BinaryExpression.java @@ -0,0 +1,7 @@ +package com._30something.lib_calc; + +public interface BinaryExpression extends Expression { + Expression getLeft(); + Expression getRight(); + BinOpKind getOperation(); +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/BinaryExpressionImpl.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/BinaryExpressionImpl.java new file mode 100644 index 0000000..faf8597 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/BinaryExpressionImpl.java @@ -0,0 +1,34 @@ +package com._30something.lib_calc; + +public class BinaryExpressionImpl implements BinaryExpression { + + private final Expression left; + private final Expression right; + private final BinOpKind operation; + + public BinaryExpressionImpl(Expression left, Expression right, BinOpKind operation) { + this.left = left; + this.right = right; + this.operation = operation; + } + + @Override + public Expression getLeft() { + return left; + } + + @Override + public Expression getRight() { + return right; + } + + @Override + public BinOpKind getOperation() { + return operation; + } + + @Override + public Object accept(ExpressionVisitor visitor) { + return visitor.visitBinaryExpression(this); + } +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ComputeExpressionVisitor.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ComputeExpressionVisitor.java new file mode 100644 index 0000000..95f6954 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ComputeExpressionVisitor.java @@ -0,0 +1,53 @@ +package com._30something.lib_calc; + +import java.util.Map; + +public class ComputeExpressionVisitor implements ExpressionVisitor { + + Map map; + + public ComputeExpressionVisitor(Map map) { + this.map = map; + } + + @Override + public Object visitBinaryExpression(BinaryExpression expr) { + BinOpKind operation = expr.getOperation(); + Double leftRes = (Double) expr.getLeft().accept(this); + Double rightRes = (Double) expr.getRight().accept(this); + switch (operation) { + case ADD: { + return leftRes + rightRes; + } + case SUBTRACT: { + return leftRes - rightRes; + } + case MULTIPLY: { + return leftRes * rightRes; + } + case DIVIDE: { + if (rightRes == 0) throw new ArithmeticException("Division by zero found"); + return leftRes / rightRes; + } + case DEFAULT: { + return 0; + } + } + return null; + } + + @Override + public Object visitLiteral(Literal expr) { + return expr.getValue(); + } + + @Override + public Object visitParenthesis(ParenthesisExpression expr) { + return expr.getExpr().accept(this); + } + + @Override + public Object visitVariable(Variable expr) { + return map.get(expr.getName()); + } +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/DebugRepresentationExpressionVisitor.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/DebugRepresentationExpressionVisitor.java new file mode 100644 index 0000000..5f04a5b --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/DebugRepresentationExpressionVisitor.java @@ -0,0 +1,36 @@ +package com._30something.lib_calc; + +public class DebugRepresentationExpressionVisitor implements ExpressionVisitor { + + @Override + public Object visitBinaryExpression(BinaryExpression expr) { + String leftRes = (String) expr.getLeft().accept(this); + String rightRes = (String) expr.getRight().accept(this); + String operationPrefix; + if (expr.getOperation() == BinOpKind.ADD) { + operationPrefix = "add"; + } else if (expr.getOperation() == BinOpKind.SUBTRACT) { + operationPrefix = "sub"; + } else if (expr.getOperation() == BinOpKind.MULTIPLY) { + operationPrefix = "mul"; + } else { + operationPrefix = "div"; + } + return operationPrefix + "(" + leftRes + ", " + rightRes + ")"; + } + + @Override + public Object visitLiteral(Literal expr) { + return "'" + expr.getValue() + "'"; + } + + @Override + public Object visitParenthesis(ParenthesisExpression expr) { + return "paran-expr(" + expr.getExpr().accept(this) + ")"; + } + + @Override + public Object visitVariable(Variable expr) { + return "var[" + expr.getName() + "]"; + } +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/DepthVisitor.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/DepthVisitor.java new file mode 100644 index 0000000..6148c96 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/DepthVisitor.java @@ -0,0 +1,35 @@ +package com._30something.lib_calc; + +/** + * Visitor class used to count the depth of expression tree. + * In fact counts the distance from current vertex to the farthest leaf plus 1. + * This distance for root matches with the depth of tree. + * + * @author 30something + * @version 1.0 + */ + +public class DepthVisitor implements ExpressionVisitor { + + @Override + public Object visitBinaryExpression(BinaryExpression expr) { + Integer leftRes = (Integer) expr.getLeft().accept(this); + Integer rightRes = (Integer) expr.getRight().accept(this); + return Math.max(leftRes, rightRes) + 1; + } + + @Override + public Object visitLiteral(Literal expr) { + return 1; + } + + @Override + public Object visitParenthesis(ParenthesisExpression expr) { + return (Integer) expr.getExpr().accept(this) + 1; + } + + @Override + public Object visitVariable(Variable expr) { + return 1; + } +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Expression.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Expression.java new file mode 100644 index 0000000..4cfc38e --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Expression.java @@ -0,0 +1,5 @@ +package com._30something.lib_calc; + +public interface Expression { + Object accept(ExpressionVisitor visitor); +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ExpressionParseException.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ExpressionParseException.java new file mode 100644 index 0000000..b1e87f2 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ExpressionParseException.java @@ -0,0 +1,7 @@ +package com._30something.lib_calc; + +public class ExpressionParseException extends Exception { + public ExpressionParseException(String message) { + super(message); + } +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ExpressionVisitor.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ExpressionVisitor.java new file mode 100644 index 0000000..574caa4 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ExpressionVisitor.java @@ -0,0 +1,8 @@ +package com._30something.lib_calc; + +public interface ExpressionVisitor { + Object visitBinaryExpression(BinaryExpression expr); + Object visitLiteral(Literal expr); + Object visitParenthesis(ParenthesisExpression expr); + Object visitVariable(Variable expr); +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Literal.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Literal.java new file mode 100644 index 0000000..36481c5 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Literal.java @@ -0,0 +1,5 @@ +package com._30something.lib_calc; + +public interface Literal extends Expression { + double getValue(); +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/LiteralImpl.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/LiteralImpl.java new file mode 100644 index 0000000..93d139b --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/LiteralImpl.java @@ -0,0 +1,20 @@ +package com._30something.lib_calc; + +public class LiteralImpl implements Literal { + + private final Double value; + + public LiteralImpl(Double value) { + this.value = value; + } + + @Override + public Object accept(ExpressionVisitor visitor) { + return visitor.visitLiteral(this); + } + + @Override + public double getValue() { + return value; + } +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Main.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Main.java new file mode 100644 index 0000000..a03b233 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Main.java @@ -0,0 +1,37 @@ +package com._30something.lib_calc; + +import java.util.Map; +import java.util.Scanner; + +public class Main { + public static void main(String[] args) { + Scanner in = new Scanner(System.in); + ParserImpl parser = new ParserImpl(); + DebugRepresentationExpressionVisitor debugVisitor = new DebugRepresentationExpressionVisitor(); + DepthVisitor depthVisitor = new DepthVisitor(); + ToStringVisitor toStringVisitor = ToStringVisitor.INSTANCE; + boolean correctInput = false; + while (!correctInput) { + try { + System.out.print("Enter expression: "); + Expression expr = parser.parseExpression(in.nextLine()); + System.out.print("Tree: "); + System.out.println((String) (expr.accept(debugVisitor))); + System.out.print("Expr-tree depth: "); + System.out.println(expr.accept(depthVisitor)); + System.out.print("Reconstructed expression: "); + System.out.println((String) (expr.accept(toStringVisitor))); + RequestVisitor requestVisitor = new RequestVisitor(in); + expr.accept(requestVisitor); + Map variablesMap = requestVisitor.getVariablesMap(); + ComputeExpressionVisitor computeVisitor = new ComputeExpressionVisitor(variablesMap); + System.out.print("Result: "); + System.out.println(expr.accept(computeVisitor)); + correctInput = true; + } catch (Exception exc) { + System.out.println(exc.getMessage()); + System.out.println("Please, input the expression again"); + } + } + } +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ParenthesisExpression.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ParenthesisExpression.java new file mode 100644 index 0000000..45c8852 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ParenthesisExpression.java @@ -0,0 +1,5 @@ +package com._30something.lib_calc; + +public interface ParenthesisExpression extends Expression { + Expression getExpr(); +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ParenthesisExpressionImpl.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ParenthesisExpressionImpl.java new file mode 100644 index 0000000..c6eb54b --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ParenthesisExpressionImpl.java @@ -0,0 +1,20 @@ +package com._30something.lib_calc; + +public class ParenthesisExpressionImpl implements ParenthesisExpression { + + private final Expression childExpr; + + public ParenthesisExpressionImpl(Expression childExpr) { + this.childExpr = childExpr; + } + + @Override + public Object accept(ExpressionVisitor visitor) { + return visitor.visitParenthesis(this); + } + + @Override + public Expression getExpr() { + return childExpr; + } +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Parser.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Parser.java new file mode 100644 index 0000000..3ddff75 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Parser.java @@ -0,0 +1,12 @@ +package com._30something.lib_calc; + +public interface Parser { + /** + * Parses expression from the string. + * If the string doesn't represent a valid expression, then throws ExpressionParseException. + * + * @param input the input string. + * @return parsed expression tree. + */ + Expression parseExpression(String input) throws ExpressionParseException; +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ParserImpl.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ParserImpl.java new file mode 100644 index 0000000..310e8c0 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ParserImpl.java @@ -0,0 +1,247 @@ +package com._30something.lib_calc; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Stack; + +public class ParserImpl implements Parser { + + public enum CharTypes { + NUMBER, + VARIABLE, + OPERATOR, + DEFAULT, + } + + public static class Token { + public String string; + public CharTypes type; + + public Token(String string, CharTypes type) { + this.string = string; + this.type = type; + } + } + + public CharTypes charType(char c) { + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) return CharTypes.VARIABLE; + if ((c >= '0' && c <= '9') || c == '.') return CharTypes.NUMBER; + if (c == '-' || c == '+' || c == '*' || c == '/' || c == '(' || c == ')') return CharTypes.OPERATOR; + return CharTypes.DEFAULT; + } + + public List tokenize(String input) { + List tokensList = new ArrayList<>(); + Token tempToken; + char[] chars = input.toCharArray(); + for (int currentPtr = 0; currentPtr < chars.length; ) { + CharTypes tokenType = charType(chars[currentPtr]); + if (tokenType == CharTypes.VARIABLE || tokenType == CharTypes.NUMBER) { + StringBuilder tokenName = new StringBuilder(); + while (currentPtr < chars.length && charType(chars[currentPtr]) == tokenType) { + tokenName.append(chars[currentPtr]); + currentPtr++; + } + tempToken = new Token(tokenName.toString(), tokenType); + tokensList.add(tempToken); + } else { + tempToken = new Token(Character.toString(chars[currentPtr]), tokenType); + tokensList.add(tempToken); + currentPtr++; + } + } + return tokensList; + } + + /** + * Validates order of tokens in expression and constructs new tokens. + * It translates variables like '-x' as '-1 * x'. + * But lefts numbers like '-123' as '-123' and ignores unary plus. + * + * @param tokens - raw tokens + * @return newTokens - verified tokens + * @throws ExpressionParseException - parsing exception + */ + public List verifyTokens(List tokens) throws ExpressionParseException { + List newTokens = new ArrayList<>(); + Token pastToken = new Token("", CharTypes.DEFAULT); + int leftBrackets = 0; + boolean unaryMinus = false; + for (Token token : tokens) { + if (Objects.equals(token.string, " ")) { + continue; + } + if (token.type == CharTypes.DEFAULT || Objects.equals(token.string, ".")) { + throw new ExpressionParseException("Unexpected token found: '" + token.string + "'"); + } + if (token.type == CharTypes.OPERATOR) { + if (Objects.equals(token.string, "(")) { + leftBrackets++; + if (unaryMinus) { + newTokens.add(new Token("-1", CharTypes.NUMBER)); + newTokens.add(new Token("*", CharTypes.OPERATOR)); + unaryMinus = false; + } else if (Objects.equals(pastToken.string, ")") || pastToken.type == CharTypes.VARIABLE || + pastToken.type == CharTypes.NUMBER) { + throw new ExpressionParseException( + "Wrong order of operators found (left bracket after right bracket or literal)"); + } + newTokens.add(token); + } else if (pastToken.type == CharTypes.DEFAULT) { + if (Objects.equals(token.string, "-")) { + unaryMinus = true; + } else if (!Objects.equals(token.string, "+")) { + throw new ExpressionParseException( + "Wrong order of operators found (operator in the start of expression)"); + } + } else if (Objects.equals(token.string, ")")) { + leftBrackets--; + if (leftBrackets < 0) { + throw new ExpressionParseException("Overflow with right brackets found"); + } + if (pastToken.type == CharTypes.OPERATOR && !Objects.equals(pastToken.string, ")")) { + throw new ExpressionParseException( + "Wrong order of operators found (right bracket not after literal or right bracket)"); + } + newTokens.add(token); + } else { + if (pastToken.type == CharTypes.OPERATOR) { + if (!Objects.equals(pastToken.string, ")") && !Objects.equals(pastToken.string, "(")) { + throw new ExpressionParseException( + "Wrong order of operators found (operator after operator)"); + } else if (Objects.equals(pastToken.string, "(")) { + if (Objects.equals(token.string, "*") || Objects.equals(token.string, "/")) { + throw new ExpressionParseException( + "Wrong order of operators found (wrong operator after left bracket)"); + } else if (Objects.equals(token.string, "-")) { + unaryMinus = true; + } + } else { + newTokens.add(token); + } + } else { + newTokens.add(token); + } + } + } else { + if (pastToken.type == CharTypes.NUMBER || pastToken.type == CharTypes.VARIABLE) { + throw new ExpressionParseException( + "Wrong order of operators found (literal after literal)"); + } + if (Objects.equals(pastToken.string, ")")) { + throw new ExpressionParseException( + "Wrong order of operators found (literal after right bracket)"); + } + if (token.type == CharTypes.NUMBER && token.string.chars().filter(c -> c == '.').count() > 1) { + throw new ExpressionParseException("Two dots in float number found: '" + token.string + "'"); + } + if (unaryMinus) { + if (token.type == CharTypes.NUMBER) { + newTokens.add(new Token("-" + token.string, token.type)); + } else { + newTokens.add(new Token("-1", CharTypes.NUMBER)); + newTokens.add(new Token("*", CharTypes.OPERATOR)); + newTokens.add(token); + } + unaryMinus = false; + } else { + newTokens.add(token); + } + } + pastToken = token; + } + if (pastToken.type != CharTypes.NUMBER && pastToken.type != CharTypes.VARIABLE && + !Objects.equals(pastToken.string, ")")) { + throw new ExpressionParseException("Wrong order of operators found (operator in the end of expression)"); + } + if (leftBrackets > 0) { + throw new ExpressionParseException("Overflow with left brackets found"); + } + if (newTokens.isEmpty()) { + throw new ExpressionParseException("Expression is empty or insignificant"); + } + return newTokens; + } + + public List buildPolishNotation(List tokens) { + Stack operators = new Stack<>(); + List newList = new ArrayList<>(); + for (Token token : tokens) { + if (token.type == CharTypes.OPERATOR) { + if (Objects.equals(token.string, "(")) { + operators.add(token); + newList.add(token); + } else if (Objects.equals(token.string, ")")) { + while (!Objects.equals(operators.peek().string, "(")) { + newList.add(operators.peek()); + operators.pop(); + } + operators.pop(); + newList.add(token); + } else if (Objects.equals(token.string, "*") || Objects.equals(token.string, "/")) { + while (!operators.empty() && (Objects.equals(operators.peek().string, "*") || + Objects.equals(operators.peek().string, "/"))) { + newList.add(operators.peek()); + operators.pop(); + } + operators.push(token); + } else { + while (!operators.empty() && !Objects.equals(operators.peek().string, "(")) { + newList.add(operators.peek()); + operators.pop(); + } + operators.push(token); + } + } else { + newList.add(token); + } + } + while (!operators.empty()) { + newList.add(operators.peek()); + operators.pop(); + } + return newList; + } + + public Expression buildExpression(List tokens) throws ExpressionParseException { + Stack expressions = new Stack<>(); + for (Token token : tokens) { + if (token.type == CharTypes.OPERATOR) { + if (Objects.equals(token.string, ")")) { + Expression lower_expr = expressions.peek(); + expressions.pop(); + expressions.push(new ParenthesisExpressionImpl(lower_expr)); + } else if (!Objects.equals(token.string, "(")) { + Expression right_expr = expressions.peek(); + expressions.pop(); + Expression left_expr = expressions.peek(); + expressions.pop(); + BinOpKind operation; + if (Objects.equals(token.string, "+")) operation = BinOpKind.ADD; + else if (Objects.equals(token.string, "-")) operation = BinOpKind.SUBTRACT; + else if (Objects.equals(token.string, "*")) operation = BinOpKind.MULTIPLY; + else operation = BinOpKind.DIVIDE; + expressions.push(new BinaryExpressionImpl(left_expr, right_expr, operation)); + } + } else if (token.type == CharTypes.NUMBER) { + expressions.push(new LiteralImpl(Double.parseDouble(token.string))); + } else { + expressions.push(new VariableImpl(token.string)); + } + } + if (expressions.size() > 1) { + // In case if method 'verifiedTokens' didn't find any errors in expression + throw new ExpressionParseException("Wrong order of operands found"); + } + return expressions.peek(); + } + + @Override + public Expression parseExpression(String input) throws ExpressionParseException { + List rawTokens = tokenize(input); + List verifiedTokens = verifyTokens(rawTokens); + List polishNotationTokens = buildPolishNotation(verifiedTokens); + return buildExpression(polishNotationTokens); + } +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/RequestVisitor.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/RequestVisitor.java new file mode 100644 index 0000000..36dbca2 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/RequestVisitor.java @@ -0,0 +1,55 @@ +package com._30something.lib_calc; + +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; + +public class RequestVisitor implements ExpressionVisitor { + + Map map = new HashMap<>(); + Scanner in; + + public RequestVisitor(Scanner in) { + this.in = in; + } + + @Override + public Object visitBinaryExpression(BinaryExpression expr) { + expr.getLeft().accept(this); + expr.getRight().accept(this); + return null; + } + + @Override + public Object visitLiteral(Literal expr) { + return null; + } + + @Override + public Object visitParenthesis(ParenthesisExpression expr) { + return expr.getExpr().accept(this); + } + + @Override + public Object visitVariable(Variable expr) { + String varName = expr.getName(); + if (!map.containsKey(varName)) { + boolean correctInput = false; + while (!correctInput) { + try { + System.out.printf("Enter value for '%s': ", varName); + map.put(varName, Double.parseDouble(in.nextLine())); + correctInput = true; + } catch (Exception exc) { + System.out.println("Unable to convert input string to value"); + System.out.println("Please input value again"); + } + } + } + return null; + } + + public Map getVariablesMap() { + return map; + } +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Tests.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Tests.java new file mode 100644 index 0000000..2bf739c --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Tests.java @@ -0,0 +1,59 @@ +package com._30something.lib_calc; + +import java.io.*; +import java.util.Objects; +import java.util.Scanner; + +/** + * All tests are kept in tests.txt in root directory. One test consists of 3 lines. + * In order not to complicate the tests with the presence of variables, please don't use them. + * Tests format (4 lines): + * 'Entered expression' + * 'Expected parsed to string expression' + * 'Computed result' + * 'Line with one space' + */ +public class Tests { + public static void main(String[] args) { + try { + int testNumber = 0; + int successfulTests = 0; + FileReader input = new FileReader("lab-01-expr-calc/tests.txt"); + Scanner in = new Scanner(input); + Parser parser = new ParserImpl(); + ComputeExpressionVisitor computeVisitor = new ComputeExpressionVisitor(null); + ToStringVisitor toStringVisitor = ToStringVisitor.INSTANCE; + while (in.hasNextLine()) { + boolean successfulTest = true; + testNumber++; + String expr = in.nextLine(); + String expectedParsedExpr = in.nextLine(); + Double expectedResult = Double.parseDouble(in.nextLine()); + in.nextLine(); + Expression parsedExpr = parser.parseExpression(expr); + String parsedToStringExpr = (String) parsedExpr.accept(toStringVisitor); + if (!Objects.equals(expectedParsedExpr, parsedToStringExpr)) { + System.out.printf("Error found in parsing of expression in test #%d%n", testNumber); + System.out.printf("Expected parsed expression: %s%n", expectedParsedExpr); + System.out.printf("Received parsed expression: %s%n", parsedToStringExpr); + successfulTest = false; + } + Double result = (Double) parsedExpr.accept(computeVisitor); + if (!expectedResult.equals(result)) { + System.out.printf("Error found in computing result of expression in test #%d%n", testNumber); + System.out.printf("Expected result: %s%n", expectedResult); + System.out.printf("Received result: %s%n", result); + successfulTest = false; + } + if (successfulTest) { + successfulTests++; + } + } + System.out.printf("Successfully checked tests: %d/%d", successfulTests, testNumber); + input.close(); + } catch (Exception exc) { + System.out.println("Error occurred while reading/performing tests. Message:"); + System.out.println(exc.getMessage()); + } + } +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ToStringVisitor.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ToStringVisitor.java new file mode 100644 index 0000000..0563e4e --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/ToStringVisitor.java @@ -0,0 +1,40 @@ +package com._30something.lib_calc; + +public class ToStringVisitor implements ExpressionVisitor { + + public static final ToStringVisitor INSTANCE = new ToStringVisitor(); + + private ToStringVisitor() {} + + @Override + public Object visitBinaryExpression(BinaryExpression expr) { + String leftRes = (String) expr.getLeft().accept(this); + String rightRes = (String) expr.getRight().accept(this); + String operation; + if (expr.getOperation() == BinOpKind.ADD) { + operation = "+"; + } else if (expr.getOperation() == BinOpKind.SUBTRACT) { + operation = "-"; + } else if (expr.getOperation() == BinOpKind.MULTIPLY) { + operation = "*"; + } else { + operation = "/"; + } + return leftRes + " " + operation + " " + rightRes; + } + + @Override + public Object visitLiteral(Literal expr) { + return Double.toString(expr.getValue()); + } + + @Override + public Object visitParenthesis(ParenthesisExpression expr) { + return "(" + expr.getExpr().accept(this) + ")"; + } + + @Override + public Object visitVariable(Variable expr) { + return expr.getName(); + } +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Variable.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Variable.java new file mode 100644 index 0000000..5885474 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/Variable.java @@ -0,0 +1,5 @@ +package com._30something.lib_calc; + +public interface Variable extends Expression { + String getName(); +} diff --git a/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/VariableImpl.java b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/VariableImpl.java new file mode 100644 index 0000000..ec8eb06 --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/lib-calc/src/main/java/com/_30something/lib_calc/VariableImpl.java @@ -0,0 +1,20 @@ +package com._30something.lib_calc; + +public class VariableImpl implements Variable { + + private final String name; + + public VariableImpl(String name) { + this.name = name; + } + + @Override + public Object accept(ExpressionVisitor visitor) { + return visitor.visitVariable(this); + } + + @Override + public String getName() { + return name; + } +} diff --git a/lab-03-calc-gui/lab3_compose/settings.gradle.kts b/lab-03-calc-gui/lab3_compose/settings.gradle.kts new file mode 100644 index 0000000..584aaac --- /dev/null +++ b/lab-03-calc-gui/lab3_compose/settings.gradle.kts @@ -0,0 +1,12 @@ +pluginManagement { + repositories { + google() + gradlePluginPortal() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + } + +} +rootProject.name = "lab3_compose" + +include(":calc-gui") +include(":lib-calc") diff --git a/lab-03-calc-gui/task.md b/lab-03-calc-gui/task.md new file mode 100644 index 0000000..6595b7e --- /dev/null +++ b/lab-03-calc-gui/task.md @@ -0,0 +1,29 @@ +# Упражнение 03 - Calculator GUI + +Используя код упражнения 01, написать GUI на Swing для приложения калькулятора. + +### Требования к окну приложения + +- Поле с текстом выражения, набранным пользователем. +- Поле с результатом, если выражение валидно; ошибку вычисления, если таковая появилась (напр. деление на 0); +если введённый текст не может быть преобразован в выражение, поле результата должно быть пустым. +- Набор кнопок с цифрами, операторами, скобками. +- Возможность ввести текст выражения кнопками из окна или с клавиатуры. +- На данном этапе поддержка переменных не требуется. + +## Дополнительные пункты задания +- Поддержать ввод переменных в выражение: + - В GUI сделать список (JList), в который можно добавлять информацию вида `variable = value`, для учёта в выражении. + - Предыдущий пункт можно сделать "дешевле" просто сделав multi-line текстовое поле и парсить оттуда строки `variable = value`, +но это оценится чуть ниже. +- Выводить в окне калькулятора результаты работы посетителей из задания-01 (depth, debug-representation), + оформить на свое усмотрение. + +### Требуемая архитектура +- Код из лабы-01, он же движок калькулятора, находится в отдельном gradle модуле, например `:lib-calc`. +- GUI код находится в отдельном gradle модуле (напр. `:calc-gui`), зависящем от `:lib-calc`. +- Язык программирования для модуля с gui - **Kotlin**, модуль `lib-calc` оставить как есть на Java. +- UI фреймворк - Swing, или любой другой, если хотите и разберётесь. + +Для справки: +- https://docs.oracle.com/javase/tutorial/uiswing, офф. док. \ No newline at end of file