Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@ public interface ActionCommandFactory {
Command getPointCommand(@Assisted Point point);

@Named("shape")
Command getShapeCommand(@Assisted Boolean silent, @Assisted Shape shape);
Command getShapeCommand(@Assisted ShapeOutputMode outputMode, @Assisted Shape shape);

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
* @author progys
*/
public interface CommandFactory {
Command getCommand(ParsedObject parsed, boolean silentCommands);
Command getCommand(ParsedObject parsed, ShapeOutputMode outputMode);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ public class GeneralCommandFactory implements CommandFactory {
this.actionCommandFactory = actionCommandFactory;
}

public Command getCommand(ParsedObject parsed, boolean silentCommands) {
public Command getCommand(ParsedObject parsed, ShapeOutputMode outputMode) {
return switch (parsed) {
case null -> actionCommandFactory.getEmptyCommand();
case ParsedPoint parsedPoint ->
actionCommandFactory.getPointCommand(parsedPoint.point());
case ParsedShape parsedShape ->
actionCommandFactory.getShapeCommand(silentCommands, parsedShape.shape());
actionCommandFactory.getShapeCommand(outputMode, parsedShape.shape());
case ParsedAction parsedAction -> switch (parsedAction.name()) {
case exit -> actionCommandFactory.getExitCommand();
case help -> actionCommandFactory.getHelpCommand();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ public void process() {
" exit - terminates the program\n",
"Interactive commands examples: ",
" triangle 4.5 1 -2.5 -33 23 0.3 - creates triangle (4.5,1) (-2.5, -33) (23, 0.3)",
" donut 1.1 7.8 2 1.8 - creates donut with center at (1.1, 7.8) inner radius 1.8 and outer radius 2",
" circle 3 5 2 - creates circle with center at (3, 5) inner radius 1.8 and outer radius 2",
" donut 1.1 7.8 1.8 2 - creates donut with center at (1.1, 7.8) inner radius 1.8 and outer radius 2",
" circle 3 5 2 - creates circle with center at (3, 5) and radius 2",
" 5.1 6.2 - prints all shapes which include given point (5.1, 6.2) with their surface area and also total area."
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,29 @@
public class ShapeCommand extends AbstractCommand {
private final Store persistence;
private final Shape shape;
private final boolean silent;
private final ShapeOutputMode outputMode;

@Inject
public ShapeCommand(Store persistence, @Assisted Shape shape, @Assisted boolean silent,
PrintStream output) {
public ShapeCommand(Store persistence, @Assisted ShapeOutputMode outputMode,
@Assisted Shape shape, PrintStream output) {
super(output);
this.persistence = persistence;
this.shape = shape;
this.silent = silent;
this.outputMode = outputMode;
}

@Override
public void process() {
StoredShape storedShape = persistence.put(shape);
if (!silent) {
if (outputMode == ShapeOutputMode.VERBOSE) {
output.println(storedShape);
}
}

@Override
protected void printSeparator() {
if (!silent)
if (outputMode == ShapeOutputMode.VERBOSE) {
super.printSeparator();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.progys.interview.quiz.commands;

/**
* Controls whether shape creation output is printed.
*
* @author progys
*/
public enum ShapeOutputMode {
SILENT, VERBOSE
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public boolean inShape(Point point) {
* (v0.y * v2.x - v0.x * v2.y + (v2.y - v0.y) * point.x + (v0.x - v2.x) * point.y);
double t = d
* (v0.x * v1.y - v0.y * v1.x + (v0.y - v1.y) * point.x + (v1.x - v0.x) * point.y);
return s >= 0 && t >= 0 && (s + t) < 1;
return s > 0 && t > 0 && (s + t) < 1;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Defines an object storage layer.
* Object storage layer. The database is the source of truth across restarts, but a copy of all
* shapes is kept in memory so point queries never hit the database (the quiz requires queries to
* scale to tens of millions of shapes held in program memory).
*
* @author progys
*/
Expand All @@ -24,11 +28,13 @@ public class ObjectStore implements Store {

private final EntityManagerFactory entityManagerFactory;
private final EntityManager manager;
private final List<StoredShape> shapes;

@Inject
ObjectStore(EntityManagerFactory entityManagerFactory) {
this.entityManagerFactory = entityManagerFactory;
this.manager = entityManagerFactory.createEntityManager();
this.shapes = new ArrayList<>(loadShapesFromDatabase());
}

@Override
Expand All @@ -40,7 +46,9 @@ public StoredShape put(Shape shape) {
manager.persist(entity);
manager.flush();
transaction.commit();
return new StoredShape(entity.getId(), shape);
StoredShape stored = new StoredShape(entity.getId(), shape);
shapes.add(stored);
return stored;
} catch (RuntimeException e) {
if (transaction.isActive()) {
transaction.rollback();
Expand All @@ -57,6 +65,7 @@ public void clear() {
try {
manager.createQuery("delete from ShapeEntity").executeUpdate();
transaction.commit();
shapes.clear();
} catch (RuntimeException e) {
if (transaction.isActive()) {
transaction.rollback();
Expand All @@ -68,10 +77,13 @@ public void clear() {

@Override
public Collection<StoredShape> getAll() {
return Collections.unmodifiableList(shapes);
}

private List<StoredShape> loadShapesFromDatabase() {
TypedQuery<ShapeEntity> query = manager.createQuery(
"SELECT e FROM " + ShapeEntity.class.getName() + " e", ShapeEntity.class);
List<ShapeEntity> entities = query.getResultList();
return entities.stream()
return query.getResultList().stream()
.map(entity -> new StoredShape(entity.getId(), entity.toShape()))
.toList();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.google.inject.Inject;
import com.progys.interview.quiz.commands.CommandFactory;
import com.progys.interview.quiz.commands.ShapeOutputMode;
import com.progys.interview.quiz.parser.ParsedObject;
import com.progys.interview.quiz.parser.Parser;
import com.progys.interview.quiz.parser.ParserFactory;
Expand Down Expand Up @@ -40,7 +41,7 @@ private void readLines(Scanner scanner) {
try {
String command = scanner.nextLine();
Parser<ParsedObject> parser = parserFactory.create(command);
commandFactory.getCommand(parser.parse(), false).execute();
commandFactory.getCommand(parser.parse(), ShapeOutputMode.VERBOSE).execute();
} catch (Exception e) {
System.err.println(e.getMessage());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.progys.interview.quiz.commands.CommandFactory;
import com.progys.interview.quiz.commands.ShapeOutputMode;
import com.progys.interview.quiz.model.Point;
import com.progys.interview.quiz.model.Shape;
import com.progys.interview.quiz.parser.ConcreteParserFactory;
Expand All @@ -11,29 +12,32 @@

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.util.Scanner;

public class FileInputProcessor implements InputProcessor {
private final File file;
private final CommandFactory commandFactory;
private final ConcreteParserFactory concreteParserFactory;
private final PrintStream output;

@Inject
FileInputProcessor(CommandFactory commandFactory, @Assisted File input,
ConcreteParserFactory concreteParserFactory) {
ConcreteParserFactory concreteParserFactory, PrintStream output) {
this.file = input;
this.commandFactory = commandFactory;
this.concreteParserFactory = concreteParserFactory;
this.output = output;
}

public void process() {
try (Scanner scanner = new Scanner(file)) {
System.out.println("Reading provided input file: " + file.getAbsolutePath());
output.println("Reading provided input file: " + file.getAbsolutePath());
while (scanner.hasNextLine()) {
processLine(scanner.nextLine());
}
} catch (FileNotFoundException e) {
System.out.println("File not found: " + file.getAbsolutePath() + "\n");
output.println("File not found: " + file.getAbsolutePath() + "\n");
}
}

Expand All @@ -42,9 +46,10 @@ private void processLine(String line) {
Parser<Point> pointParser = concreteParserFactory.getPointParser(lineScanner);
Parser<Shape> shapeParser = concreteParserFactory.getShapeParser(lineScanner,
pointParser);
commandFactory.getCommand(new ParsedShape(shapeParser.parse()), true).execute();
commandFactory.getCommand(new ParsedShape(shapeParser.parse()),
ShapeOutputMode.SILENT).execute();
} catch (Exception e) {
System.out.println("Exception while reading input from file: " + e.getMessage());
output.println("Exception while reading input from file: " + e.getMessage());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ public void printsCommandExamples() {
String output = outputContent.toString();
assertThat(output).contains(
"triangle 4.5 1 -2.5 -33 23 0.3 - creates triangle (4.5,1) (-2.5, -33) (23, 0.3)",
"donut 1.1 7.8 2 1.8 - creates donut with center at (1.1, 7.8) inner radius 1.8 and outer radius 2",
"circle 3 5 2 - creates circle with center at (3, 5) inner radius 1.8 and outer radius 2"
"donut 1.1 7.8 1.8 2 - creates donut with center at (1.1, 7.8) inner radius 1.8 and outer radius 2",
"circle 3 5 2 - creates circle with center at (3, 5) and radius 2"
);
}
}
16 changes: 16 additions & 0 deletions src/test/java/com/progys/interview/quiz/model/CircleTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,20 @@ public void calculatesAreaWithRadiusTwo() {
assertThat(circleWithRadiusTwo.getArea())
.isCloseTo(Math.PI * 4, Assertions.within(0.01));
}

@Test
public void containsPointInsideCircle() {
assertThat(circleWithRadiusOne.inShape(new Point(1, 1))).isTrue();
assertThat(circleWithRadiusOne.inShape(new Point(1.5, 1))).isTrue();
}

@Test
public void doesNotContainPointOutsideCircle() {
assertThat(circleWithRadiusOne.inShape(new Point(3, 1))).isFalse();
}

@Test
public void doesNotContainPointOnBoundary() {
assertThat(circleWithRadiusOne.inShape(new Point(2, 1))).isFalse();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.progys.interview.quiz.parser;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class CommandParserTest {

@Test
void parsesKnownCommands() {
assertThat(new CommandParser("list").parse())
.isEqualTo(new ParsedAction(ActionNames.list));
assertThat(new CommandParser("exit").parse())
.isEqualTo(new ParsedAction(ActionNames.exit));
assertThat(new CommandParser("help").parse())
.isEqualTo(new ParsedAction(ActionNames.help));
assertThat(new CommandParser("clear").parse())
.isEqualTo(new ParsedAction(ActionNames.clear));
}

@Test
void parsesEmptyInputAsEmptyCommand() {
assertThat(new CommandParser("").parse())
.isEqualTo(new ParsedAction(ActionNames.empty));
}

@Test
void throwsOnUnknownCommand() {
assertThatThrownBy(() -> new CommandParser("bogus").parse())
.isInstanceOf(IllegalArgumentException.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.progys.interview.quiz.parser;

import com.progys.interview.quiz.exceptions.ParseException;
import com.progys.interview.quiz.model.Point;
import com.progys.interview.quiz.model.Shape;
import org.junit.jupiter.api.Test;

import java.util.Scanner;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class GeneralParserFactoryTest {
private final GeneralParserFactory parserFactory =
new GeneralParserFactory(new ConcreteParserFactory() {
@Override
public Parser<Point> getPointParser(Scanner scanner) {
return new PointParser(scanner);
}

@Override
public Parser<Shape> getShapeParser(Scanner scanner, Parser<Point> pointParser) {
return new ShapeParser(scanner, pointParser);
}

@Override
public Parser<ParsedAction> getCommandParser(String command) {
return new CommandParser(command);
}
});

@Test
void parsesActionCommand() {
assertThat(parserFactory.create("list").parse())
.isEqualTo(new ParsedAction(ActionNames.list));
}

@Test
void parsesEmptyInputAsActionCommand() {
assertThat(parserFactory.create("").parse())
.isEqualTo(new ParsedAction(ActionNames.empty));
}

@Test
void parsesPointQuery() {
ParsedObject parsed = parserFactory.create("1 2").parse();

assertThat(parsed).isInstanceOf(ParsedPoint.class);
assertThat(((ParsedPoint) parsed).point().x).isEqualTo(1);
assertThat(((ParsedPoint) parsed).point().y).isEqualTo(2);
}

@Test
void parsesShape() {
ParsedObject parsed = parserFactory.create("circle 0 0 1").parse();

assertThat(parsed).isInstanceOf(ParsedShape.class);
}

@Test
void throwsParseExceptionOnInvalidShape() {
assertThatThrownBy(() -> parserFactory.create("circle 0 0 0").parse())
.isInstanceOf(ParseException.class);
}
}
Loading
Loading