<file_sep>package uk.co.jimmythompson.robocleaner.geometry;

import org.junit.Assert;
import org.junit.Test;

public class CoordinateTests {
    @Test
    public void getXShouldReturnSpecifiedXCoordinate() {
        Coordinate coordinate = new Coordinate(1, 0);

        Assert.assertEquals(1, coordinate.getX());
    }

    @Test
    public void getYShouldReturnSpecifiedYCoordinate() {
        Coordinate coordinate = new Coordinate(0, 1);

        Assert.assertEquals(1, coordinate.getY());
    }

    @Test
    public void translateShouldApplyTranslations() {
        Coordinate coordinate = new Coordinate(0, 1);
        Translation translation = Translation.of(2, -1);
        Coordinate expectedResult = new Coordinate(2, 0);

        Assert.assertEquals(expectedResult, coordinate.translate(translation));
    }

    @Test
    public void equalsShouldReturnTrueIfObjectsAreTheSame() {
        Coordinate coordinate = new Coordinate(5, 5);

        Assert.assertEquals(coordinate, coordinate);
    }

    @Test
    public void equalsShouldReturnFalseIfTheOtherIsNull() {
        Coordinate coordinate = new Coordinate(3, 3);

        Assert.assertNotEquals(coordinate, null);
    }

    @Test
    public void equalsShouldReturnFalseIfTheTypesAreNotTheSame() {
        Coordinate coordinate = new Coordinate(2, 2);

        Assert.assertNotEquals(coordinate, new Object());
    }

    @Test
    public void equalsShouldReturnTrueIfXAndYAreTheSame() {
        Coordinate coordinate = new Coordinate(1, 1);
        Coordinate other = new Coordinate(1, 1);

        Assert.assertEquals(coordinate, other);
    }

    @Test
    public void toStringShouldShowXAndY() {
        Coordinate coordinate = new Coordinate(3, 2);

        Assert.assertEquals("Coordinate{x=3, y=2}", coordinate.toString());
    }
}
<file_sep>package uk.co.jimmythompson.robocleaner.cleaning;

public class SpillStatus {
    private final int totalPatches;
    private final int cleanedPatches;

    public SpillStatus(int totalPatches, int cleanedPatches) {
        this.totalPatches = totalPatches;
        this.cleanedPatches = cleanedPatches;
    }

    public int getTotalPatches() {
        return this.totalPatches;
    }

    public int getCleanedPatches() {
        return cleanedPatches;
    }
}
<file_sep>package uk.co.jimmythompson.robocleaner.geometry;

public enum Direction {
    NORTH(Translation.of(0, 1)),
    EAST(Translation.of(1, 0)),
    SOUTH(Translation.of(0, -1)),
    WEST(Translation.of(-1, 0));

    private Translation translation;

    Direction(Translation translation) {
        this.translation = translation;
    }

    public Translation getTranslation() {
        return translation;
    }
}
<file_sep>package uk.co.jimmythompson.robocleaner.web;

import org.junit.Assert;
import org.junit.Test;
import uk.co.jimmythompson.robocleaner.geometry.Direction;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class RouteParserTests {
    @Test
    public void shouldParseNAsNorth() {
        List<Direction> expectedList = Collections.singletonList(Direction.NORTH);
        Assert.assertEquals(expectedList, RouteParser.parseRoute("N"));
    }

    @Test
    public void shouldParseEAsEast() {
        List<Direction> expectedList = Collections.singletonList(Direction.EAST);
        Assert.assertEquals(expectedList, RouteParser.parseRoute("E"));
    }

    @Test
    public void shouldParseSAsSouth() {
        List<Direction> expectedList = Collections.singletonList(Direction.SOUTH);
        Assert.assertEquals(expectedList, RouteParser.parseRoute("S"));
    }

    @Test
    public void shouldParseWAsWest() {
        List<Direction> expectedList = Collections.singletonList(Direction.WEST);
        Assert.assertEquals(expectedList, RouteParser.parseRoute("W"));
    }

    @Test
    public void shouldBeAbleToComposeRoutes() {
        List<Direction> expectedList = Arrays.asList(
                Direction.NORTH,
                Direction.EAST,
                Direction.SOUTH,
                Direction.WEST
        );

        Assert.assertEquals(expectedList, RouteParser.parseRoute("NESW"));
    }
}
<file_sep>package uk.co.jimmythompson.robocleaner.web;

import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import uk.co.jimmythompson.robocleaner.cleaning.Cleaner;
import uk.co.jimmythompson.robocleaner.cleaning.CleanerPilot;
import uk.co.jimmythompson.robocleaner.cleaning.SpillTracker;

@RestController
public class MainController {

    @RequestMapping(path = "/", method = RequestMethod.POST)
    SpillTrackingResult trackSpill(@RequestBody SpillTrackingRequest request) {
        SpillTrackingRequestParser mapper = new SpillTrackingRequestParser(request);
        Cleaner cleaner = Cleaner.deploy(mapper.getStartingPosition());
        SpillTracker spillTracker = new SpillTracker(mapper.getOilPatches());

        spillTracker.follow(cleaner);

        CleanerPilot pilot = new CleanerPilot(mapper.getArea(), cleaner);

        pilot.navigate(mapper.getRoute());

        return new SpillTrackingResult(
                cleaner.getLocation(),
                spillTracker.getStatus().getCleanedPatches()
        );
    }
}
<file_sep>package uk.co.jimmythompson.robocleaner.cleaning;

import org.junit.Assert;
import org.junit.Test;

public class SpillStatusTests {
    @Test
    public void shouldHaveTheTotalNumberOfPatches() {
        SpillStatus spillStatus = new SpillStatus(1, 2);
        Assert.assertEquals(1, spillStatus.getTotalPatches());
    }

    @Test
    public void shouldHaveTheNumberOfCleanedPatches() {
        SpillStatus spillStatus = new SpillStatus(1, 2);
        Assert.assertEquals(2, spillStatus.getCleanedPatches());
    }
}
<file_sep>package uk.co.jimmythompson.robocleaner.cleaning;

import uk.co.jimmythompson.robocleaner.InvalidRouteException;
import uk.co.jimmythompson.robocleaner.geometry.Area;
import uk.co.jimmythompson.robocleaner.geometry.Coordinate;
import uk.co.jimmythompson.robocleaner.geometry.Direction;

public class CleanerPilot {
    private Area area;
    private Cleaner cleaner;

    public CleanerPilot(Area area, Cleaner cleaner) {
        this.area = area;
        this.cleaner = cleaner;
    }

    public void navigate(Iterable<Direction> route) {
        for (Direction direction: route) {
            if (!this.isValidMovement(direction)) {
                throw new InvalidRouteException();
            }

            this.cleaner.move(direction);
        }
    }

    private boolean isValidMovement(Direction direction) {
        Coordinate newLocation = this.cleaner.projectMovement(direction);
        return this.area.contains(newLocation);
    }
}
<file_sep>package uk.co.jimmythompson.robocleaner.cleaning;

import org.junit.Test;
import org.mockito.Mockito;
import uk.co.jimmythompson.robocleaner.InvalidRouteException;
import uk.co.jimmythompson.robocleaner.geometry.Area;
import uk.co.jimmythompson.robocleaner.geometry.Coordinate;
import uk.co.jimmythompson.robocleaner.geometry.Direction;

import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class CleanerPilotTests {
    @Test
    public void navigateShouldDirectTheCleaner() {
        Area mockArea = Mockito.mock(Area.class);
        Cleaner mockCleaner = Mockito.mock(Cleaner.class);
        CleanerPilot pilot = new CleanerPilot(mockArea, mockCleaner);

        pilot.navigate(asList(Direction.NORTH, Direction.EAST));

        Coordinate newLocation = new Coordinate(1, 1);
        when(mockCleaner.projectMovement(Direction.NORTH))
                .thenReturn(newLocation);
        when(mockArea.contains(newLocation)).thenReturn(true);

        verify(mockCleaner).move(Direction.NORTH);
        verify(mockCleaner).projectMovement(Direction.NORTH);
        verify(mockCleaner).move(Direction.EAST);
    }

    @Test(expected = InvalidRouteException.class)
    public void navigateShouldThrowAnExceptionWhenTheCleanerIsGoingToLeaveTheArea() {
        Area mockArea = Mockito.mock(Area.class);
        Cleaner mockCleaner = Mockito.mock(Cleaner.class);
        CleanerPilot pilot = new CleanerPilot(mockArea, mockCleaner);

        Coordinate newLocation = new Coordinate(1, 1);
        when(mockCleaner.projectMovement(Direction.NORTH))
                .thenReturn(newLocation);
        when(mockArea.contains(newLocation)).thenReturn(false);

        pilot.navigate(singletonList(Direction.NORTH));
    }
}
<file_sep>package uk.co.jimmythompson.robocleaner.web;

import uk.co.jimmythompson.robocleaner.cleaning.OilPatch;
import uk.co.jimmythompson.robocleaner.geometry.Area;
import uk.co.jimmythompson.robocleaner.geometry.Coordinate;
import uk.co.jimmythompson.robocleaner.geometry.Direction;

import java.util.List;
import java.util.stream.Collectors;

public class SpillTrackingRequestParser {
    private final SpillTrackingRequest request;

    private static Coordinate parseCoordinate(List<Integer> unparsed) {
        return new Coordinate(unparsed.get(0), unparsed.get(1));
    }

    public SpillTrackingRequestParser(SpillTrackingRequest request) {
        this.request = request;
    }

    public Area getArea() {
        Coordinate topRight = parseCoordinate(this.request.getAreaSize());
        return new Area(topRight);
    }

    public Coordinate getStartingPosition() {
        return parseCoordinate(this.request.getStartingPosition());
    }

    public List<OilPatch> getOilPatches() {
        return this.request.getOilPatches().stream()
                .map(SpillTrackingRequestParser::parseCoordinate)
                .map(OilPatch::new)
                .collect(Collectors.toList());
    }

    public List<Direction> getRoute() {
        return RouteParser.parseRoute(this.request.getNavigationInstructions());
    }
}
<file_sep>package uk.co.jimmythompson.robocleaner.web;

import uk.co.jimmythompson.robocleaner.geometry.Coordinate;

import java.util.List;

import static java.util.Arrays.asList;

public class SpillTrackingResult {
    private List<Integer> finalPosition;
    private Integer oilPatchesCleaned;

    private static List<Integer> coordinateToList(Coordinate coordinate) {
        return asList(coordinate.getX(), coordinate.getY());
    }

    public SpillTrackingResult(Coordinate finalPosition, int oilPatchesCleaned) {
        this.finalPosition = coordinateToList(finalPosition);
        this.oilPatchesCleaned = oilPatchesCleaned;
    }

    public List<Integer> getFinalPosition() {
        return finalPosition;
    }

    public Integer getOilPatchesCleaned() {
        return oilPatchesCleaned;
    }
}
<file_sep>package uk.co.jimmythompson.robocleaner;

public class InvalidRouteException extends IllegalArgumentException {
}
<file_sep>package uk.co.jimmythompson.robocleaner.geometry;

import java.util.Objects;

public class Coordinate {
    private int x;
    private int y;

    public Coordinate(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return this.x;
    }

    public int getY() {
        return this.y;
    }

    public Coordinate translate(Translation translation) {
        int newX = this.x + translation.getX();
        int newY = this.y + translation.getY();

        return new Coordinate(newX, newY);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }

        if (o == null) {
            return false;
        }

        if (getClass() != o.getClass()) {
            return false;
        }

        Coordinate other = (Coordinate) o;

        return Objects.equals(this.x, other.x)
                && Objects.equals(this.y, other.y);
    }

    @Override
    public String toString() {
        return "Coordinate{" +
                "x=" + x +
                ", y=" + y +
                '}';
    }
}
<file_sep>package uk.co.jimmythompson.robocleaner.cleaning;

import org.junit.Assert;
import org.junit.Test;
import uk.co.jimmythompson.robocleaner.geometry.Coordinate;
import uk.co.jimmythompson.robocleaner.geometry.Direction;

import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;

public class SpillTrackerTests {
    @Test
    public void shouldHaveOilPatches() {
        Coordinate location = new Coordinate(1,1);
        OilPatch oilPatch = new OilPatch(location);
        SpillTracker spillTracker = new SpillTracker(singletonList(oilPatch));

        Assert.assertEquals(singletonList(oilPatch), spillTracker.getOilPatches());
    }

    @Test
    public void shouldMarkOilPatchesAsCleanWhenTheCleanerGoesOverThem() {
        Coordinate oilPatchLocation = new Coordinate(1,1);
        OilPatch oilPatch = new OilPatch(oilPatchLocation);
        SpillTracker spillTracker = new SpillTracker(singletonList(oilPatch));

        Coordinate cleanerLocation = new Coordinate(1, 0);
        Cleaner cleaner = Cleaner.deploy(cleanerLocation);

        Assert.assertFalse(oilPatch.hasBeenCleaned());

        spillTracker.follow(cleaner);
        cleaner.move(Direction.NORTH);

        Assert.assertTrue(oilPatch.hasBeenCleaned());
    }

    @Test
    public void getStatusShouldReturnTheNumberOfPatches() {
        Coordinate firstLocation = new Coordinate(1,1);
        OilPatch firstPatch = new OilPatch(firstLocation);

        Coordinate secondLocation = new Coordinate(2,2);
        OilPatch secondPatch = new OilPatch(secondLocation);

        Coordinate thirdLocation = new Coordinate(3,3);
        OilPatch thirdPatch = new OilPatch(thirdLocation);

        SpillTracker spillTracker = new SpillTracker(asList(firstPatch, secondPatch, thirdPatch));

        Assert.assertEquals(3, spillTracker.getStatus().getTotalPatches());
    }

    @Test
    public void getStatusShouldReturnTheNumberOfCleanedPatches() {
        Coordinate firstLocation = new Coordinate(1,1);
        OilPatch firstPatch = new OilPatch(firstLocation);

        Coordinate secondLocation = new Coordinate(2,2);
        OilPatch secondPatch = new OilPatch(secondLocation);

        Coordinate thirdLocation = new Coordinate(3,3);
        OilPatch thirdPatch = new OilPatch(thirdLocation);

        SpillTracker spillTracker = new SpillTracker(asList(firstPatch, secondPatch, thirdPatch));

        Coordinate cleanerLocation = new Coordinate(1, 0);
        Cleaner cleaner = Cleaner.deploy(cleanerLocation);

        spillTracker.follow(cleaner);
        cleaner.move(Direction.NORTH);

        Assert.assertEquals(1, spillTracker.getStatus().getCleanedPatches());
    }
}
<file_sep>package uk.co.jimmythompson.robocleaner.web;

import uk.co.jimmythompson.robocleaner.InvalidDirectionException;
import uk.co.jimmythompson.robocleaner.geometry.Direction;

import java.util.ArrayList;
import java.util.List;

public class RouteParser {
    public static List<Direction> parseRoute(String routeAsString) {
        List<Direction> route = new ArrayList<>();

        for (String letter : routeAsString.split("")) {
            route.add(fromString(letter));
        }

        return route;
    }

    private static Direction fromString(String code) {
        switch (code) {
            case "N":
                return Direction.NORTH;
            case "E":
                return Direction.EAST;
            case "S":
                return Direction.SOUTH;
            case "W":
                return Direction.WEST;
        }

        throw new InvalidDirectionException();
    }
}
<file_sep>package uk.co.jimmythompson.robocleaner.cleaning;

import uk.co.jimmythompson.robocleaner.geometry.Coordinate;
import uk.co.jimmythompson.robocleaner.geometry.Direction;

import java.util.Observable;

public class Cleaner extends Observable {
    private Coordinate location;

    public static Cleaner deploy(Coordinate startingLocation) {
        return new Cleaner(startingLocation);
    }

    private Cleaner(Coordinate startingLocation) {
        this.location = startingLocation;
    }

    public Coordinate getLocation() {
        return this.location;
    }

    public Coordinate projectMovement(Direction direction) {
        return this.location.translate(direction.getTranslation());
    }

    public Coordinate move(Direction direction) {
        Coordinate newLocation = this.location.translate(direction.getTranslation());

        this.location = newLocation;

        this.setChanged();
        this.notifyObservers(newLocation);

        return newLocation;
    }
}
<file_sep>package uk.co.jimmythompson.robocleaner.cleaning;

import org.junit.Assert;
import org.junit.Test;
import uk.co.jimmythompson.robocleaner.geometry.Coordinate;

public class OilPatchTests {
    @Test
    public void getLocationShouldReturnLocation() {
        Coordinate location = new Coordinate(1, 1);
        OilPatch oilPatch = new OilPatch(location);

        Assert.assertEquals(location, oilPatch.getLocation());
    }

    @Test
    public void hasBeenCleanedShouldBeginAsFalse() {
        Coordinate location = new Coordinate(1, 1);
        OilPatch oilPatch = new OilPatch(location);

        Assert.assertFalse(oilPatch.hasBeenCleaned());
    }

    @Test
    public void markAsCleanedShouldMarkThePatchAsCleaned() {
        Coordinate location = new Coordinate(1, 1);
        OilPatch oilPatch = new OilPatch(location);

        oilPatch.markAsCleaned();

        Assert.assertTrue(oilPatch.hasBeenCleaned());
    }
}
